浏览代码

角色管理 & 用户管理

zhengnaiwen_citu 7 月之前
父节点
当前提交
0f2a054ade

+ 5 - 0
src/api/user.js

@@ -19,6 +19,11 @@ export function saveUser (data) {
   return http.post('/authentication/user/save', data)
 }
 
+// 冻结用户
+export function blockUser (data) {
+  return http.post('/authentication/user/block', data)
+}
+
 // 删除用户
 export function deleteUser (data) {
   return http.post('/authentication/user/del', data)

+ 19 - 0
src/components/AutoComponents/MEmpty/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <el-empty :description="description"></el-empty>
+</template>
+
+<script>
+export default {
+  name: 'm-empty',
+  props: {
+    description: {
+      type: String,
+      default: '暂无内容'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 57 - 45
src/components/AutoComponents/MForm/index.vue

@@ -8,55 +8,69 @@
     v-bind="$attrs"
     v-on="$listeners"
   >
-    <el-form-item
-      v-for="item in items"
-      :key="item.prop"
-      v-bind="item"
-    >
-      <template v-if="!item.type">
-        <slot :name="item.prop"></slot>
-      </template>
-      <template v-if="item.type === 'input'">
-        <el-input v-bind="item.options" v-on="item.handles" v-model="query[item.prop]">
-          <template v-for="slot in item.slots" :slot="slot">
-            <slot :name="`${item.prop}.${slot}`"></slot>
-          </template>
-        </el-input>
-      </template>
-      <template v-if="item.type === 'number'">
-        <el-input-number v-bind="item.options" v-on="item.handles" v-model="query[item.prop]"></el-input-number>
-      </template>
-      <template v-if="item.type === 'select'">
-        <el-select v-bind="item.options" v-on="item.handles" v-model="query[item.prop]" placeholder="请选择">
-          <template v-for="slot in item.slots" :slot="slot">
-            <slot :name="`${item.prop}.${slot}`"></slot>
-          </template>
-          <template v-if="item.options.groups">
-            <el-option-group
-              v-for="group in item.options.groups"
-              :key="group.label"
-              :label="group.label"
-            >
+    <template v-for="item in items">
+      <el-form-item
+        v-if="!item.hidden"
+        :key="item.prop"
+        v-bind="item"
+      >
+        <template v-if="!item.type">
+          <slot :name="item.prop"></slot>
+        </template>
+        <template v-if="item.type === 'input'">
+          <el-input v-bind="item.options" v-on="item.handles" v-model="query[item.prop]">
+            <template v-for="slot in item.slots" :slot="slot">
+              <slot :name="`${item.prop}.${slot}`"></slot>
+            </template>
+          </el-input>
+        </template>
+        <template v-if="item.type === 'number'">
+          <el-input-number v-bind="item.options" v-on="item.handles" v-model="query[item.prop]"></el-input-number>
+        </template>
+        <template v-if="item.type === 'select'">
+          <el-select v-bind="item.options" v-on="item.handles" v-model="query[item.prop]" placeholder="请选择">
+            <template v-for="slot in item.slots" :slot="slot">
+              <slot :name="`${item.prop}.${slot}`"></slot>
+            </template>
+            <template v-if="item.options.groups">
+              <el-option-group
+                v-for="group in item.options.groups"
+                :key="group.label"
+                :label="group.label"
+              >
+                <el-option
+                  v-for="_item in group.items"
+                  :key="_item.label"
+                  v-bind="_item"
+                >
+                </el-option>
+              </el-option-group>
+            </template>
+            <template v-else>
               <el-option
-                v-for="_item in group.items"
+                v-for="_item in item.options.items"
                 :key="_item.label"
-                v-bind="_item"
-              >
-              </el-option>
-            </el-option-group>
-          </template>
-          <template v-else>
-            <el-option
+                :label="_item.label"
+                :value="_item.value"
+                :disable="_item.disable"
+              ></el-option>
+            </template>
+          </el-select>
+        </template>
+        <template v-if="item.type === 'radioGroup'">
+          <el-radio-group v-model="query[item.prop]">
+            <el-radio
               v-for="_item in item.options.items"
               :key="_item.label"
+              v-model="query[item.prop]"
               v-bind="_item"
             >
-              <template></template>
-            </el-option>
-          </template>
-        </el-select>
-      </template>
-    </el-form-item>
+            {{ _item.text }}
+            </el-radio>
+          </el-radio-group>
+        </template>
+      </el-form-item>
+    </template>
     <slot></slot>
   </el-form>
 </template>
@@ -87,14 +101,12 @@ export default {
   watch: {
     value: {
       handler (val) {
-        console.log('changeValue', val)
         this.query = val
       },
       deep: true
     },
     query: {
       handler (val) {
-        console.log('changeQuery', val)
         this.$emit('input', val)
       },
       deep: true

+ 9 - 1
src/components/AutoComponents/MSearch/index.vue

@@ -1,6 +1,14 @@
 <template>
   <m-card>
-    <el-form :inline="true" :model="form" ref="form" label-width="80px" size="small" :show-message="false" inline-message>
+    <el-form
+      :inline="true"
+      :model="form"
+      ref="form"
+      label-width="80px"
+      size="small"
+      :show-message="false"
+      inline-message
+    >
       <el-form-item
         v-for="(item, index) in items"
         :key="index"

+ 3 - 3
src/components/AutoComponents/MTable/index.vue

@@ -19,14 +19,14 @@
         </template>
       </m-table-column>
     </el-table>
-    <div class="pt-3">
+    <div class="pt-3 text-right">
       <el-pagination
         @current-change="handleCurrentChange"
         :current-page="pageCurrent"
+        background
         :page-size="pageSize"
         layout="total, prev, pager, next, jumper"
         :total="total"
-        :hide-on-single-page="true"
       >
       </el-pagination>
     </div>
@@ -62,7 +62,7 @@ export default {
   },
   methods: {
     handleCurrentChange (val) {
-      this.$emit('pageChange', val)
+      this.$emit('page-change', val)
     }
   }
 }

+ 4 - 7
src/permission.js

@@ -3,8 +3,7 @@ import Layout from '@/layout'
 import { checkToken, refreshToken } from '@/api/system'
 import {
   getToken,
-  setToken,
-  deleteToken
+  setToken
 } from './utils/auth'
 import store from './store'
 import Vue from 'vue'
@@ -69,16 +68,14 @@ async function onFilterRoutes (to, _next) {
     const res = await store.dispatch('menu/getMenu2')
     if (!res || !res.length) {
       // 无权限 返回登录页面
-      Vue.prototype.$message.error('当前账户没有菜单权限,请联系管理员')
-      deleteToken()
-      _next()
+      Vue.prototype.$message.error('无权限账户,请联系管理员')
+      store.dispatch('user/userLogout')
       return
     }
     initRoutes(to, _next, res)
   } catch (error) {
-    deleteToken()
+    store.dispatch('user/userLogout')
     Vue.prototype.$message.error(error)
-    _next({ ...to, replace: true })
   }
 }
 

+ 1 - 0
src/store/modules/user.js

@@ -54,6 +54,7 @@ const actions = {
    * @param { Boolean } isLogin 是否调用回到登录页方法
    */
   userLogout ({ commit }) {
+    debugger
     // 清空token
     deleteToken()
     // 清除路由

+ 22 - 1
src/styles/index.scss

@@ -59,4 +59,25 @@ $max-classes: 10;
 
 .white {
   background-color: white;
-}
+}
+
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-justify {
+  text-align: justify;
+}
+
+.text-nowrap {
+  white-space: nowrap;
+}

+ 6 - 2
src/utils/request.js

@@ -4,7 +4,7 @@ import { blobToJson } from '@/utils'
 import { checkToken } from '@/api/system'
 import route from '@/router'
 import qs from 'qs'
-// import Vue from 'vue'
+import Vue from 'vue'
 // create an axios instance
 const service = axios.create({
   baseURL: window?.g?.VUE_APP_BASE_API ?? process.env.VUE_APP_BASE_API,
@@ -59,7 +59,11 @@ service.interceptors.response.use(
     if ([50008, 50012, 50014, 402000, 401].includes(res.code)) {
       // Vue.prototype.$message.error('登陆过期,请重新登陆')
       const str = '登陆过期,请重新登陆'
-      checkToken()
+      try {
+        checkToken()
+      } catch (error) {
+        Vue.prototype.$message.error(error)
+      }
       return Promise.reject(str)
     }
     if (res.code === 302) {

+ 3 - 1
src/views/home/index.vue

@@ -1,5 +1,7 @@
 <template>
-  <div>111</div>
+  <div>
+    <m-empty></m-empty>
+  </div>
 </template>
 
 <script>

+ 7 - 9
src/views/systemManage/roleManage/index.vue

@@ -13,7 +13,7 @@
       :headers="headers"
       :page-size="pageInfo.size"
       :page-current="pageInfo.current"
-      :total="pageInfo.total"
+      :total="total"
       @page-change="handlePageChange"
     >
       <template #createTime="scope">
@@ -85,11 +85,6 @@ export default {
       roleFormValue: {
         roleName: null
       },
-      menuFormItems: [
-        {
-          label: '角色名称'
-        }
-      ],
       searchValues: {
         roleName: null
       },
@@ -97,6 +92,9 @@ export default {
         {
           label: '角色名称',
           prop: 'roleName',
+          option: {
+            placeholder: '请输入角色名称'
+          },
           type: 'input'
         }
       ],
@@ -105,9 +103,9 @@ export default {
       show: false,
       showMenu: false,
       headers: [
-        { label: '角色', align: 'start', prop: 'roleName' },
-        { label: '创建时间', align: 'start', prop: 'createTime' },
-        { label: '操作', align: 'start', prop: 'actions', fixed: 'right' }
+        { label: '角色', prop: 'roleName' },
+        { label: '创建时间', prop: 'createTime' },
+        { label: '操作', prop: 'actions', fixed: 'right' }
       ],
       items: [],
       total: 0,

+ 318 - 91
src/views/systemManage/user/index.vue

@@ -5,6 +5,12 @@
         <m-button type="primary" icon="el-icon-plus" @click="onAdd">
           新增
         </m-button>
+        <m-button type="primary" icon="el-icon-upload2">
+          批量导入
+        </m-button>
+        <m-button type="primary" icon="el-icon-download">
+          模板下载
+        </m-button>
       </template>
     </m-search>
     <MTable
@@ -13,54 +19,111 @@
       :headers="headers"
       :page-size="pageInfo.size"
       :page-current="pageInfo.current"
-      :total="pageInfo.total"
+      :total="total"
       @page-change="handlePageChange"
     >
-      <template #createTime="scope">
-        {{ dateFormat(scope.row.createTime) }}
+      <template #createdTime="scope">
+        {{ dateFormat(scope.row.createdTime) }}
+      </template>
+      <template #state="scope">
+        <el-tag size="small" :type="scope.row.state ? 'default' : 'success'">{{ scope.row.state ? '未启用' : '已启用' }}</el-tag>
+      </template>
+      <template #role="scope">
+        <template v-if="scope.row.companyInfo?.homeUserId === scope.row.id">
+          <el-tag size="small" type="danger">超级管理员</el-tag>
+        </template>
+        <template v-if="scope.row.role && scope.row.role.length">
+          <el-tag
+            v-for="item in scope.row.role"
+            :key="item.id"
+            size="small"
+          >{{ item.roleName }}</el-tag>
+        </template>
       </template>
       <template #actions="scope">
-        <m-button type="primary" text @click="onEdit(scope.row)">编辑</m-button>
-        <m-button type="warning" text @click="onMenu(scope.row)">重置密码</m-button>
-        <m-button type="danger" text @click="onDelete(scope.row)">删除</m-button>
+        <template v-if="scope.row.companyInfo?.homeUserId !== scope.row.id">
+          <m-button
+            :type="scope.row.state ? 'success' : 'danger'"
+            class="pa-0"
+            text
+            @click="onStatus(scope.row)"
+          >
+            {{ scope.row.state ? '启用' : '禁用' }}
+          </m-button>
+          <m-button type="primary" class="pa-0" text @click="onEdit(scope.row)">编辑</m-button>
+          <m-button type="primary" class="pa-0" text @click="onSetRole(scope.row)">角色分配</m-button>
+          <m-button type="warning" class="pa-0" text @click="onReset(scope.row)">重置密码</m-button>
+          <m-button type="danger" class="pa-0" text @click="onDelete(scope.row)">删除</m-button>
+        </template>
       </template>
     </MTable>
+    <MDialog ref="dialog" :title="itemData.id ? '编辑用户' : '新增用户'" @sure="handleSave">
+      <MForm ref="userForm" :items="userFormItems" v-model="userFormValue"></MForm>
+    </MDialog>
+    <MDialog ref="role" title="角色分配" @sure="handleSaveRole">
+      <MForm ref="roleForm" :items="roleFormItems" v-model="roleFormValue">
+        <template #username>
+          <el-tag>{{ itemData.username }}</el-tag>
+        </template>
+        <template #name>
+          <el-tag>{{ itemData.name }}</el-tag>
+        </template>
+      </MForm>
+    </MDialog>
+    <MDialog ref="password" title="重置密码" @sure="handleSavePassword">
+      <MForm ref="passwordForm" :items="passwordFormItems" v-model="passwordFormValue"></MForm>
+    </MDialog>
   </div>
 </template>
 
 <script>
 import util from '@/utils/base64ToFile'
-import { getUserList, saveUser, deleteUser, resetPassword, downloadUserTemplate, userExcelExport } from '@/api/user'
+import {
+  getUserList,
+  saveUser,
+  deleteUser,
+  resetPassword,
+  downloadUserTemplate,
+  userExcelExport,
+  blockUser
+} from '@/api/user'
 import { getRoleList } from '@/api/menu'
-import { currentTime } from '@/utils/date'
+import { dateFormat } from '@/utils/date'
 export default {
   name: 'user-list',
-  components: { },
   data () {
     return {
       searchItems: [
         {
-          label: '用户名称',
-          prop: 'username',
+          label: '用户查找',
+          option: {
+            placeholder: '请输入用户昵称 / 账号'
+          },
+          prop: 'searchKey',
           type: 'input'
         }
       ],
-      query: {
-        name: null,
-        username: null
+      searchValues: {
+        searchKey: null
+      },
+      roleFormValue: {
+        roleId: null
+      },
+      userFormValue: {
+        type: 1
+      },
+      passwordFormValue: {
+        newPwd: null
       },
-      showReset: false,
       headers: [
-        { text: '用户昵称', align: 'start', value: 'name' },
-        { text: '账号', align: 'start', value: 'username' },
-        { text: '人员编码', value: 'employeeCode' },
-        { text: '电话', align: 'start', value: 'phone' },
-        { text: '角色', align: 'start', value: 'role' },
-        { text: '状态', align: 'start', value: 'state' },
-        { text: '登陆时间', align: 'start', value: 'loginTime' },
-        { text: '上次登录时间', align: 'start', value: 'lastLoginTime' },
-        { text: '创建时间', align: 'start', value: 'createdTime' },
-        { text: '操作', align: 'start', value: 'actions' }
+        { label: '用户昵称', prop: 'name', width: 200 },
+        { label: '账号', prop: 'username', width: 200 },
+        { label: '人员编码', prop: 'employeeCode' },
+        { label: '角色', prop: 'role' },
+        { label: '状态', prop: 'state' },
+        { label: '上次登录时间', prop: 'lastLoginTime', width: 150 },
+        { label: '创建时间', prop: 'createdTime', width: 200 },
+        { label: '操作', prop: 'actions', fixed: 'right', width: 300 }
       ],
       items: [],
       total: 0,
@@ -69,41 +132,145 @@ export default {
         size: 10
       },
       loading: false,
-      isEdit: false,
+      itemData: {},
+      showReset: false,
       show: false,
-      title: '新增',
       roleList: [],
-      itemData: {},
       uploadLoading: false
     }
   },
+  computed: {
+    passwordFormItems () {
+      return [
+        {
+          label: '新密码',
+          prop: 'newPwd',
+          type: 'input',
+          options: {
+            showPassword: true,
+            placeholder: '请输入新密码'
+          },
+          rules: [
+            { required: true, message: '请输入密码', trigger: 'change' }
+          ]
+        }
+      ]
+    },
+    roleFormItems () {
+      return [
+        {
+          label: '用户名称',
+          prop: 'username'
+        },
+        {
+          label: '用户昵称',
+          prop: 'name'
+        },
+        {
+          label: '角色',
+          prop: 'roleId',
+          type: 'select',
+          options: {
+            multiple: true,
+            items: this.roleList.map(e => {
+              return {
+                label: e.roleName,
+                value: e.id
+              }
+            })
+          },
+          rules: [
+            { required: true, message: '请选择角色', trigger: 'change' }
+          ]
+        }
+      ]
+    },
+    userFormItems () {
+      const hidden = this.userFormValue.type === 2
+      return [
+        {
+          label: '账户类型',
+          prop: 'type',
+          type: 'radioGroup',
+          options: {
+            items: [
+              { text: '统一认证号用户', label: 1 },
+              { text: '系统普通用户', label: 2 }
+            ]
+          },
+          rules: [
+            { required: true, message: '请选择账户类型', trigger: 'blur' }
+          ]
+        },
+        {
+          label: !hidden ? '统一认证号' : '用户账号',
+          prop: 'username',
+          type: 'input',
+          options: {
+            placeholder: '请输入' + (!hidden ? '统一认证号' : '用户账号')
+          },
+          rules: [
+            { required: true, message: `请输入${!hidden ? '统一认证号' : '用户账号'}`, trigger: 'blur' }
+          ]
+        },
+        {
+          label: '用户昵称',
+          prop: 'name',
+          type: 'input',
+          options: {
+            placeholder: '请输入用户昵称'
+          },
+          rules: [
+            { required: true, message: '请输入用户昵称', trigger: 'blur' }
+          ]
+        },
+        {
+          label: '用户密码',
+          prop: 'password',
+          type: 'input',
+          options: {
+            showPassword: true,
+            placeholder: '请输入用户密码'
+          },
+          hidden: !hidden || this.itemData.id,
+          rules: [
+            { required: true, message: '请输入用户密码', trigger: 'blur' }
+          ]
+        },
+        {
+          label: '用户邮箱',
+          prop: 'email',
+          type: 'input',
+          options: {
+            placeholder: '请输入用户邮箱'
+          }
+        }
+      ]
+    }
+  },
   async created () {
     await this.initDice()
     await this.init()
   },
   methods: {
-    formatToTime (str) {
-      return currentTime(str)
+    dateFormat (str) {
+      const date = new Date(+str * 1000)
+      return dateFormat('YYYY-mm-dd HH:MM:SS', date)
     },
     async init () {
       try {
         this.loading = true
-        const { data } = await getUserList({ ...this.pageInfo, ...this.query })
-        this.items = data.records.map(e => {
-          // e.roleName = e.role.map(_m => _m.roleName).toString()
-          return e
-        })
+        const { data } = await getUserList({ ...this.pageInfo, ...this.searchValues })
+        this.items = data.records
         this.total = data.total
       } catch (error) {
-        this.$snackbar.error(error)
+        this.$message.error(error)
       } finally {
         this.loading = false
       }
     },
-    handleSearch (val) {
-      // console.log(val)
+    search (val) {
       this.pageInfo.current = 1
-      Object.assign(this.query, val)
       this.init()
     },
     async initDice () {
@@ -111,71 +278,131 @@ export default {
         const { data } = await getRoleList({ size: 999 })
         this.roleList = data.records
       } catch (error) {
-        this.$snackbar.error(error)
+        this.$message.error(error)
       }
     },
-    handleEdit (item) {
-      this.title = '编辑用户'
-      this.itemData = item
-      this.isEdit = true
-      this.show = true
-    },
-    handleAdd () {
-      this.title = '新增用户'
-      this.isEdit = false
-      this.show = true
+    async onSave (query) {
+      try {
+        await saveUser({
+          id: this.itemData?.id,
+          tenantCode: this.itemData.tenantCode,
+          ...query
+        })
+        this.$message.success('保存成功')
+        this.init()
+        return true
+      } catch (error) {
+        this.$message.error(error)
+      }
     },
-    async handleSave () {
-      if (!this.$refs.user.validate()) return
-      const param = this.$refs.user.getValue()
-      if (this.isEdit) param.id = this.itemData.id
-      param.roleId = param.roleId.toString()
+    async onStatus (item) {
+      const state = item.state ? 1 : 0
       try {
-        await saveUser(param)
-        this.$snackbar.success('保存成功')
-        this.show = false
+        await blockUser({
+          userId: item.id,
+          state: 1 ^ state
+        })
+        this.$message.success('保存成功')
         this.init()
       } catch (error) {
-        this.$snackbar.error(error)
+        this.$message.error(error)
       }
     },
-    handleDelete (userId) {
-      if (Array.isArray(userId)) return
-      this.$confirm('提示', '是否确定删除该选项')
+    onEdit (item) {
+      this.itemData = item
+      this.userFormValue = {
+        type: item.password ? 2 : 1,
+        username: item.username,
+        name: item.name,
+        email: item.email
+      }
+      this.$refs.dialog.open()
+    },
+    onAdd () {
+      this.itemData = {}
+      this.userFormValue = {
+        type: 1
+      }
+      this.$refs.dialog.open()
+    },
+    onSetRole (item) {
+      this.itemData = item
+      console.log(item.roleId.length)
+      this.roleFormValue = {
+        roleId: item.roleId ? item.roleId.split(',').map(e => +e) : []
+      }
+      this.$refs.role.open()
+    },
+    onReset (item) {
+      this.itemData = item
+      this.passwordFormValue.newPwd = null
+      this.$refs.password.open()
+    },
+    onDelete (item) {
+      this.$confirm('是否确定删除该选项', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
         .then(async () => {
           try {
-            await deleteUser({ userId })
-            this.$snackbar.success('删除成功')
+            await deleteUser({ userId: item.id })
+            this.$message.success('删除成功')
             this.init()
           } catch (error) {
-            this.$snackbar.error(error)
+            this.$message.error(error)
           }
         })
+        .catch(_ => {})
     },
-    handleReset (item) {
-      this.itemData = item
-      this.showReset = true
-    },
-    async handleSaveReset () {
-      if (!this.$refs.password.validate()) return
-      const obj = this.$refs.password.getValue()
-      const companyCode = this.itemData.companyInfo.companyCode
-      const userId = this.itemData.id
-      const param = {
-        type: 1,
-        userId,
-        companyCode
-      }
-      Object.assign(param, obj)
-      try {
-        await resetPassword(param)
-        this.showReset = false
-        this.$snackbar.success('修改成功')
-      } catch (error) {
-        this.$snackbar.error(error)
-      }
+    async handleSave () {
+      this.$refs.userForm.validate(async valid => {
+        if (!valid) {
+          return
+        }
+        const { type, ...obj } = this.userFormValue
+        const check = await this.onSave({
+          ...obj,
+          state: 0,
+          roleId: ''
+        })
+        check && this.$refs.dialog.close()
+      })
+    },
+    async handleSaveRole () {
+      this.$refs.roleForm.validate(async valid => {
+        if (!valid) {
+          return
+        }
+        const check = await this.onSave({
+          roleId: this.roleFormValue.roleId.toString()
+        })
+        check && this.$refs.role.close()
+      })
+    },
+    async handleSavePassword () {
+      this.$refs.passwordForm.validate(async valid => {
+        if (!valid) {
+          return
+        }
+        const companyCode = this.itemData.companyInfo?.companyCode
+        const userId = this.itemData.id
+        const param = {
+          type: 1,
+          userId,
+          companyCode,
+          ...this.passwordFormValue
+        }
+        try {
+          await resetPassword(param)
+          this.$refs.password.close()
+          this.$message.success('修改成功')
+        } catch (error) {
+          this.$message.error(error)
+        }
+      })
     },
-    pageHandleChange (page) {
+    handlePageChange (page) {
       this.pageInfo.current = page
       this.init()
     },
@@ -185,7 +412,7 @@ export default {
         const { data } = await downloadUserTemplate()
         util.downloadFileByByte(data, '批量上传文件模板.xls')
       } catch (error) {
-        this.$snackbar.error(error)
+        this.$message.error(error)
       }
     },
     // 批量上传用户
@@ -195,10 +422,10 @@ export default {
       this.uploadLoading = true
       try {
         await userExcelExport(formData)
-        this.$snackbar.success('上传成功')
+        this.$message.success('上传成功')
         this.init()
       } catch (error) {
-        this.$snackbar.error(error)
+        this.$message.error(error)
       } finally {
         this.uploadLoading = false
       }