dusenyao преди 2 години
родител
ревизия
bcc0bc52dd

+ 13 - 0
.eslintrc.js

@@ -23,6 +23,7 @@ module.exports = {
     'plugin:vue/essential',
     'plugin:vue/strongly-recommended',
     'plugin:vue/recommended',
+    'plugin:prettier/recommended',
     '@vue/eslint-config-prettier'
   ],
 
@@ -45,6 +46,18 @@ module.exports = {
         }
       }
     ],
+    'vue/html-self-closing': [
+      1,
+      {
+        html: {
+          void: 'always',
+          normal: 'never',
+          component: 'always'
+        },
+        svg: 'always',
+        math: 'always'
+      }
+    ],
     'accessor-pairs': 2,
     'arrow-spacing': [
       2,

+ 7 - 5
.vscode/javascript.code-snippets → .vscode/base.code-snippets

@@ -1,15 +1,17 @@
 {
   "api request": {
-    "scope": "javascript,typescript",
+    "scope": "javascript",
     "prefix": "api",
     "body": [
+      "/**",
+      " * ",
+      " * @param {$4} ${5|data,params|}",
+      " */",
       "export function ${0:n}(data) {",
-      "  let params = getRequestParams('$1');",
-      "",
       "  return request({",
       "    method: 'post',",
-      "    url: process.env$2,",
-      "    params,",
+      "    url: process.env.${2|VUE_APP_FileServer,VUE_APP_LearnWebSI,VUE_APP_BookWebSI|},",
+      "    params: getRequestParams('$3'),",
       "    data",
       "  });",
       "}"

Файловите разлики са ограничени, защото са твърде много
+ 262 - 248
package-lock.json


+ 20 - 20
package.json

@@ -10,48 +10,48 @@
     "lint:css": "stylelint **/*.{html,vue,css,sass,scss,less}"
   },
   "dependencies": {
-    "axios": "^0.26.1",
-    "core-js": "^3.22.2",
-    "element-ui": "^2.15.8",
+    "axios": "^0.27.2",
+    "core-js": "^3.23.2",
+    "element-ui": "^2.15.9",
     "js-cookie": "^3.0.1",
     "md5": "^2.3.0",
     "normalize.css": "^8.0.1",
     "nprogress": "^0.2.0",
     "vue": "^2.6.14",
-    "vue-router": "^3.5.3",
+    "vue-router": "^3.5.4",
     "vuex": "^3.6.2"
   },
   "devDependencies": {
-    "@babel/core": "^7.17.9",
-    "@babel/eslint-parser": "^7.17.0",
+    "@babel/core": "^7.18.5",
+    "@babel/eslint-parser": "^7.18.2",
     "@rushstack/eslint-patch": "^1.1.3",
-    "@vue/cli-plugin-babel": "~4.5.17",
-    "@vue/cli-plugin-eslint": "~4.5.17",
-    "@vue/cli-plugin-router": "~4.5.17",
-    "@vue/cli-plugin-unit-jest": "^4.5.17",
-    "@vue/cli-plugin-vuex": "~4.5.17",
-    "@vue/cli-service": "~4.5.17",
+    "@vue/cli-plugin-babel": "~4.5.18",
+    "@vue/cli-plugin-eslint": "~4.5.18",
+    "@vue/cli-plugin-router": "~4.5.18",
+    "@vue/cli-plugin-unit-jest": "^4.5.18",
+    "@vue/cli-plugin-vuex": "~4.5.18",
+    "@vue/cli-service": "~4.5.18",
     "@vue/eslint-config-prettier": "^7.0.0",
     "@vue/test-utils": "^1.3.0",
     "babel-jest": "^27.5.1",
     "babel-plugin-dynamic-import-node": "^2.3.3",
     "eslint": "^7.32.0",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "^8.7.1",
-    "html-webpack-plugin": "^5.3.1",
-    "postcss": "^8.4.12",
+    "eslint-plugin-vue": "^9.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "postcss": "^8.4.14",
     "postcss-html": "^1.4.1",
-    "prettier": "2.6.2",
-    "sass": "^1.50.1",
+    "prettier": "2.7.1",
+    "sass": "^1.52.3",
     "sass-loader": "^10.2.1",
     "script-ext-html-webpack-plugin": "^2.1.5",
-    "stylelint": "^14.7.1",
+    "stylelint": "^14.9.1",
     "stylelint-config-prettier": "^9.0.3",
     "stylelint-config-recess-order": "^3.0.0",
     "stylelint-config-recommended-vue": "^1.4.0",
-    "stylelint-config-standard-scss": "^3.0.0",
+    "stylelint-config-standard-scss": "^4.0.0",
     "stylelint-declaration-block-no-ignored-properties": "^2.5.0",
-    "stylelint-webpack-plugin": "^3.1.0",
+    "stylelint-webpack-plugin": "^3.3.0",
     "svg-sprite-loader": "^6.0.11",
     "svgo": "^2.8.0",
     "vue-loader": "^15.9.8",

+ 26 - 0
src/api/org.js

@@ -132,3 +132,29 @@ export function EnableOrgQuota(data) {
     data
   });
 }
+
+/**
+ * 设置机构配额提醒量
+ * @param {Object} data
+ */
+export function SetOrgQuotaRemind(data) {
+  return request({
+    method: 'post',
+    url: process.env.VUE_APP_FileServer,
+    params: getRequestParams('live_quota_manager-SetOrgQuotaRemind'),
+    data
+  });
+}
+
+/**
+ * 得到机构配额提醒量
+ * @param {Object} data
+ */
+export function GetOrgQuotaRemind(data) {
+  return request({
+    method: 'post',
+    url: process.env.VUE_APP_FileServer,
+    params: getRequestParams('live_quota_manager-GetOrgQuotaRemind'),
+    data
+  });
+}

+ 87 - 0
src/components/common/DateSearch.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="date-search">
+    <span class="search-name">{{ name }}</span>
+    <el-date-picker v-model="join_org_time_min_date" type="date" value-format="yyyy-MM-dd" />&nbsp;
+    <el-select v-model="join_org_time_min_hour" class="date-hour">
+      <el-option v-for="item in hourArr" :key="item" :label="item" :value="item" />
+    </el-select>
+    <span> : </span>
+    <el-select v-model="join_org_time_min_minute" class="date-minute">
+      <el-option v-for="item in minuteArr" :key="item" :label="item" :value="item" />
+    </el-select>
+    <span> ~ </span>
+    <el-date-picker v-model="join_org_time_max_date" type="date" value-format="yyyy-MM-dd" />&nbsp;
+    <el-select v-model="join_org_time_max_hour" class="date-hour">
+      <el-option v-for="item in hourArr" :key="item" :label="item" :value="item" />
+    </el-select>
+    <span> : </span>
+    <el-select v-model="join_org_time_max_minute" class="date-minute">
+      <el-option v-for="item in minuteArr" :key="item" :label="item" :value="item" />
+    </el-select>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    name: {
+      type: String,
+      required: true
+    }
+  },
+  data() {
+    return {
+      hourArr: [],
+      minuteArr: [],
+      join_org_time_min_date: '',
+      join_org_time_min_hour: '00',
+      join_org_time_min_minute: '00',
+      join_org_time_max_date: '',
+      join_org_time_max_hour: '00',
+      join_org_time_max_minute: '00'
+    };
+  },
+  computed: {
+    join_org_time_min() {
+      if (this.join_org_time_min_date.length <= 0) return '';
+      return `${this.join_org_time_min_date} ${this.join_org_time_min_hour}:${this.join_org_time_min_minute}`;
+    },
+    join_org_time_max() {
+      if (this.join_org_time_max_date.length <= 0) return '';
+      return `${this.join_org_time_max_date} ${this.join_org_time_max_hour}:${this.join_org_time_max_minute}`;
+    }
+  },
+  created() {
+    for (let i = 0; i < 60; i++) {
+      let item = String(i);
+      if (i < 10) item = `0${item}`;
+      if (i < 24) this.hourArr.push(item);
+      this.minuteArr.push(item);
+    }
+  },
+  methods: {}
+};
+</script>
+
+<style lang="scss" scoped>
+.date-search {
+  margin-top: 8px;
+
+  span.search-name {
+    margin-right: 14px;
+  }
+
+  .date-minute,
+  .date-hour {
+    width: 78px;
+
+    > .el-input {
+      width: 100%;
+    }
+  }
+
+  .el-date-editor.el-input {
+    width: 160px;
+  }
+}
+</style>

+ 97 - 0
src/components/quota/SetOrgQuotaRemind.vue

@@ -0,0 +1,97 @@
+<template>
+  <el-dialog
+    title="设置余额提醒"
+    width="480px"
+    :visible="visible"
+    :before-close="handleClose"
+    :close-on-click-modal="false"
+  >
+    <div class="tips">输入时长,系统将在直播可用时长低于设置数值时进行提醒。</div>
+    <el-form label-width="90px" label-position="left" size="small">
+      <el-form-item label="1v1配额:"> <el-input v-model="quota_remind_1v1_online" /> 小时 </el-form-item>
+      <el-form-item label="1v多配额:"> <el-input v-model="quota_remind_1vm_online" /> 小时 </el-form-item>
+    </el-form>
+
+    <span slot="footer">
+      <el-button class="confirm" @click="setOrgQuotaRemind">确定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { SetOrgQuotaRemind, GetOrgQuotaRemind } from '@/api/org';
+
+export default {
+  props: {
+    orgId: {
+      type: String,
+      required: true
+    },
+    visible: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data() {
+    return {
+      quota_remind_1v1_online: 0,
+      quota_remind_1vm_online: 0
+    };
+  },
+  watch: {
+    orgId(newVal) {
+      if (!this.visible) return;
+      if (newVal.length <= 0) return;
+      this.getOrgQuotaRemind();
+    }
+  },
+  methods: {
+    getOrgQuotaRemind() {
+      GetOrgQuotaRemind({ org_id: this.orgId }).then(({ quota_remind_1v1_online, quota_remind_1vm_online }) => {
+        this.quota_remind_1v1_online = quota_remind_1v1_online;
+        this.quota_remind_1vm_online = quota_remind_1vm_online;
+      });
+    },
+
+    setOrgQuotaRemind() {
+      SetOrgQuotaRemind({
+        org_id: this.orgId,
+        quota_remind_1v1_online: this.quota_remind_1v1_online,
+        quota_remind_1vm_online: this.quota_remind_1vm_online
+      }).then(() => {
+        this.$message.success('设置机构配额提醒量成功');
+        this.handleClose();
+      });
+    },
+
+    handleClose() {
+      this.$emit('close');
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.el-dialog {
+  ::v-deep &__body {
+    padding-top: 16px;
+    padding-bottom: 16px;
+  }
+
+  .tips {
+    margin-bottom: 8px;
+    color: $tips-color;
+  }
+
+  .el-form {
+    .el-input {
+      width: 122px;
+    }
+  }
+
+  .confirm {
+    color: #fff;
+    background-color: #597ef7;
+  }
+}
+</style>

+ 1 - 1
src/styles/common.scss

@@ -15,6 +15,6 @@
   text-align: right;
 }
 
-.operation {
+.operable {
   cursor: pointer;
 }

+ 4 - 0
src/styles/element-ui.scss

@@ -8,3 +8,7 @@
     background-color: #f19100;
   }
 }
+
+.el-dialog__header {
+  border-bottom: 1px solid #f0f0f0;
+}

+ 1 - 0
src/styles/variables.scss

@@ -2,3 +2,4 @@ $header-h: 49px;
 $bac-color: #c4c4c4;
 $basic-color: #f90;
 $border-color: #999;
+$tips-color: #808080;

+ 1 - 0
src/views/account_manager/OrgList.vue

@@ -1,3 +1,4 @@
+<!-- TODO 待删除 -->
 <template>
   <el-dialog class="org-list" :visible="visible" :before-close="handleClose">
     <div slot="title">用户【{{ curUserName }}】加入的机构列表</div>

+ 3 - 24
src/views/account_manager/index.vue

@@ -8,12 +8,7 @@
           <el-select v-model="org_id" clearable class="org-type"></el-select> -->
           <span class="search-name">用户类型</span>
           <el-select v-model="user_type" clearable class="user-type">
-            <el-option
-              v-for="item in userTypeList"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            ></el-option>
+            <el-option v-for="item in userTypeList" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
           <span class="search-name">用户名</span>
           <el-input v-model="user_name" class="account-search" type="text" @keyup.enter.native="queryAccountList" />
@@ -21,7 +16,7 @@
           <el-input v-model="real_name" class="account-search" type="text" @keyup.enter.native="queryAccountList" />
         </el-col>
         <el-col :span="2">
-          <el-button class="search-button" icon="el-icon-search" @click="queryAccountList"></el-button>
+          <el-button class="search-button" icon="el-icon-search" @click="queryAccountList" />
         </el-col>
       </el-row>
     </div>
@@ -34,13 +29,7 @@
         <el-table-column prop="user_name" label="用户名" width="180" />
         <el-table-column prop="real_name" label="姓名" width="180" />
         <el-table-column prop="user_type_name" label="用户类型" width="120" />
-        <el-table-column label="加入的机构数量" width="150">
-          <template slot-scope="{ row }">
-            <span class="link" @click="showUserOrgList(row.id, row.user_name)">
-              {{ row.org_count }}
-            </span>
-          </template>
-        </el-table-column>
+        <el-table-column prop="org_name_desc" label="加入机构名称" width="300" />
         <el-table-column prop="email" label="邮箱" />
       </el-table>
     </div>
@@ -56,18 +45,14 @@
       @current-change="changePage"
       @size-change="changePageSize"
     />
-
-    <org-list ref="orgList" :user-id="userId" :cur-user-name="curUserName" @orgListClose="orgListClose" />
   </div>
 </template>
 
 <script>
-import OrgList from './OrgList.vue';
 import { pageQueryUserList } from '@/api/list';
 
 export default {
   name: 'AccountManager',
-  components: { OrgList },
   data() {
     return {
       org_id: '',
@@ -126,12 +111,6 @@ export default {
       console.log(i, row);
     },
 
-    showUserOrgList(id, user_name) {
-      this.userId = id;
-      this.curUserName = user_name;
-      this.$refs.orgList.show();
-    },
-
     orgListClose() {
       this.userId = '';
       this.curUserName = '';

+ 20 - 5
src/views/quota/index.vue

@@ -21,17 +21,18 @@
         <el-table-column prop="quota_lave" label="1v多余额" width="160">
           <template slot-scope="{ row }"> {{ row.money_lave_1vm_online }}元 </template>
         </el-table-column>
-        <el-table-column prop="quota_status" label="状态" width="160">
+        <el-table-column prop="quota_status" label="状态" width="96">
           <template slot-scope="{ row }">
             <span :style="{ color: row.quota_status ? '#019319' : '#CA0000' }">
               {{ row.quota_status ? '使用中' : '已停用' }}
             </span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" fixed="right">
+        <el-table-column label="操作" fixed="right" width="200">
           <template slot-scope="{ row }">
-            <span class="set-quota operation" @click="setQuota(row.org_id)">设置配额</span>
-            <span class="operation" @click="setStatus(row.org_id, !row.quota_status)">
+            <span class="set-quota operable" @click="setQuota(row.org_id)">设置配额</span>
+            <span class="set-quota operable" @click="quotaRemind(row.org_id)">余额提醒</span>
+            <span class="operable" @click="setStatus(row.org_id, !row.quota_status)">
               {{ row.quota_status ? '停用' : '启用' }}
             </span>
           </template>
@@ -53,6 +54,7 @@
     />
 
     <set-quota :org-id="curOrgId" :visible="visible" @close="closeSetQuota" />
+    <set-remind :org-id="curOrgId" :visible="visible_remind" @close="closeQuotaRemind" />
   </div>
 </template>
 
@@ -60,10 +62,11 @@
 import { PageQueryOrgQuotaList } from '@/api/list';
 import { EnableOrgQuota } from '@/api/org';
 import SetQuota from './SetQuota.vue';
+import SetRemind from '@/components/quota/SetOrgQuotaRemind.vue';
 
 export default {
   name: 'QuotaList',
-  components: { SetQuota },
+  components: { SetQuota, SetRemind },
   data() {
     return {
       data_list: [],
@@ -71,6 +74,7 @@ export default {
       total_count: 0,
       cur_page: 1,
       visible: false,
+      visible_remind: false,
       curOrgId: ''
     };
   },
@@ -115,6 +119,17 @@ export default {
     closeSetQuota() {
       this.visible = false;
       this.curOrgId = '';
+    },
+
+    // 余额提醒
+    quotaRemind(org_id) {
+      this.visible_remind = true;
+      this.curOrgId = org_id;
+    },
+
+    closeQuotaRemind() {
+      this.visible_remind = false;
+      this.curOrgId = '';
     }
   }
 };

+ 16 - 5
src/views/student_manager/index.vue

@@ -2,7 +2,7 @@
   <div class="student-manager">
     <!--搜索-->
     <div class="student-manager-search">
-      <el-row type="flex" justify="space-between">
+      <el-row type="flex" justify="space-between" class="search-condition">
         <el-col :span="20">
           <span class="search-name">用户名</span>
           <el-input
@@ -26,9 +26,10 @@
           <el-select v-model="is_audited" class="account-search">
             <el-option v-for="item in auditList" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
+          <date-search ref="dateSearch" name="加入机构时间" />
         </el-col>
         <el-col :span="2">
-          <el-button class="search-button" icon="el-icon-search" @click="pageQueryOrgStudentUserList"></el-button>
+          <el-button class="search-button" icon="el-icon-search" @click="pageQueryOrgStudentUserList" />
         </el-col>
       </el-row>
     </div>
@@ -41,8 +42,8 @@
         <el-table-column prop="user_name" label="用户名" width="180" />
         <el-table-column prop="user_real_name" label="姓名" width="180" />
         <el-table-column prop="org_name" label="机构名称" width="180" />
-        <el-table-column prop="user_phone" label="手机号" width="120" />
         <el-table-column prop="user_email" label="邮箱" width="180" />
+        <el-table-column prop="join_org_time" label="加入机构时间" width="160" />
         <el-table-column prop="is_audited" label="已审核">
           <template slot-scope="{ row }">
             <div :style="{ 'text-align': 'center', width: '40px' }">{{ row.is_audited === 'true' ? '√' : '' }}</div>
@@ -53,7 +54,7 @@
             <div :style="{ 'text-align': 'center', width: '40px' }">{{ row.is_valid === 'true' ? '√' : '' }}</div>
           </template>
         </el-table-column>
-        <el-table-column fixed="right" width="180">
+        <el-table-column fixed="right" width="160">
           <template slot-scope="{ row }">
             <el-row type="flex" justify="space-between">
               <el-col>
@@ -88,8 +89,10 @@
 <script>
 import { PageQueryOrgStudentUserList } from '@/api/list';
 import { AuditOrgStudentUser } from '@/api/student';
+import DateSearch from '@/components/common/DateSearch.vue';
 
 export default {
+  components: { DateSearch },
   data() {
     return {
       user_name: '',
@@ -141,7 +144,9 @@ export default {
         is_valid: this.is_valid,
         is_audited: this.is_audited,
         page_capacity: this.page_capacity,
-        cur_page: this.cur_page
+        cur_page: this.cur_page,
+        join_org_time_min: this.$refs?.dateSearch?.join_org_time_min ?? '',
+        join_org_time_max: this.$refs?.dateSearch?.join_org_time_max ?? ''
       }).then(({ org_student_user_list, total_page, cur_page, total_count }) => {
         this.org_student_user_list = org_student_user_list;
         this.cur_page = cur_page;
@@ -192,11 +197,17 @@ export default {
     .search-button {
       float: right;
     }
+
+    .search-condition {
+      align-items: center;
+    }
   }
 
   &-list {
     @include list;
 
+    min-height: 554px;
+
     &-title {
       font-size: 20px;
       font-weight: 400;

+ 26 - 9
src/views/teacher_manager/index.vue

@@ -2,7 +2,7 @@
   <div class="teacher-manager">
     <!--搜索-->
     <div class="teacher-manager-search">
-      <el-row type="flex" justify="space-between">
+      <el-row type="flex" justify="space-between" class="search-condition">
         <el-col :span="20">
           <span class="search-name">用户名</span>
           <el-input
@@ -19,12 +19,13 @@
             @keyup.enter.native="queryOrgTeacherUserList"
           />
           <span class="search-name">审核状态</span>
-          <el-select v-model="is_audited">
+          <el-select v-model="is_audited" :style="{ 'margin-right': '12px' }">
             <el-option v-for="{ value, label } in auditedList" :key="value" :value="value" :label="label" />
           </el-select>
+          <date-search ref="dateSearch" name="加入机构时间" />
         </el-col>
         <el-col :span="2">
-          <el-button class="search-button" icon="el-icon-search" @click="queryOrgTeacherUserList"></el-button>
+          <el-button class="search-button" icon="el-icon-search" @click="queryOrgTeacherUserList" />
         </el-col>
       </el-row>
     </div>
@@ -37,8 +38,8 @@
         <el-table-column prop="user_name" label="用户名" width="180" />
         <el-table-column prop="user_real_name" label="姓名" width="180" />
         <el-table-column prop="org_name" label="服务机构" width="180" />
-        <el-table-column prop="user_phone" label="手机号" width="120" />
         <el-table-column prop="user_email" label="邮箱" width="180" />
+        <el-table-column prop="join_org_time" label="加入机构时间" width="180" />
         <el-table-column prop="is_audited" label="已审核">
           <template slot-scope="{ row }">
             <div :style="{ 'text-align': 'center', width: '40px' }">{{ row.is_audited === 'true' ? '√' : '' }}</div>
@@ -92,9 +93,13 @@
 <script>
 import { pageQueryOrgTeacherUserList } from '@/api/list';
 import { auditOrgTeacherUser, getPopedomList_OrgTeacherUse, setPopedom_OrgTeacherUser } from '@/api/teacher';
+import DateSearch from '@/components/common/DateSearch.vue';
 
 export default {
   name: 'TeacherManager',
+  components: {
+    DateSearch
+  },
   data() {
     return {
       user_name: '',
@@ -129,17 +134,21 @@ export default {
       this.cur_page = newPage;
       this.queryOrgTeacherUserList();
     },
+
     changePageSize(pageSize) {
       this.page_capacity = pageSize;
       this.queryOrgTeacherUserList();
     },
+
     queryOrgTeacherUserList() {
       pageQueryOrgTeacherUserList({
         user_name: this.user_name,
         user_real_name: this.user_real_name,
         page_capacity: this.page_capacity,
         cur_page: this.cur_page,
-        is_audited: this.is_audited
+        is_audited: this.is_audited,
+        join_org_time_min: this.$refs?.dateSearch?.join_org_time_min ?? '',
+        join_org_time_max: this.$refs?.dateSearch?.join_org_time_max ?? ''
       }).then(({ cur_page, org_teacher_user_list, total_count }) => {
         this.cur_page = cur_page;
         this.org_teacher_user_list = org_teacher_user_list;
@@ -167,10 +176,12 @@ export default {
     },
     // 设置教师权限
     setPopedom(user_org_id) {
-      let popedom_code_list = [];
-      this.popedom_list.forEach(({ popedom_code, is_selected }) => {
-        if (is_selected) popedom_code_list.push(popedom_code);
-      });
+      let popedom_code_list = this.popedom_list
+        .map(({ popedom_code, is_selected }) => {
+          if (is_selected) return popedom_code;
+        })
+        .filter(item => item !== undefined);
+
       setPopedom_OrgTeacherUser({
         user_org_id,
         popedom_code_list
@@ -196,6 +207,10 @@ export default {
       margin-right: 14px;
     }
 
+    .search-condition {
+      align-items: center;
+    }
+
     .account-search {
       width: 240px;
       margin-right: 48px;
@@ -209,6 +224,8 @@ export default {
   &-list {
     @include list;
 
+    min-height: 554px;
+
     &-title {
       font-size: 20px;
       font-weight: 400;

Някои файлове не бяха показани, защото твърде много файлове са промени