소스 검색

feature(项目):
①增加附件管理页签,报价单可多次上传,不覆盖之前的报价单信息。
②项目创建、升级等操作,增加逻辑,点击提交后按钮灰掉。系统优化

ZZH-wl 2 년 전
부모
커밋
ee54699756

+ 15 - 6
src/App.vue

@@ -5,6 +5,7 @@
 </template>
 
 <script>
+  import store from '@/store'
   import { mapGetters } from 'vuex'
   import messageApi from '@/api/system/message'
 
@@ -18,18 +19,18 @@
         timerId: '',
       }
     },
-    created() {
-      this.connectWebsocket()
-    },
-    destroyed() {
-      clearInterval(this.interValId)
-    },
     computed: {
       ...mapGetters({
         userid: 'user/id',
         username: 'user/username',
       }),
     },
+    created() {
+      this.connectWebsocket()
+    },
+    destroyed() {
+      clearInterval(this.interValId)
+    },
     methods: {
       connectWebsocket() {
         if (this.userid) {
@@ -59,6 +60,14 @@
       },
       // 心跳且重新
       heartAndReconnect() {
+        let hasToken = store.getters['user/token']
+        console.log('hasToken', hasToken)
+        if (!hasToken) {
+          clearInterval(this.interValId)
+          this.connectWebsocket()
+          return
+        }
+
         if (this.ws) {
           let data = {
             type: 'heartbeat',

+ 14 - 0
src/api/proj/business.js

@@ -53,4 +53,18 @@ export default {
   getBusinessDynamicsList(query) {
     return micro_request.postRequest(basePath, 'Business', 'GetBusinessDynamicsList', query)
   },
+  // 获取项目附件记录
+  getBusinessEnclosureList(query) {
+    return micro_request.postRequest(basePath, 'BusinessFile', 'GetList', query)
+  },
+  createBusinessEnclosure(query) {
+    return micro_request.postRequest(basePath, 'BusinessFile', 'Create', query)
+  },
+  deleteBusinessEnclosure(query) {
+    return micro_request.postRequest(basePath, 'BusinessFile', 'DeleteByIds', query)
+  },
+  // 获取项目附件记录
+  downDingTalkFile(query) {
+    return micro_request.postRequest(basePath, 'BusinessFile', 'DownloadDingTalkFile', query)
+  },
 }

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

@@ -148,7 +148,7 @@ const actions = {
       // 如不使用avatar头像,可删除以下代码
       if (avatar) commit('setAvatar', avatar)
       // 如不使用roles权限控制,可删除以下代码
-      if (roleIds) dispatch('acl/setRole', roleIds, { root: true })
+      if (roleKeys) dispatch('acl/setRole', roleKeys, { root: true })
       if (roleKeys) commit('setRoleKeys', roleKeys)
       // 如不使用permissions权限控制,可删除以下代码
       if (permissions) dispatch('acl/setPermission', permissions, { root: true })

+ 1 - 0
src/utils/routes.js

@@ -11,6 +11,7 @@ import qs from 'qs'
  */
 export function convertRouter(asyncRoutes) {
   return asyncRoutes.map((route) => {
+    route.meta.badge = false
     if (route.component) {
       if (route.component === 'Layout') {
         route.component = (resolve) => require(['@/vab/layouts'], resolve)

+ 19 - 6
src/vab/plugins/permissions.js

@@ -1,18 +1,14 @@
 /**
  * @description 路由守卫,目前两种模式:all模式与intelligence模式
  */
+import messageApi from '@/api/system/message'
 import router from '@/router'
 import store from '@/store'
 import VabProgress from 'nprogress'
 import 'nprogress/nprogress.css'
 import getPageTitle from '@/utils/pageTitle'
 import { toLoginRoute } from '@/utils/routes'
-import {
-  authentication,
-  loginInterception,
-  routesWhiteList,
-  supportVisit,
-} from '@/config'
+import { authentication, loginInterception, routesWhiteList, supportVisit } from '@/config'
 
 VabProgress.configure({
   easing: 'ease',
@@ -61,4 +57,21 @@ router.beforeEach(async (to, from, next) => {
 router.afterEach((to) => {
   document.title = getPageTitle(to.meta.title)
   if (VabProgress.status) VabProgress.done()
+  let hasToken = store.getters['user/token']
+  if (!hasToken) return
+  messageApi
+    .getUserHistory({ isRead: '10', pageSize: 1 })
+    .then(function (res) {
+      if (res && res.code === 200 && res.data) {
+        let isReadTotal = res.data.total
+        if (isReadTotal) {
+          store.dispatch('routes/changeMenuMeta', { name: 'NoticeHistory', meta: { badge: isReadTotal } })
+        } else {
+          store.dispatch('routes/changeMenuMeta', { name: 'NoticeHistory', meta: { badge: false } })
+        }
+      }
+    })
+    .catch((err) => {
+      console.log(err)
+    })
 })

+ 26 - 13
src/views/proj/business/components/BusinessAdd.vue

@@ -185,7 +185,7 @@
     <div slot="footer" class="dialog-footer">
       <el-button v-if="activeSteps !== 1" type="primary" @click="activeSteps--">上一步</el-button>
       <el-button v-if="activeSteps !== 2" type="primary" @click="nextStep">下一步</el-button>
-      <el-button v-if="activeSteps === 2" type="primary" @click="save">提 交</el-button>
+      <el-button v-if="activeSteps === 2" :loading="loading" type="primary" @click="save">提 交</el-button>
     </div>
     <!-- 选择客户弹窗 -->
     <select-customer ref="selectCustomer" @save="selectCustomer" />
@@ -210,9 +210,10 @@
 </template>
 
 <script>
+  import to from 'await-to-js'
+  import { mapGetters } from 'vuex'
   import businessApi from '@/api/proj/business'
   import AmountInput from '@/components/currency'
-  import { mapGetters } from 'vuex'
   import ProductTable from './ProductTable'
   import SelectContact from '@/components/select/SelectCustomerContact'
   import SelectCustomer from '@/components/select/SelectCustomer'
@@ -247,6 +248,7 @@
       }
       return {
         activeSteps: 1,
+        loading: false,
         form: {
           nboName: '',
           custId: undefined,
@@ -307,6 +309,13 @@
         productData: [],
       }
     },
+    computed: {
+      ...mapGetters({
+        userId: 'user/id',
+        nickName: 'user/nickName',
+        roleKeys: 'user/roleKeys',
+      }),
+    },
     watch: {
       custInfo: function (val) {
         this.form.custId = val.custId
@@ -315,13 +324,6 @@
         this.customerInfo = val
       },
     },
-    computed: {
-      ...mapGetters({
-        userId: 'user/id',
-        nickName: 'user/nickName',
-        roleKeys: 'user/roleKeys',
-      }),
-    },
     created() {},
     mounted() {
       this.getOptions()
@@ -487,18 +489,29 @@
         this.$refs['form'].resetFields()
         this.form = this.$options.data().form
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
         this.form.products = this.productData
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            let res
+            this.loading = true
             if (this.form.id) {
-              res = await businessApi.doEdit(this.form)
+              const [err, res] = await to(businessApi.doEdit(this.form))
+              if (err) {
+                this.$baseMessage(res.msg, 'error')
+              } else {
+                this.$baseMessage(res.msg, 'success')
+              }
             } else {
-              res = await businessApi.doAdd(this.form)
+              const [err, res] = await to(businessApi.doAdd(this.form))
+              if (err) {
+                this.$baseMessage(res.msg, 'error')
+              } else {
+                this.$baseMessage(res.msg, 'success')
+              }
             }
-            this.$baseMessage(res.msg, 'success')
+            this.loading = false
             this.$emit('fetch-data')
             this.close()
           } else {

+ 25 - 12
src/views/proj/business/components/BusinessEdit.vue

@@ -286,7 +286,7 @@
     <div slot="footer" class="dialog-footer">
       <el-button v-if="activeSteps !== 1" type="primary" @click="activeSteps--">上一步</el-button>
       <el-button v-if="activeSteps !== 2" type="primary" @click="nextStep">下一步</el-button>
-      <el-button v-if="activeSteps === 2" type="primary" @click="save">提 交</el-button>
+      <el-button v-if="activeSteps === 2" :loading="loading" type="primary" @click="save">提 交</el-button>
     </div>
     <!-- 选择客户弹窗 -->
     <select-customer ref="selectCustomer" @save="selectCustomer" />
@@ -313,6 +313,7 @@
 <script>
   import businessApi from '@/api/proj/business'
   import AmountInput from '@/components/currency'
+  import to from 'await-to-js'
   import { mapGetters } from 'vuex'
   import ProductTable from './ProductTable'
   import SelectContact from '@/components/select/SelectCustomerContact'
@@ -347,6 +348,7 @@
         else callback()
       }
       return {
+        loading: false,
         activeSteps: 1,
         form: {
           nboName: '',
@@ -426,6 +428,13 @@
         productData: [],
       }
     },
+    computed: {
+      ...mapGetters({
+        userId: 'user/id',
+        nickName: 'user/nickName',
+        roleKeys: 'user/roleKeys',
+      }),
+    },
     watch: {
       custInfo: function (val) {
         this.form.custId = val.custId
@@ -434,13 +443,6 @@
         this.customerInfo = val
       },
     },
-    computed: {
-      ...mapGetters({
-        userId: 'user/id',
-        nickName: 'user/nickName',
-        roleKeys: 'user/roleKeys',
-      }),
-    },
     created() {},
     mounted() {
       this.getOptions()
@@ -606,18 +608,29 @@
         this.$refs['form'].resetFields()
         this.form = this.$options.data().form
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
         this.form.products = this.productData
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            let res
+            this.loading = true
             if (this.form.id) {
-              res = await businessApi.doEdit(this.form)
+              const [err, res] = await to(businessApi.doEdit(this.form))
+              if (err) {
+                this.$baseMessage(res.msg, 'error')
+              } else {
+                this.$baseMessage(res.msg, 'success')
+              }
             } else {
-              res = await businessApi.doAdd(this.form)
+              const [err, res] = await to(businessApi.doAdd(this.form))
+              if (err) {
+                this.$baseMessage(res.msg, 'error')
+              } else {
+                this.$baseMessage(res.msg, 'success')
+              }
             }
-            this.$baseMessage(res.msg, 'success')
+            this.loading = false
             this.$emit('fetch-data')
             this.close()
           } else {

+ 25 - 10
src/views/proj/business/components/BusinessGradation.vue

@@ -219,7 +219,7 @@
       </el-form>
       <template #footer>
         <el-button @click="close">取 消</el-button>
-        <el-button type="primary" @click="save">确 定</el-button>
+        <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
       </template>
     </el-dialog>
 
@@ -235,6 +235,7 @@
 </template>
 
 <script>
+  import to from 'await-to-js'
   import businessApi from '@/api/proj/business'
   import AmountInput from '@/components/currency/index.vue'
   import SelectContact from '@/components/select/SelectCustomerContact'
@@ -287,6 +288,7 @@
       return {
         title: '项目',
         type: '',
+        loading: false,
         form: {
           id: undefined,
           distributorId: undefined,
@@ -430,32 +432,45 @@
         this.form.id = row.id
         this.form = Object.assign(this.form, row)
         this.dialogFormVisible = true
+        this.loading = false
       },
       close() {
         this.$refs['form'].resetFields()
         this.form = this.$options.data().form
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
-        console.log(this.form.dashooParamFile, '-----dashooParamFile-------')
-        console.log(this.form.quotationFile, '------quotationFile------')
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
             this.$baseConfirm('你确定要对当前项目' + this.title + '吗', null, async () => {
+              this.loading = true
               if (this.type === 'up') {
                 if (this.form.nboType === '10' || this.form.nboType === '20') {
-                  const { msg } = await businessApi.BusinessUpgradeAorB(this.form)
-                  this.$baseMessage(msg, 'success')
+                  const [err, res] = await to(businessApi.BusinessUpgradeAorB(this.form))
+                  if (err) {
+                    this.$baseMessage(res.msg, 'error')
+                  } else {
+                    this.$baseMessage(res.msg, 'success')
+                  }
                 } else {
-                  const { msg } = await businessApi.businessUpgrade(this.form)
-                  this.$baseMessage(msg, 'success')
+                  const [err, res] = await to(businessApi.businessUpgrade(this.form))
+                  if (err) {
+                    this.$baseMessage(res.msg, 'error')
+                  } else {
+                    this.$baseMessage(res.msg, 'success')
+                  }
                 }
               }
               if (this.type === 'down') {
-                const { msg } = await businessApi.businessDowngrade(this.form)
-                this.$baseMessage(msg, 'success')
+                const [err, res] = await to(businessApi.businessDowngrade(this.form))
+                if (err) {
+                  this.$baseMessage(res.msg, 'error')
+                } else {
+                  this.$baseMessage(res.msg, 'success')
+                }
               }
-
+              this.loading = false
               this.$emit('fetch-data')
               this.close()
             })

+ 314 - 0
src/views/proj/business/components/DetailsEnclosure.vue

@@ -0,0 +1,314 @@
+<!--
+ * @Author: liuzhenlin 461480418@qq.ocm
+ * @Date: 2023-01-10 15:03:27
+ * @LastEditors: liuzhenlin
+ * @LastEditTime: 2023-05-18 15:09:27
+ * @Description: file content
+ * @FilePath: \订单全流程管理系统\src\views\contract\components\DetailsEnclosure.vue
+-->
+<template>
+  <!-- 附件 -->
+  <div class="enclosure-container">
+    <el-row class="mb10">
+      <el-col class="text-right" :span="24">
+        <el-upload
+          ref="uploadRef"
+          action="#"
+          :before-upload="
+            (file) => {
+              return beforeAvatarUpload(file)
+            }
+          "
+          :file-list="fileList"
+          :http-request="uploadrequest">
+          <el-button size="mini" type="primary">点击上传</el-button>
+          <el-button v-permissions="['business:detail:enclosure:add']" size="mini" type="primary">点击上传</el-button>
+        </el-upload>
+      </el-col>
+    </el-row>
+    <el-table border :data="enclosureData" height="calc(100% - 80px)">
+      <el-table-column
+        v-for="(item, index) in columns"
+        :key="index"
+        align="center"
+        :label="item.label"
+        :min-width="item.width"
+        :prop="item.prop"
+        show-overflow-tooltip>
+        <template #default="{ row }">
+          <span v-if="item.prop == 'createdTime'">
+            {{ parseTime(row.createdTime, '{y}-{m}-{d} {h}:{i}') }}
+          </span>
+          <span v-else>{{ row[item.prop] }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" fixed="right" label="操作">
+        <template slot-scope="scope">
+          <el-button v-permissions="['business:detail:enclosure:download']" type="text" @click="downFile(scope.row)">
+            下载
+          </el-button>
+          <el-button v-permissions="['business:detail:enclosure:delete']" type="text" @click="handleDel(scope.row)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-dialog title="编辑" :visible.sync="editVisible" width="300px">
+      <el-form ref="editForm" :model="editFiles" :rules="editRules">
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="新名称" prop="contractCode">
+              <el-input v-model="editFiles.fileName" placeholder="请输入新名称" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <span slot="footer">
+        <el-button type="primary" @click="saveEditName">保存</el-button>
+        <el-button @click="editVisible = false">取消</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import axios from 'axios'
+  import to from 'await-to-js'
+  import enclosureApi from '@/api/proj/business'
+  import asyncUploadFile from '@/utils/uploadajax'
+  import downloadFileByByte from '@/utils/base64ToFile'
+
+  export default {
+    name: 'DetailsEnclosure',
+    // props: {
+    //   // 项目Id
+    //   busId: {
+    //     type: Number,
+    //     default: 0,
+    //   },
+    // },
+    data() {
+      return {
+        busId: undefined,
+
+        fileList: [],
+        fileSettings: {
+          // 文件配置信息
+          fileSize: 52428800,
+          fileTypes: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          pictureSize: 52428800,
+          pictureTypes: '.jpg,.jpeg,.gif,.png,.jfif,.txt',
+          types: '.doc,.docx,.zip,.xls,.xlsx,.rar,.jpg,.jpeg,.gif,.png,.jfif,.mp4,.txt',
+          videoSize: 104857600,
+          videoType: '.mp4',
+        },
+        editVisible: false, //编辑
+        // 编辑文件
+        editFiles: {
+          fileName: '',
+          id: 0,
+        },
+        editRules: {
+          fileName: [{ required: true, trigger: 'blur', message: '请输入新名称' }],
+        },
+        enclosureData: [],
+        columns: [
+          {
+            label: '附件名称',
+            width: 'auto',
+            prop: 'fileName',
+          },
+          {
+            label: '上传人',
+            width: '120px',
+            prop: 'createdName',
+          },
+          {
+            label: '上传时间',
+            width: '100px',
+            prop: 'createdTime',
+          },
+        ],
+      }
+    },
+
+    mounted() {
+      // this.getEnclosureList()
+    },
+
+    methods: {
+      open(busId) {
+        console.log('busId', busId)
+        this.busId = busId
+        this.getEnclosureList()
+      },
+      async getEnclosureList() {
+        let params = { busId: this.busId }
+        const [err, res] = await to(enclosureApi.getBusinessEnclosureList(params))
+        if (err) return
+        this.enclosureData = res.data
+      },
+      // 下载
+      /**
+       * 下载文件以及自定义文件名称
+       */
+      downFile(row) {
+        if (row.fileUrl.startsWith('dingtalk')) {
+          this.downDingtalkFile(row)
+          return
+        }
+
+        let url = row.fileUrl
+        let fileName = row.fileName
+        const xhr = new XMLHttpRequest()
+        xhr.open('GET', url, true)
+        xhr.responseType = 'blob' // 通过文件下载url拿到对应的blob对象
+        xhr.onload = () => {
+          if (xhr.status === 200) {
+            let link = document.createElement('a')
+            let body = document.querySelector('body')
+            link.href = window.URL.createObjectURL(xhr.response)
+            link.download = fileName
+            link.click()
+            body.removeChild(link)
+            window.URL.revokeObjectURL(link.href)
+          }
+        }
+
+        xhr.send()
+      },
+      downDingtalkFile(row) {
+        enclosureApi
+          .downDingTalkFile({ id: row.id })
+          .then((res) => {
+            if (res.code == 200) {
+              downloadFileByByte(res.data, row.fileName)
+            }
+          })
+          .catch((err) => {
+            console.error(err)
+          })
+      },
+      // 重命名
+      rename(id) {
+        this.editFiles.id = id
+        this.editVisible = true
+      },
+      // 编辑新名称
+      async saveEditName() {
+        let params = { ...this.editFiles }
+        const [valid] = await to(this.$refs.editForm.validate())
+        if (valid == false) return
+        const [err, res] = await to(enclosureApi.updateEnclosure(params))
+        if (err) return
+        if (res.code == 200) this.getEnclosureList()
+        else return
+        this.editVisible = false
+      },
+
+      // 上传图片
+      beforeAvatarUpload(file) {
+        let flag1 = file.size < this.fileSettings.fileSize
+        if (!flag1) {
+          this.$message.warning('文件过大,请重新选择!')
+          return false
+        }
+        let flag2 = this.fileSettings.fileTypes.split(',').includes('.' + file.name.split('.').pop())
+        if (!flag2) {
+          this.$message.warning('文件类型不符合,请重新选择!')
+          return false
+        }
+        return true
+      },
+      // 上传
+      uploadrequest(option) {
+        let _this = this
+        let url = process.env.VUE_APP_UPLOAD_WEED
+        axios
+          .post(url)
+          .then(function (res) {
+            if (res.data && res.data.fid && res.data.fid !== '') {
+              option.action = `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}`
+              let file_name = option.file.name
+              let index = file_name.lastIndexOf('.')
+              let file_extend = ''
+              if (index > 0) {
+                // 截取名称中的扩展名
+                file_extend = file_name.substr(index + 1)
+              }
+              let uploadform = {
+                fileName: file_name, // 资料名称
+                fileSize: option.file.size.toString(), // 资料大小
+                fileType: file_extend, // 资料文件类型
+                fileUrl: `${process.env.VUE_APP_PROTOCOL}${res.data.publicUrl}/${res.data.fid}`, // 资料存储url
+                fileSource: '附件上传',
+              }
+              asyncUploadFile(option).then(() => {
+                let params = Object.assign({ ...uploadform, busId: _this.busId })
+                _this.addEnclosure(params)
+                // 一秒后删除上传信息
+                setTimeout(() => {
+                  _this.$refs.uploadRef.clearFiles()
+                }, 1000)
+              })
+            } else {
+              _this.$message({
+                type: 'warning',
+                message: '未上传成功!请刷新界面重新上传!',
+              })
+            }
+          })
+          .catch(function () {
+            _this.$message({
+              type: 'warning',
+              message: '未上传成功!请重新上传!',
+            })
+          })
+      },
+      // 新增附件接口
+      async addEnclosure(params) {
+        const [err, res] = await to(enclosureApi.createBusinessEnclosure(params))
+        if (err) return
+        if (res.code == 200) await this.getEnclosureList()
+      },
+      // 删除图片
+      handleDel(row) {
+        this.$confirm('确认删除?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(async () => {
+            const [err, res] = await to(enclosureApi.deleteBusinessEnclosure({ ids: [row.id] }))
+            if (err) return
+            if (res.code == 200) {
+              this.$message({
+                type: 'success',
+                message: '删除成功!',
+              })
+              this.getEnclosureList()
+            }
+          })
+          .catch(() => {})
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  .enclosure-container {
+    height: 100%;
+    .mb10 {
+      margin-bottom: 10px;
+    }
+    .collection {
+      height: 100%;
+      .el-row {
+        height: 30px;
+      }
+    }
+    .text-right {
+      text-align: right;
+    }
+  }
+</style>

+ 13 - 4
src/views/proj/business/components/FollowAdd.vue

@@ -91,7 +91,7 @@
     </el-form>
     <div slot="footer" class="dialog-footer">
       <el-button @click="close">取 消</el-button>
-      <el-button type="primary" @click="save">确 定</el-button>
+      <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
     </div>
 
     <!-- 选择客户联系人弹窗 -->
@@ -104,10 +104,11 @@
 </template>
 
 <script>
+  import axios from 'axios'
+  import to from 'await-to-js'
   import followApi from '@/api/customer/follow'
   import SelectContact from '@/components/select/SelectCustomerContact'
   import asyncUploadFile from '@/utils/uploadajax'
-  import axios from 'axios'
 
   export default {
     name: 'FollowAdd',
@@ -116,6 +117,7 @@
     },
     data() {
       return {
+        loading: false,
         form: {
           followType: '',
           followDate: '',
@@ -210,12 +212,19 @@
         this.form = this.$options.data().form
         this.fileList = []
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            const { msg } = await followApi.addFollowUp(this.form)
-            this.$baseMessage(msg, 'success')
+            this.loading = true
+            const [err, res] = await to(followApi.addFollowUp(this.form))
+            if (err) {
+              this.$baseMessage(res.msg, 'error')
+            } else {
+              this.$baseMessage(res.msg, 'success')
+            }
+            this.loading = false
             this.$emit('fetch-data')
             this.close()
           } else {

+ 15 - 5
src/views/proj/business/components/ToReserve.vue

@@ -13,18 +13,22 @@
     </el-form>
     <template #footer>
       <el-button @click="close">取 消</el-button>
-      <el-button type="primary" @click="save">确 定</el-button>
+      <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
     </template>
   </el-dialog>
 </template>
 
 <script>
+  import to from 'await-to-js'
   import businessApi from '@/api/proj/business'
 
   export default {
     name: 'Transfer',
     data() {
       return {
+        title: '转储备项目',
+        dialogFormVisible: false,
+        loading: false,
         form: {
           id: undefined,
           projConversionReason: undefined,
@@ -32,25 +36,31 @@
         rules: {
           projConversionReason: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
         },
-        title: '转储备项目',
-        dialogFormVisible: false,
       }
     },
     methods: {
       open(id) {
         this.form.id = id
         this.dialogFormVisible = true
+        this.loading = false
       },
       close() {
         this.$refs['form'].resetFields()
         this.form = this.$options.data().form
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            const { msg } = await businessApi.businessConvertToReserve(this.form)
-            this.$baseMessage(msg, 'success')
+            this.loading = true
+            const [err, res] = await to(businessApi.businessConvertToReserve(this.form))
+            if (err) {
+              this.$baseMessage(res.msg, 'error')
+            } else {
+              this.$baseMessage(res.msg, 'success')
+            }
+            this.loading = false
             this.$emit('fetch-data')
             this.close()
           }

+ 15 - 5
src/views/proj/business/components/Transfer.vue

@@ -21,7 +21,7 @@
     </el-form>
     <template #footer>
       <el-button @click="close">取 消</el-button>
-      <el-button type="primary" @click="save">确 定</el-button>
+      <el-button :loading="loading" type="primary" @click="save">确 定</el-button>
     </template>
     <!-- 选择负责人弹窗 -->
     <select-user
@@ -32,6 +32,7 @@
 </template>
 
 <script>
+  import to from 'await-to-js'
   import businessApi from '@/api/proj/business'
   import SelectUser from '@/components/select/SelectUser'
 
@@ -40,6 +41,9 @@
     components: { SelectUser },
     data() {
       return {
+        title: '转移项目',
+        dialogFormVisible: false,
+        loading: false,
         form: {
           id: undefined,
           userId: undefined,
@@ -50,8 +54,6 @@
           userName: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
           remark: [{ required: true, message: '不能为空', trigger: ['blur', 'change'] }],
         },
-        title: '转移项目',
-        dialogFormVisible: false,
       }
     },
     methods: {
@@ -67,17 +69,25 @@
       open(row) {
         this.form.id = row.id
         this.dialogFormVisible = true
+        this.loading = false
       },
       close() {
         this.$refs['form'].resetFields()
         this.form = this.$options.data().form
         this.dialogFormVisible = false
+        this.loading = false
       },
       save() {
         this.$refs['form'].validate(async (valid) => {
           if (valid) {
-            const { msg } = await businessApi.businessTransfer(this.form)
-            this.$baseMessage(msg, 'success')
+            this.loading = true
+            const [err, res] = await to(businessApi.businessTransfer(this.form))
+            if (err) {
+              this.$baseMessage(res.msg, 'error')
+            } else {
+              this.$baseMessage(res.msg, 'success')
+            }
+            this.loading = false
             this.$emit('fetch-data')
             this.close()
           }

+ 14 - 6
src/views/proj/business/detail.vue

@@ -203,6 +203,9 @@
               <el-table-column align="center" label="备注" prop="remark" show-overflow-tooltip />
             </el-table>
           </el-tab-pane>
+          <el-tab-pane label="附件" name="enclosure">
+            <details-enclosure ref="detailsEnclosure" />
+          </el-tab-pane>
         </el-tabs>
       </div>
       <div class="info-side">
@@ -244,6 +247,7 @@
   import DetailsRecords from './components/DetailsRecords'
   import DetailsFollow from './components/DetailsFollow'
   import DetailsWorkOrder from './components/DetailsWorkOrder'
+  import DetailsEnclosure from './components/DetailsEnclosure'
   import WorkOrderEdit from '@/views/work/order/components/Edit'
   import ContractEdit from '@/views/contract/components/Edit'
 
@@ -261,6 +265,7 @@
       DetailsRecords,
       DetailsFollow,
       DetailsWorkOrder,
+      DetailsEnclosure,
       WorkOrderEdit,
       ContractEdit,
     },
@@ -279,6 +284,7 @@
         belongLoading: false,
         belongTotal: 0,
         belongs: [],
+        enclosure: [],
         yesOrNoOptions: [],
         nboTypeOptions: [],
         nboSourceOptions: [],
@@ -336,17 +342,17 @@
         }
       },
       async handleClick(tab) {
-        if (tab.name == 'follow') {
+        if (tab.name === 'follow') {
           await this.$refs.follow.fetchData()
-        } else if (tab.name == 'contact') {
+        } else if (tab.name === 'contact') {
           await this.$refs.contact.fetchData()
-        } else if (tab.name == 'product') {
+        } else if (tab.name === 'product') {
           await this.getProductData(this.id)
-        } else if (tab.name == 'contract') {
+        } else if (tab.name === 'contract') {
           this.$refs.detailsContract.open(this.id)
-        } else if (tab.name == 'workorder') {
+        } else if (tab.name === 'workorder') {
           await this.$refs.detailsWorkOrder.open(this.details.nboCode)
-        } else if (tab.name == 'belong') {
+        } else if (tab.name === 'belong') {
           // 获取项目转移记录
           this.belongLoading = true
           const {
@@ -355,6 +361,8 @@
           this.belongs = list ? list : []
           this.belongTotal = total
           this.belongLoading = false
+        } else if (tab.name === 'enclosure') {
+          await this.$refs.detailsEnclosure.open(this.id)
         } else {
           return
         }

+ 1 - 0
src/views/system/notice/details.vue

@@ -35,6 +35,7 @@
       },
       close() {
         this.dialogFormVisible = false
+        this.$emit('close')
       },
       open(id) {
         messageApi.getEntityById({ id: id }).then((res) => {

+ 23 - 4
src/views/system/notice/history.vue

@@ -15,6 +15,12 @@
               <el-option v-for="dict in msgTypeOptions" :key="dict.key" :label="dict.value" :value="dict.key" />
             </el-select>
           </el-form-item>
+          <el-form-item prop="isRead">
+            <el-select v-model="queryForm.isRead" clearable placeholder="是否已读">
+              <el-option label="是" value="20" />
+              <el-option label="否" value="10" />
+            </el-select>
+          </el-form-item>
           <el-form-item>
             <el-button icon="el-icon-search" type="primary" @click="queryData">搜索</el-button>
             <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
@@ -53,11 +59,12 @@
       @current-change="handleCurrentChange"
       @size-change="handleSizeChange" />
 
-    <notice-details ref="notice-details" />
+    <notice-details ref="notice-details" @close="fetchData" />
   </div>
 </template>
 
 <script>
+  import { mapActions } from 'vuex'
   import messageApi from '@/api/system/message'
   import NoticeDetails from './details.vue'
 
@@ -102,9 +109,9 @@
         queryForm: {
           pageNum: 1,
           pageSize: 10,
-          configName: '',
-          configKey: '',
-          configType: '',
+          msgTitle: '',
+          msgType: '',
+          isRead: '',
         },
         msgTypeOptions: [],
         msgStatusOptions: [],
@@ -118,6 +125,9 @@
       this.fetchData()
     },
     methods: {
+      ...mapActions({
+        changeMenuMeta: 'routes/changeMenuMeta',
+      }),
       getOptions() {
         this.getDicts('sys_msg_type').then((response) => {
           this.msgTypeOptions = response.data.values || []
@@ -185,6 +195,15 @@
         this.list = list
         this.total = total
         this.listLoading = false
+
+        const {
+          data: { total: isReadTotal },
+        } = await messageApi.getUserHistory({ isRead: '10', pageSize: 1 })
+        if (isReadTotal) {
+          this.changeMenuMeta({ name: 'NoticeHistory', meta: { badge: isReadTotal } })
+        } else {
+          this.changeMenuMeta({ name: 'NoticeHistory', meta: { badge: false } })
+        }
       },
     },
   }

+ 11 - 1
src/views/system/user/index.vue

@@ -66,7 +66,7 @@
               {{ $index + 1 }}
             </template>
           </el-table-column>-->
-          <!--          <el-table-column align="center" label="id" prop="id" show-overflow-tooltip />-->
+          <el-table-column v-permissions="['SysAdmin']" align="center" label="id" prop="id" width="60" />
           <el-table-column align="center" label="用户名" prop="userName" show-overflow-tooltip />
           <el-table-column align="center" label="昵称" prop="nickName" show-overflow-tooltip />
           <el-table-column align="center" label="手机号" prop="phone" show-overflow-tooltip />
@@ -85,6 +85,11 @@
               <span>{{ parseTime(scope.row.createdTime) }}</span>
             </template>
           </el-table-column>
+          <el-table-column align="center" label="账号状态" prop="status">
+            <template #default="scope">
+              <span>{{ selectDictLabel(statusOptions, scope.row.status) }}</span>
+            </template>
+          </el-table-column>
           <el-table-column align="center" label="操作" show-overflow-tooltip width="120">
             <template #default="{ row }">
               <el-button v-permissions="['system:user:resetPwd']" type="text" @click="handleResetPwd(row)">
@@ -142,6 +147,8 @@
           children: 'children',
           label: 'deptName',
         },
+        // 状态数据字典
+        statusOptions: [],
       }
     },
     computed: {
@@ -152,6 +159,9 @@
     created() {
       this.fetchData()
       this.getTreeselect()
+      this.getDicts('sys_normal_disable').then((response) => {
+        this.statusOptions = response.data.values || []
+      })
     },
     methods: {
       /** 查询部门下拉树结构 */