Ver código fonte

feat(announcement): 新增登录弹窗公告功能

- 新增 getPopupAnnouncements API 接口获取登录弹窗公告
- 新增 LoginAnnouncementModal 组件,登录时自动弹窗展示公告
- 公告表单增加"登录弹窗"标记字段及提交逻辑
- 公告列表增加"登录弹窗"列展示
- 公告面板重构为分区布局,资料与公告上下分栏显示
- 登录成功后通过 sessionStorage 标记触发弹窗,仅弹窗一次
- 设备信息/维修单中所属车间改为树形选择组件
- 维修申请单增加"是否停机"筛选条件和表格列
- 多处下拉框增加 showSearch 搜索过滤功能
- 维修单部门筛选改为树形选择,限制多选标签显示数量
dazhaxie 1 semana atrás
pai
commit
e3282a1d1d

+ 14 - 0
src/api/upms/announcement.js

@@ -32,6 +32,20 @@ export function getLatestAnnouncements (parameter) {
 }
 
 /**
+ * 登录弹窗公告:已发布且未过期且勾选登录弹窗的公告
+ * @returns {*}
+ */
+export function getPopupAnnouncements () {
+  return axios({
+    url: '/upms/announcements/popup',
+    method: 'get',
+    headers: {
+      'Content-Type': 'application/json;charset=UTF-8'
+    }
+  })
+}
+
+/**
  * 查询单条
  * @param parameter { id }
  * @returns {*}

+ 115 - 0
src/components/LoginAnnouncementModal/index.vue

@@ -0,0 +1,115 @@
+<template>
+  <a-modal
+    :title="current.title"
+    :visible="visible"
+    :width="720"
+    :mask-closable="false"
+    @cancel="handleClose"
+  >
+    <!-- 文字公告 -->
+    <div v-if="current.type === 1" class="anno-content" v-html="current.content"></div>
+    <!-- 文件资料 -->
+    <div v-else class="anno-file">
+      <a-icon type="paper-clip" class="anno-file-icon"/>
+      <span class="anno-file-name">{{ current.fileName || current.title }}</span>
+      <a-button type="link" icon="download" @click="downloadFile(current)">下载</a-button>
+    </div>
+
+    <template slot="footer">
+      <span class="anno-index" v-if="list.length > 1">第 {{ index + 1 }} / {{ list.length }} 条</span>
+      <a-button v-if="index < list.length - 1" type="primary" @click="next">下一条</a-button>
+      <a-button v-else @click="handleClose">我已知晓</a-button>
+    </template>
+  </a-modal>
+</template>
+
+<script>
+import { getPopupAnnouncements } from '@/api/upms/announcement'
+import { LOGIN_POPUP_PENDING } from '@/store/mutation-types'
+
+export default {
+  name: 'LoginAnnouncementModal',
+  data () {
+    return {
+      visible: false,
+      list: [],
+      index: 0
+    }
+  },
+  computed: {
+    current () {
+      return this.list[this.index] || {}
+    }
+  },
+  mounted () {
+    this.tryPopup()
+  },
+  methods: {
+    // 仅登录进入时弹窗一次:消费 sessionStorage 标记,避免切换到"我的通知"等界面重复弹窗
+    tryPopup () {
+      if (sessionStorage.getItem(LOGIN_POPUP_PENDING) !== '1') {
+        return
+      }
+      // 先消费标记,无论是否有公告、请求成功与否,本次登录都只尝试弹窗一次
+      sessionStorage.removeItem(LOGIN_POPUP_PENDING)
+      getPopupAnnouncements().then(res => {
+        const rows = res.data || []
+        if (rows.length > 0) {
+          this.list = rows
+          this.index = 0
+          this.visible = true
+        }
+      }).catch(() => {})
+    },
+    next () {
+      if (this.index < this.list.length - 1) {
+        this.index += 1
+      } else {
+        this.handleClose()
+      }
+    },
+    handleClose () {
+      this.visible = false
+      this.list = []
+      this.index = 0
+    },
+    downloadFile (item) {
+      if (this.BaseTool.String.isBlank(item.fileUrl)) {
+        this.$message.warning('文件不存在')
+        return
+      }
+      window.open(this.BaseTool.Constant.FILE_URL + item.fileUrl, '_blank')
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.anno-content {
+  max-height: 60vh;
+  overflow-y: auto;
+  img {
+    max-width: 100%;
+  }
+}
+.anno-file {
+  display: flex;
+  align-items: center;
+  padding: 8px 4px;
+  .anno-file-icon {
+    margin-right: 6px;
+    color: #1890ff;
+  }
+  .anno-file-name {
+    flex: 1;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+}
+.anno-index {
+  float: left;
+  color: #999;
+  line-height: 32px;
+}
+</style>

+ 6 - 1
src/layouts/BasicLayout.vue

@@ -56,6 +56,9 @@
       <!--      <setting-drawer v-if="!production"></setting-drawer>-->
       <setting-drawer></setting-drawer>
     </a-layout>
+
+    <!-- 登录公告弹窗:仅登录进入系统时弹一次 -->
+    <login-announcement-modal/>
   </a-layout>
 
 </template>
@@ -71,6 +74,7 @@ import SideMenu from '@/components/Menu/SideMenu'
 import GlobalHeader from '@/components/GlobalHeader'
 import GlobalFooter from '@/components/GlobalFooter'
 import SettingDrawer from '@/components/SettingDrawer'
+import LoginAnnouncementModal from '@/components/LoginAnnouncementModal'
 
 export default {
   name: 'BasicLayout',
@@ -80,7 +84,8 @@ export default {
     SideMenu,
     GlobalHeader,
     GlobalFooter,
-    SettingDrawer
+    SettingDrawer,
+    LoginAnnouncementModal
   },
   data () {
     return {

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

@@ -2,7 +2,7 @@ import Vue from 'vue'
 import { login, getInfo, logout, smsLogin } from '@/api/upms/login'
 import { dictData } from '@/api/upms/dict'
 import { configData } from '@/api/upms/config'
-import { ACCESS_TOKEN } from '@/store/mutation-types'
+import { ACCESS_TOKEN, LOGIN_POPUP_PENDING } from '@/store/mutation-types'
 import DictCache from '@/utils/dict'
 import { welcome } from '@/utils/util'
 import { getRepairApplicationFormPage } from '@/api/repair/application-form'
@@ -69,6 +69,8 @@ const user = {
               const accessToken = result.access_token
               Vue.ls.set(ACCESS_TOKEN, accessToken, 7 * 24 * 60 * 60 * 1000)
               commit('SET_TOKEN', accessToken)
+              // 登录成功,标记本次需要触发公告登录弹窗(仅弹窗一次)
+              sessionStorage.setItem(LOGIN_POPUP_PENDING, '1')
             }
             dictData().then(response => {
               const data = response.data
@@ -85,6 +87,8 @@ const user = {
               const accessToken = result.access_token
               Vue.ls.set(ACCESS_TOKEN, accessToken, 7 * 24 * 60 * 60 * 1000)
               commit('SET_TOKEN', accessToken)
+              // 登录成功,标记本次需要触发公告登录弹窗(仅弹窗一次)
+              sessionStorage.setItem(LOGIN_POPUP_PENDING, '1')
             }
             dictData().then(response => {
               const data = response.data
@@ -171,6 +175,7 @@ const user = {
         commit('SET_TOKEN', '')
         commit('SET_USER_ROLES', [])
         Vue.ls.remove(ACCESS_TOKEN)
+        sessionStorage.removeItem(LOGIN_POPUP_PENDING)
         resolve()
         // logout(state.token).then(() => {
         //   resolve()

+ 2 - 0
src/store/mutation-types.js

@@ -1,4 +1,6 @@
 export const ACCESS_TOKEN = 'Access-Token'
+// 登录公告弹窗标记:登录成功后置位,进入主框架弹窗一次后即消费清除(存于 sessionStorage)
+export const LOGIN_POPUP_PENDING = 'LOGIN_POPUP_PENDING'
 export const CURRENT_USER_INFO = 'CURRENT_USER_INFO'
 export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
 export const DEFAULT_THEME = 'DEFAULT_THEME'

+ 44 - 7
src/views/repair/application-form/RepairApplicationForm.vue

@@ -26,12 +26,10 @@
             </a-col>
             <a-col :md="6" :sm="24">
               <a-form-item label="部门">
-                <a-select v-model="queryParam.deptIdList" mode="multiple" placeholder="请选择部门" allowClear showSearch
-                  optionFilterProp="label">
-                  <a-select-option v-for="item in deptList" :key="item.deptId" :label="item.name"
-                    :value="item.deptId">{{ item.name }}
-                  </a-select-option>
-                </a-select>
+                <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+                  :treeData="deptTreeData" :treeNodeFilterProp="'title'" :showSearch="true"
+                  v-model="queryParam.deptIdList" multiple allowClear :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择部门">
+                </a-tree-select>
               </a-form-item>
             </a-col>
             <a-col :md="6" :sm="24">
@@ -58,6 +56,15 @@
               </a-form-item>
             </a-col>
             <a-col :md="6" :sm="24">
+              <a-form-item label="是否停机">
+                <a-select v-model="queryParam.needStop" placeholder="请选择" allowClear>
+                  <a-select-option v-for="(label, value) in needStopMap" :key="value" :label="label"
+                    :value="parseInt(value)">{{ label }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
+            <a-col :md="6" :sm="24">
               <a-form-item label="报修起始日期">
                 <a-date-picker v-model="queryParam.searchStartTime" style="width: 100%"
                   :format="BaseTool.Date.PICKER_NORM_DATE_PATTERN" />
@@ -110,6 +117,10 @@
           <badge :text="BaseTool.Object.getField(statusMap, text)"
             :status="DictCache.COLOR.REPAIR_APPLICATION_FORM_STATUS[text]" />
         </span>
+        <span slot="needStop" slot-scope="text"
+          :style="{ color: Number(text) === 1 ? 'red' : undefined, fontWeight: Number(text) === 1 ? 'bold' : undefined }">
+          {{ BaseTool.Object.getField(needStopMap, text) }}
+        </span>
         <span slot="level" slot-scope="text">
           <badge :text="BaseTool.Object.getField(levelMap, text)"
             :status="DictCache.COLOR.REPAIR_APPLICATION_FORM_LEVEL[text]" />
@@ -134,7 +145,7 @@ import Detail from './modules/Detail'
 import DetailOut from './modules/DetailRepairOut'
 import { getRepairApplicationFormPage, deleteRepairApplicationForms, fetchRepairApplicationForm, exportRepairApplicationForm } from '@/api/repair/application-form'
 import DispatchBaseForm from '@/views/repair/application-form/modules/DispatchBaseForm'
-import { queryDept } from '@/api/upms/dept'
+import { queryDept, getDeptTree } from '@/api/upms/dept'
 
 export default {
   name: 'RepairForm',
@@ -173,6 +184,7 @@ export default {
         content: null,
         categoryList: undefined,
         statusList: undefined,
+        needStop: undefined,
         searchStartTime: null,
         searchEndTime: null
       },
@@ -204,6 +216,14 @@ export default {
           }
         },
         {
+          title: '是否停机',
+          checked: true,
+          align: 'center',
+          width: '100px',
+          dataIndex: 'needStop',
+          scopedSlots: { customRender: 'needStop' }
+        },
+        {
           title: '紧急程度',
           checked: true,
           width: '120px',
@@ -306,6 +326,7 @@ export default {
       faultTypeMap: {},
       // 部门下拉数据
       deptList: [],
+      deptTreeData: [],
       // 加载数据方法 必须为 Promise 对象
       loadData: parameter => {
         parameter = {
@@ -356,6 +377,9 @@ export default {
     queryDept({}).then(res => {
       this.deptList = res.data || []
     })
+    getDeptTree({}).then(res => {
+      this.deptTreeData = res.data || []
+    })
     this.tableOption()
   },
   methods: {
@@ -442,6 +466,7 @@ export default {
       this.queryParam.content = null
       this.queryParam.categoryList = undefined
       this.queryParam.statusList = undefined
+      this.queryParam.needStop = undefined
       this.queryParam.searchStartTime = null
       this.queryParam.searchEndTime = null
       // 部门:先清空让 a-select 同步状态
@@ -475,3 +500,15 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+/* 修复 tree-select 多选显示溢出问题 */
+.ant-select-selection--multiple {
+  max-height: 80px;
+  overflow-y: auto;
+}
+
+.ant-select-selection--multiple .ant-select-selection__choice {
+  max-width: 100%;
+}
+</style>

+ 60 - 8
src/views/repair/application-form/RepairForm.vue

@@ -26,12 +26,10 @@
             </a-col>
             <a-col :md="6" :sm="24">
               <a-form-item label="部门">
-                <a-select v-model="queryParam.deptIdList" mode="multiple" placeholder="请选择部门" allowClear showSearch
-                  optionFilterProp="label">
-                  <a-select-option v-for="item in deptList" :key="item.deptId" :label="item.name"
-                    :value="item.deptId">{{ item.name }}
-                  </a-select-option>
-                </a-select>
+                <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+                  :treeData="deptTreeData" :treeNodeFilterProp="'title'" :showSearch="true"
+                  v-model="queryParam.deptIdList" multiple allowClear :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择部门">
+                </a-tree-select>
               </a-form-item>
             </a-col>
             <a-col :md="6" :sm="24">
@@ -40,8 +38,17 @@
               </a-form-item>
             </a-col>
             <a-col :md="6" :sm="24">
+              <a-form-item label="工单类别">
+                <a-select v-model="queryParam.categoryList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
+                  <a-select-option v-for="(label, value) in planFlagMap" :key="value" :label="label"
+                    :value="parseInt(value)">{{ label }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
+            <a-col :md="6" :sm="24">
               <a-form-item label="状态">
-                <a-select v-model="queryParam.statusList" mode="multiple" placeholder="请选择">
+                <a-select v-model="queryParam.statusList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                   <a-select-option v-for="(label, value) in statusMap" :key="value" :label="label"
                     :value="parseInt(value)">{{ label }}
                   </a-select-option>
@@ -49,6 +56,15 @@
               </a-form-item>
             </a-col>
             <a-col :md="6" :sm="24">
+              <a-form-item label="是否停机">
+                <a-select v-model="queryParam.needStop" placeholder="请选择" allowClear>
+                  <a-select-option v-for="(label, value) in needStopMap" :key="value" :label="label"
+                    :value="parseInt(value)">{{ label }}
+                  </a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
+            <a-col :md="6" :sm="24">
               <a-form-item label="报修起始日期">
                 <a-date-picker v-model="queryParam.searchStartTime" style="width: 100%"
                   :format="BaseTool.Date.PICKER_NORM_DATE_PATTERN" />
@@ -112,6 +128,10 @@
           <badge :text="BaseTool.Object.getField(statusMap, text)"
             :status="DictCache.COLOR.REPAIR_APPLICATION_FORM_STATUS[text]" />
         </span>
+        <span slot="needStop" slot-scope="text"
+          :style="{ color: Number(text) === 1 ? 'red' : undefined, fontWeight: Number(text) === 1 ? 'bold' : undefined }">
+          {{ BaseTool.Object.getField(needStopMap, text) }}
+        </span>
         <span slot="level" slot-scope="text">
           <badge :text="BaseTool.Object.getField(levelMap, text)"
             :status="DictCache.COLOR.REPAIR_APPLICATION_FORM_LEVEL[text]" />
@@ -136,7 +156,7 @@ import Detail from './modules/DetailRepair'
 import DetailOut from './modules/DetailRepairOut'
 import { getRepairApplicationFormPage, deleteRepairApplicationForms, fetchRepairApplicationForm, exportRepairApplicationForm } from '@/api/repair/application-form'
 import DispatchBaseForm from '@/views/repair/application-form/modules/DispatchBaseForm'
-import { queryDept } from '@/api/upms/dept'
+import { queryDept, getDeptTree } from '@/api/upms/dept'
 // import { selectUserDept } from '@/api/upms/user-dept'
 
 export default {
@@ -174,7 +194,9 @@ export default {
         repairUserName: null,
         deptIdList: skipDeptDefault ? [] : [this.$store.getters.userInfo.deptId],
         content: null,
+        categoryList: undefined,
         statusList: undefined,
+        needStop: undefined,
         searchStartTime: null,
         searchEndTime: null
       },
@@ -260,6 +282,14 @@ export default {
           dataIndex: 'repairUserName'
         },
         {
+          title: '是否停机',
+          checked: true,
+          align: 'center',
+          width: '100px',
+          dataIndex: 'needStop',
+          scopedSlots: { customRender: 'needStop' }
+        },
+        {
           title: '紧急程度',
           checked: true,
           align: 'center',
@@ -317,6 +347,7 @@ export default {
       faultTypeMap: {},
       // 部门下拉数据
       deptList: [],
+      deptTreeData: [],
       // 加载数据方法 必须为 Promise 对象
       loadData: parameter => {
         parameter = {
@@ -367,9 +398,16 @@ export default {
     queryDept({}).then(res => {
       this.deptList = res.data || []
     })
+    getDeptTree({}).then(res => {
+      this.deptTreeData = res.data || []
+    })
     this.tableOption()
   },
   methods: {
+    // 下拉框搜索过滤函数
+    filterOption(input, option) {
+      return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+    },
     tableOption() {
       if (!this.optionAlertShow) {
         this.options = {
@@ -458,7 +496,9 @@ export default {
       this.queryParam.actualUser = null
       this.queryParam.repairUserName = null
       this.queryParam.content = null
+      this.queryParam.categoryList = undefined
       this.queryParam.statusList = undefined
+      this.queryParam.needStop = undefined
       this.queryParam.searchStartTime = null
       this.queryParam.searchEndTime = null
       // 部门:先清空让 a-select 同步状态
@@ -492,3 +532,15 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+/* 修复 tree-select 多选显示溢出问题 */
+.ant-select-selection--multiple {
+  max-height: 80px;
+  overflow-y: auto;
+}
+
+.ant-select-selection--multiple .ant-select-selection__choice {
+  max-width: 100%;
+}
+</style>

+ 36 - 12
src/views/sb/info/SbInfo.vue

@@ -40,7 +40,7 @@
                 </a-col>
                 <a-col :md="6" :sm="24">
                   <a-form-item label="设备等级">
-                    <a-select v-model="queryParam.levelList" mode="multiple" placeholder="请选择">
+                    <a-select v-model="queryParam.levelList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                       <a-select-option v-for="(label, value) in levelMap" :key="value" :label="label"
                         :value="parseInt(value)">{{ label }}
                       </a-select-option>
@@ -49,7 +49,7 @@
                 </a-col>
                 <a-col :md="6" :sm="24">
                   <a-form-item label="自定义类型">
-                    <a-select v-model="queryParam.useTypeList" mode="multiple" placeholder="请选择">
+                    <a-select v-model="queryParam.useTypeList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                       <a-select-option v-for="(label, value) in useTypeMap" :key="value" :label="label"
                         :value="parseInt(value)">{{ label }}
                       </a-select-option>
@@ -63,12 +63,10 @@
                 </a-col>
                 <a-col :md="6" :sm="24">
                   <a-form-item label="所属车间">
-                    <a-select v-model="queryParam.positionIdList" mode="multiple" placeholder="请选择">
-                      <a-select-option v-for="({ id, name }) in sbPositionData" :key="id" :label="name" :value="id">{{
-                        name
-                        }}
-                      </a-select-option>
-                    </a-select>
+                    <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+                      :treeData="positionTreeData" :treeNodeFilterProp="'title'" :showSearch="true" v-model="queryParam.positionIdList"
+                      multiple allowClear :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
+                    </a-tree-select>
                   </a-form-item>
                 </a-col>
                 <a-col :md="6" :sm="24">
@@ -80,13 +78,13 @@
                   <a-form-item label="设备类型">
                     <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
                       :treeData="treeData" :treeNodeFilterProp="'title'" :showSearch="true" v-model="queryParam.typeIdList"
-                      multiple allowClear placeholder="请选择">
+                      multiple allowClear :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                     </a-tree-select>
                   </a-form-item>
                 </a-col>
                 <a-col :md="6" :sm="24">
                   <a-form-item label="设备状态">
-                    <a-select v-model="queryParam.statusList" mode="multiple" placeholder="请选择">
+                    <a-select v-model="queryParam.statusList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                       <a-select-option v-for="(label, value) in statusMap" :key="value"
                         :defaultValue="DictCache.VALUE.SB_INFO_STATUS.IN_USE" :label="label" :value="parseInt(value)">{{
                           label }}
@@ -96,7 +94,7 @@
                 </a-col>
                 <a-col :md="6" :sm="24">
                   <a-form-item label="主子设备">
-                    <a-select v-model="queryParam.isChildList" mode="multiple" placeholder="请选择">
+                    <a-select v-model="queryParam.isChildList" mode="multiple" showSearch :filterOption="filterOption" :maxTagCount="3" :maxTagTextLength="10" placeholder="请选择">
                       <a-select-option value="1">子设备</a-select-option>
                       <a-select-option value="2">父设备</a-select-option>
                       <a-select-option value="3">普通设备</a-select-option>
@@ -294,7 +292,7 @@ import ImportFormUpdate from './modules/ImportFormUpdate'
 import PrintSbCode from '@/views/sb/info/modules/PrintSbCode'
 import PrintInSbInfoBatch from '@/views/sb/info/modules/PrintInSbInfoBatch'
 import InitPointMapSelect from '@/views/sb/info/modules/InitPointMapSelect'
-import { querySbPosition, initPoints } from '@/api/sb/position'
+import { querySbPosition, initPoints, getSbPositionTree } from '@/api/sb/position'
 import { queryFile } from '@/api/upms/file'
 
 export default {
@@ -363,6 +361,7 @@ export default {
       selectedKeys: [],
       sourceTypeMap: {},
       treeData: [],
+      positionTreeData: [],
       // 表头
       columns: [
         {
@@ -621,11 +620,18 @@ export default {
     querySbPosition().then(res => {
       this.sbPositionData = res.data
     })
+    getSbPositionTree().then(res => {
+      this.positionTreeData = res.data
+    })
     queryFile({ type: 56 }).then(res => {
       this.mapNameList = res.data
     })
   },
   methods: {
+    // 下拉框搜索过滤函数
+    filterOption(input, option) {
+      return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+    },
     tableOption() {
       this.setTree()
       if (!this.optionAlertShow) {
@@ -902,3 +908,21 @@ export default {
   }
 }
 </script>
+
+<style scoped>
+/* 修复多选下拉框显示溢出问题 */
+.ant-select-selection--multiple {
+  max-height: 80px;
+  overflow-y: auto;
+}
+
+.ant-select-selection--multiple .ant-select-selection__choice {
+  max-width: 100%;
+}
+
+/* 修复 tree-select 多选显示溢出 */
+.ant-select-tree {
+  max-height: 400px;
+  overflow-y: auto;
+}
+</style>

+ 16 - 8
src/views/sb/info/modules/BaseForm.vue

@@ -174,10 +174,10 @@
       <a-row class="form-row" :gutter="BaseTool.Constant.row.gutter">
         <a-col :lg="12" :md="24" :sm="24">
           <a-form-item label="所属车间" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
-            <a-select v-decorator="['positionId', { rules: [{ required: true, message: '所属车间不能为空' }] }]" placeholder="请选择">
-              <a-select-option v-for="({ id, name }) in sbPositionData" :key="id" :label="name" :value="id">{{ name }}
-              </a-select-option>
-            </a-select>
+            <a-tree-select style="width: 100%" :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+              :treeData="positionTreeData" :treeNodeFilterProp="'title'" :showSearch="true"
+              v-decorator="['positionId', { rules: [{ required: true, message: '所属车间不能为空' }] }]" placeholder="请选择">
+            </a-tree-select>
           </a-form-item>
         </a-col>
         <a-col :lg="12" :md="24" :sm="24">
@@ -225,7 +225,7 @@
         </a-col>
         <a-col :lg="12" :md="24" :sm="24">
           <a-form-item label="使用机台" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
-            <a-select v-decorator="['saveUser', { rules: [{ required: false, message: '项目部不能为空' }] }]" placeholder="请选择">
+            <a-select v-decorator="['saveUser', { rules: [{ required: false, message: '项目部不能为空' }] }]" showSearch :filterOption="filterOption" placeholder="请选择">
               <a-select-option v-for="({ userId, realName }) in userList" :key="userId" :label="realName"
                 :value="userId">{{ realName }}
               </a-select-option>
@@ -321,7 +321,7 @@
         </a-col>-->
         <a-col :lg="12" :md="24" :sm="24">
           <a-form-item label="维修负责人" :labelCol="BaseTool.Constant.labelCol" :wrapperCol="BaseTool.Constant.wrapperCol">
-            <a-select v-decorator="['repairUser']" placeholder="请选择">
+            <a-select v-decorator="['repairUser']" showSearch :filterOption="filterOption" placeholder="请选择">
               <a-select-option v-for="({ userId, realName }) in userList" :key="userId" :label="realName"
                 :value="userId">{{ realName }}
               </a-select-option>
@@ -331,7 +331,7 @@
         <a-col :lg="12" :md="24" :sm="24">
           <a-form-item label="第二维修负责人" :labelCol="BaseTool.Constant.labelCol"
             :wrapperCol="BaseTool.Constant.wrapperCol">
-            <a-select v-decorator="['repairUserSecond']" placeholder="请选择">
+            <a-select v-decorator="['repairUserSecond']" showSearch :filterOption="filterOption" placeholder="请选择">
               <a-select-option v-for="({ userId, realName }) in userList" :key="userId" :label="realName"
                 :value="userId">{{ realName }}
               </a-select-option>
@@ -597,7 +597,7 @@
 import pick from 'lodash.pick'
 import { addSbInfo, updateSbInfo, fetchSbInfo, copySbInfo } from '@/api/sb/info'
 import { fetchSbTypeTree } from '@/api/sb/type'
-import { querySbPosition } from '@/api/sb/position'
+import { querySbPosition, getSbPositionTree } from '@/api/sb/position'
 import FirmProducerSelectModal from '@/views/firm/producer/modules/FirmProducerSelectModal'
 import SbGuigeSelectModal from '@/views/sb/guige/modules/SbGuigeSelectModal'
 import SbModelSelectModal from '@/views/sb/model/modules/SbModelSelectModal'
@@ -641,6 +641,7 @@ export default {
       visible: false,
       sbTypeTreeData: [],
       sbPositionData: [],
+      positionTreeData: [],
       // sbInfoTreeData: [],
       // sourceSbInfoTreeData: [],
       // sbPositionTreeData: [],
@@ -705,6 +706,10 @@ export default {
     })
   },
   methods: {
+    // 下拉框搜索过滤函数
+    filterOption(input, option) {
+      return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+    },
     base(record, type) {
       this.setTree(record)
       // 清除文件列表
@@ -957,6 +962,9 @@ export default {
       fetchSbTypeTree().then(res => {
         this.sbTypeTreeData = res.data
       })
+      getSbPositionTree().then(res => {
+        this.positionTreeData = res.data
+      })
       querySbPosition().then(res => {
         this.sbPositionData = res.data
       })

+ 13 - 1
src/views/upms/announcement/Announcement.vue

@@ -55,7 +55,7 @@
       :data="loadData"
       :alert="options.alert"
       :rowSelection="options.rowSelection"
-      :scroll="{ x: 1250 }"
+      :scroll="{ x: 1340 }"
       showPagination="auto"
     >
       <span slot="topFlag" slot-scope="text">
@@ -63,6 +63,11 @@
         <span v-else>—</span>
       </span>
 
+      <span slot="popupFlag" slot-scope="text">
+        <a-tag v-if="text === 1" color="orange">弹窗</a-tag>
+        <span v-else>—</span>
+      </span>
+
       <span slot="showSwitch" slot-scope="record">
         <template v-if="record.status === 0">
           <span style="color:#999;">未发布</span>
@@ -153,6 +158,13 @@ export default {
           scopedSlots: { customRender: 'topFlag' }
         },
         {
+          title: '登录弹窗',
+          dataIndex: 'popupFlag',
+          width: 90,
+          align: 'center',
+          scopedSlots: { customRender: 'popupFlag' }
+        },
+        {
           title: '发布时间',
           dataIndex: 'publishTime',
           width: 160,

+ 12 - 0
src/views/upms/announcement/modules/BaseForm.vue

@@ -49,6 +49,14 @@
         </a-radio-group>
       </a-form-item>
 
+      <a-form-item label="登录弹窗" :labelCol="labelCol" :wrapperCol="wrapperCol">
+        <a-radio-group v-model="popupFlag">
+          <a-radio :value="1">是</a-radio>
+          <a-radio :value="0">否</a-radio>
+        </a-radio-group>
+        <div style="color:#999;font-size:12px;">勾选后,用户登录进入系统时会弹窗提醒一次(仅已发布且未过期时生效)</div>
+      </a-form-item>
+
       <a-form-item label="有效期至" :labelCol="labelCol" :wrapperCol="wrapperCol">
         <a-date-picker
           v-model="expireTime"
@@ -89,6 +97,7 @@ export default {
       content: '',
       resultContent: '',
       topFlag: 0,
+      popupFlag: 0,
       expireTime: null,
       fileList: [],
       fileUrl: '',
@@ -111,6 +120,7 @@ export default {
         this.type = record.type || 1
         this.content = record.content || ''
         this.topFlag = record.topFlag || 0
+        this.popupFlag = record.popupFlag || 0
         this.expireTime = record.expireTime ? moment(record.expireTime) : null
         if (record.fileUrl) {
           this.fileUrl = record.fileUrl
@@ -130,6 +140,7 @@ export default {
       this.content = ''
       this.resultContent = ''
       this.topFlag = 0
+      this.popupFlag = 0
       this.expireTime = null
       this.fileList = []
       this.fileUrl = ''
@@ -198,6 +209,7 @@ export default {
           fileName: values.type === 2 ? this.fileName : '',
           fileSize: values.type === 2 ? this.fileSize : null,
           topFlag: this.topFlag,
+          popupFlag: this.popupFlag,
           expireTime: this.expireTime ? this.expireTime.format('YYYY-MM-DD HH:mm:ss') : null
         }
         if (this.BaseTool.String.isBlank(params.id)) {

+ 72 - 22
src/views/workplace/backlog/modules/AnnouncementPanel.vue

@@ -1,38 +1,46 @@
 <template>
   <a-card :bordered="false" title="公告栏" class="announcement-panel">
-    <a-tabs default-active-key="1">
-      <!-- 文字公告 -->
-      <a-tab-pane key="1" :tab="`公告 (${textList.length})`">
-        <a-spin :spinning="loading">
-          <a-list v-if="textList.length" size="small" :data-source="textList">
+    <a-spin :spinning="loading">
+      <!-- 资料公告(上) -->
+      <div class="anno-section">
+        <div class="anno-section-title">
+          <a-icon type="file-text"/><span>资料下载</span>
+          <span class="anno-count">({{ fileList.length }})</span>
+        </div>
+        <div class="anno-section-body">
+          <a-list v-if="fileList.length" size="small" :data-source="fileList">
             <a-list-item slot="renderItem" slot-scope="item">
-              <div class="anno-item" @click="viewText(item)">
-                <a-tag v-if="item.topFlag === 1" color="red">置顶</a-tag>
-                <span class="anno-title">{{ item.title }}</span>
+              <div class="anno-item" @click="downloadFile(item)">
+                <a-icon type="paper-clip" class="anno-icon"/>
+                <span class="anno-title">{{ item.fileName || item.title }}</span>
                 <span class="anno-time">{{ formatDate(item.publishTime) }}</span>
               </div>
             </a-list-item>
           </a-list>
-          <a-empty v-else description="暂无公告"/>
-        </a-spin>
-      </a-tab-pane>
+          <a-empty v-else :image="simpleEmpty" description="暂无资料"/>
+        </div>
+      </div>
 
-      <!-- 文件资料 -->
-      <a-tab-pane key="2" :tab="`资料下载 (${fileList.length})`">
-        <a-spin :spinning="loading">
-          <a-list v-if="fileList.length" size="small" :data-source="fileList">
+      <!-- 文字公告(下) -->
+      <div class="anno-section">
+        <div class="anno-section-title">
+          <a-icon type="notification"/><span>公告</span>
+          <span class="anno-count">({{ textList.length }})</span>
+        </div>
+        <div class="anno-section-body">
+          <a-list v-if="textList.length" size="small" :data-source="textList">
             <a-list-item slot="renderItem" slot-scope="item">
-              <div class="anno-item" @click="downloadFile(item)">
-                <a-icon type="paper-clip" class="anno-icon"/>
-                <span class="anno-title">{{ item.fileName || item.title }}</span>
+              <div class="anno-item" @click="viewText(item)">
+                <a-tag v-if="item.topFlag === 1" color="red">置顶</a-tag>
+                <span class="anno-title">{{ item.title }}</span>
                 <span class="anno-time">{{ formatDate(item.publishTime) }}</span>
               </div>
             </a-list-item>
           </a-list>
-          <a-empty v-else description="暂无资料"/>
-        </a-spin>
-      </a-tab-pane>
-    </a-tabs>
+          <a-empty v-else :image="simpleEmpty" description="暂无公告"/>
+        </div>
+      </div>
+    </a-spin>
 
     <a-modal
       :title="current.title"
@@ -48,12 +56,14 @@
 
 <script>
 import { getLatestAnnouncements } from '@/api/upms/announcement'
+import { Empty } from 'ant-design-vue'
 
 export default {
   name: 'AnnouncementPanel',
   data () {
     return {
       loading: false,
+      simpleEmpty: Empty.PRESENTED_IMAGE_SIMPLE,
       textList: [],
       fileList: [],
       textVisible: false,
@@ -106,6 +116,46 @@ export default {
     padding: 8px 16px;
   }
 }
+.anno-section {
+  border: 1px solid #e8e8e8;
+  border-radius: 4px;
+  padding: 8px 12px;
+  background: #fff;
+  & + .anno-section {
+    margin-top: 12px;
+  }
+  .anno-section-title {
+    display: flex;
+    align-items: center;
+    font-weight: 600;
+    color: rgba(0, 0, 0, 0.85);
+    padding-bottom: 6px;
+    margin-bottom: 2px;
+    border-bottom: 1px solid #f0f0f0;
+    .anticon {
+      margin-right: 6px;
+      color: #1890ff;
+    }
+    .anno-count {
+      margin-left: 4px;
+      color: #999;
+      font-weight: normal;
+    }
+  }
+  .anno-section-body {
+    max-height: 240px;
+    overflow-y: auto;
+    /deep/ .ant-list-sm .ant-list-item {
+      padding: 7px 0;
+    }
+    /deep/ .ant-list-split .ant-list-item:last-child {
+      border-bottom: none;
+    }
+    /deep/ .ant-empty-small {
+      margin: 8px 0;
+    }
+  }
+}
 .anno-item {
   display: flex;
   align-items: center;