ysc 2 vuotta sitten
vanhempi
commit
0af4723ff0

+ 3 - 0
src/components/DeptUserTree/index.vue

@@ -69,6 +69,9 @@ export default {
     },
     closeUserDialog() {
       this.usersVisible = false
+    },
+    clearText() {
+      this.userNames = undefined
     }
   }
 }

+ 49 - 0
src/components/DictData/index.js

@@ -0,0 +1,49 @@
+import Vue from 'vue'
+import store from '@/store'
+import DataDict from '@/utils/dict'
+import { getDictData as getDicts} from '@/api/system/dict'
+
+function searchDictByKey(dict, key) {
+  if (key == null && key == "") {
+    return null
+  }
+  try {
+    for (let i = 0; i < dict.length; i++) {
+      if (dict[i].key == key) {
+        return dict[i].value
+      }
+    }
+  } catch (e) {
+    return null
+  }
+}
+
+function install() {
+  Vue.use(DataDict, {
+    metas: {
+      '*': {
+        labelField: 'dictLabel',
+        valueField: 'dictValue',
+        request(dictMeta) {
+          const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
+          if (storeDict) {
+            return new Promise(resolve => { resolve(storeDict) })
+          } else {
+            return new Promise((resolve, reject) => {
+              getDicts(dictMeta.type).then(res => {
+                store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
+                resolve(res.data)
+              }).catch(error => {
+                reject(error)
+              })
+            })
+          }
+        },
+      },
+    },
+  })
+}
+
+export default {
+  install,
+}

+ 89 - 0
src/components/DictTag/index.vue

@@ -0,0 +1,89 @@
+<template>
+  <div>
+    <template v-for="(item, index) in options">
+      <template v-if="values.includes(item.value)">
+        <span
+          v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)"
+          :key="item.value"
+          :index="index"
+          :class="item.raw.cssClass"
+          >{{ item.label + ' ' }}</span
+        >
+        <el-tag
+          v-else
+          :disable-transitions="true"
+          :key="item.value"
+          :index="index"
+          :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass"
+          :class="item.raw.cssClass"
+        >
+          {{ item.label + ' ' }}
+        </el-tag>
+      </template>
+    </template>
+    <template v-if="unmatch && showValue">
+      {{ unmatchArray | handleArray }}
+    </template>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DictTag",
+  props: {
+    options: {
+      type: Array,
+      default: null,
+    },
+    value: [Number, String, Array],
+    // 当未找到匹配的数据时,显示value
+    showValue: {
+      type: Boolean,
+      default: true,
+    },
+    separator: {
+      type: String,
+      default: ","
+    }
+  },
+  data() {
+    return {
+      unmatchArray: [], // 记录未匹配的项
+    }
+  },
+  computed: {
+    values() {
+      if (this.value === null || typeof this.value === 'undefined' || this.value === '') return []
+      return Array.isArray(this.value) ? this.value.map(item => '' + item) : String(this.value).split(this.separator)
+    },
+    unmatch() {
+      this.unmatchArray = []
+      // 没有value不显示
+      if (this.value === null || typeof this.value === 'undefined' || this.value === '' || this.options.length === 0) return false
+      // 传入值为数组
+      let unmatch = false // 添加一个标志来判断是否有未匹配项
+      this.values.forEach(item => {
+        if (!this.options.some(v => v.value === item)) {
+          this.unmatchArray.push(item)
+          unmatch = true // 如果有未匹配项,将标志设置为true
+        }
+      })
+      return unmatch // 返回标志的值
+    },
+
+  },
+  filters: {
+    handleArray(array) {
+      if (array.length === 0) return '';
+      return array.reduce((pre, cur) => {
+        return pre + ' ' + cur;
+      })
+    },
+  }
+};
+</script>
+<style scoped>
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+</style>

+ 12 - 1
src/main.js

@@ -17,7 +17,11 @@ import router from './router'
 
 import '@/icons' // icon
 import '@/permission' // permission control
-
+import { getDictData } from "@/api/system/dict";
+// 字典标签组件
+import DictTag from '@/components/DictTag'
+// 字典数据组件
+import DictData from '@/components/DictData'
 import {
   parseTime,
   resetForm,
@@ -28,6 +32,8 @@ import {
   getMonthDate
 } from "@/utils/common";
 
+Vue.prototype.getDicts = getDictData
+
 Vue.prototype.parseTime = parseTime
 Vue.prototype.resetForm = resetForm
 Vue.prototype.addDateRange = addDateRange
@@ -36,12 +42,17 @@ Vue.prototype.selectDictLabels = selectDictLabels
 Vue.prototype.handleTree = handleTree
 Vue.prototype.getMonthDate = getMonthDate
 
+Vue.component('DictTag', DictTag)
+
+
 // set ElementUI lang to EN
 // Vue.use(ElementUI, { locale })
 // 如果想要中文版 element-ui,按如下方式声明
 Vue.use(ElementUI, {locale: zh})
 Vue.use(directive)
 Vue.use(auth)
+DictData.install()
+
 
 Vue.config.productionTip = false
 

+ 3 - 1
src/store/index.js

@@ -5,6 +5,7 @@ import app from './modules/app'
 import settings from './modules/settings'
 import user from './modules/user'
 import permission from './modules/permission'
+import dict from './modules/dict'
 
 Vue.use(Vuex)
 
@@ -13,7 +14,8 @@ const store = new Vuex.Store({
     app,
     settings,
     user,
-    permission
+    permission,
+    dict
   },
   getters
 })

+ 50 - 0
src/store/modules/dict.js

@@ -0,0 +1,50 @@
+const state = {
+  dict: new Array()
+}
+const mutations = {
+  SET_DICT: (state, { key, value }) => {
+    if (key !== null && key !== "") {
+      state.dict.push({
+        key: key,
+        value: value
+      })
+    }
+  },
+  REMOVE_DICT: (state, key) => {
+    try {
+      for (let i = 0; i < state.dict.length; i++) {
+        if (state.dict[i].key == key) {
+          state.dict.splice(i, 1)
+          return true
+        }
+      }
+    } catch (e) {
+    }
+  },
+  CLEAN_DICT: (state) => {
+    state.dict = new Array()
+  }
+}
+
+const actions = {
+  // 设置字典
+  setDict({ commit }, data) {
+    commit('SET_DICT', data)
+  },
+  // 删除字典
+  removeDict({ commit }, key) {
+    commit('REMOVE_DICT', key)
+  },
+  // 清空字典
+  cleanDict({ commit }) {
+    commit('CLEAN_DICT')
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 82 - 0
src/utils/dict/Dict.js

@@ -0,0 +1,82 @@
+import Vue from 'vue'
+import { mergeRecursive } from "@/utils/common";
+import DictMeta from './DictMeta'
+import DictData from './DictData'
+
+const DEFAULT_DICT_OPTIONS = {
+  types: [],
+}
+
+/**
+ * @classdesc 字典
+ * @property {Object} label 标签对象,内部属性名为字典类型名称
+ * @property {Object} dict 字段数组,内部属性名为字典类型名称
+ * @property {Array.<DictMeta>} _dictMetas 字典元数据数组
+ */
+export default class Dict {
+  constructor() {
+    this.owner = null
+    this.label = {}
+    this.type = {}
+  }
+
+  init(options) {
+    if (options instanceof Array) {
+      options = { types: options }
+    }
+    const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
+    if (opts.types === undefined) {
+      throw new Error('need dict types')
+    }
+    const ps = []
+    this._dictMetas = opts.types.map(t => DictMeta.parse(t))
+    this._dictMetas.forEach(dictMeta => {
+      const type = dictMeta.type
+      Vue.set(this.label, type, {})
+      Vue.set(this.type, type, [])
+      if (dictMeta.lazy) {
+        return
+      }
+      ps.push(loadDict(this, dictMeta))
+    })
+    return Promise.all(ps)
+  }
+
+  /**
+   * 重新加载字典
+   * @param {String} type 字典类型
+   */
+  reloadDict(type) {
+    const dictMeta = this._dictMetas.find(e => e.type === type)
+    if (dictMeta === undefined) {
+      return Promise.reject(`the dict meta of ${type} was not found`)
+    }
+    return loadDict(this, dictMeta)
+  }
+}
+
+/**
+ * 加载字典
+ * @param {Dict} dict 字典
+ * @param {DictMeta} dictMeta 字典元数据
+ * @returns {Promise}
+ */
+function loadDict(dict, dictMeta) {
+  return dictMeta.request(dictMeta)
+    .then(response => {
+      const type = dictMeta.type
+      let dicts = dictMeta.responseConverter(response, dictMeta)
+      if (!(dicts instanceof Array)) {
+        console.error('the return of responseConverter must be Array.<DictData>')
+        dicts = []
+      } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
+        console.error('the type of elements in dicts must be DictData')
+        dicts = []
+      }
+      dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
+      dicts.forEach(d => {
+        Vue.set(dict.label[type], d.value, d.label)
+      })
+      return dicts
+    })
+}

+ 17 - 0
src/utils/dict/DictConverter.js

@@ -0,0 +1,17 @@
+import DictOptions from './DictOptions'
+import DictData from './DictData'
+
+export default function(dict, dictMeta) {
+  const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)
+  const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)
+  return new DictData(dict[label], dict[value], dict)
+}
+
+/**
+ * 确定字典字段
+ * @param {DictData} dict
+ * @param  {...String} fields
+ */
+function determineDictField(dict, ...fields) {
+  return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))
+}

+ 13 - 0
src/utils/dict/DictData.js

@@ -0,0 +1,13 @@
+/**
+ * @classdesc 字典数据
+ * @property {String} label 标签
+ * @property {*} value 标签
+ * @property {Object} raw 原始数据
+ */
+export default class DictData {
+  constructor(label, value, raw) {
+    this.label = label
+    this.value = value
+    this.raw = raw
+  }
+}

+ 38 - 0
src/utils/dict/DictMeta.js

@@ -0,0 +1,38 @@
+import { mergeRecursive } from "@/utils/common";
+import DictOptions from './DictOptions'
+
+/**
+ * @classdesc 字典元数据
+ * @property {String} type 类型
+ * @property {Function} request 请求
+ * @property {String} label 标签字段
+ * @property {String} value 值字段
+ */
+export default class DictMeta {
+  constructor(options) {
+    this.type = options.type
+    this.request = options.request
+    this.responseConverter = options.responseConverter
+    this.labelField = options.labelField
+    this.valueField = options.valueField
+    this.lazy = options.lazy === true
+  }
+}
+
+
+/**
+ * 解析字典元数据
+ * @param {Object} options
+ * @returns {DictMeta}
+ */
+DictMeta.parse= function(options) {
+  let opts = null
+  if (typeof options === 'string') {
+    opts = DictOptions.metas[options] || {}
+    opts.type = options
+  } else if (typeof options === 'object') {
+    opts = options
+  }
+  opts = mergeRecursive(DictOptions.metas['*'], opts)
+  return new DictMeta(opts)
+}

+ 51 - 0
src/utils/dict/DictOptions.js

@@ -0,0 +1,51 @@
+import { mergeRecursive } from "@/utils/common";
+import dictConverter from './DictConverter'
+
+export const options = {
+  metas: {
+    '*': {
+      /**
+       * 字典请求,方法签名为function(dictMeta: DictMeta): Promise
+       */
+      request: (dictMeta) => {
+        console.log(`load dict ${dictMeta.type}`)
+        return Promise.resolve([])
+      },
+      /**
+       * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData
+       */
+      responseConverter,
+      labelField: 'label',
+      valueField: 'value',
+    },
+  },
+  /**
+   * 默认标签字段
+   */
+  DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
+  /**
+   * 默认值字段
+   */
+  DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
+}
+
+/**
+ * 映射字典
+ * @param {Object} response 字典数据
+ * @param {DictMeta} dictMeta 字典元数据
+ * @returns {DictData}
+ */
+function responseConverter(response, dictMeta) {
+  const dicts = response.content instanceof Array ? response.content : response
+  if (dicts === undefined) {
+    console.warn(`no dict data of "${dictMeta.type}" found in the response`)
+    return []
+  }
+  return dicts.map(d => dictConverter(d, dictMeta))
+}
+
+export function mergeOptions(src) {
+  mergeRecursive(options, src)
+}
+
+export default options

+ 33 - 0
src/utils/dict/index.js

@@ -0,0 +1,33 @@
+import Dict from './Dict'
+import { mergeOptions } from './DictOptions'
+
+export default function(Vue, options) {
+  mergeOptions(options)
+  Vue.mixin({
+    data() {
+      if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
+        return {}
+      }
+      const dict = new Dict()
+      dict.owner = this
+      return {
+        dict
+      }
+    },
+    created() {
+      if (!(this.dict instanceof Dict)) {
+        return
+      }
+      options.onCreated && options.onCreated(this.dict)
+      this.dict.init(this.$options.dicts).then(() => {
+        options.onReady && options.onReady(this.dict)
+        this.$nextTick(() => {
+          this.$emit('dictReady', this.dict)
+          if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
+            this.$options.methods.onDictReady.call(this, this.dict)
+          }
+        })
+      })
+    },
+  })
+}

+ 6 - 1
src/views/task/components/project.vue

@@ -35,6 +35,8 @@
                 <el-button size="mini" @click="handleUpdate(data)" v-hasPermi="['task:project:edit']">修改</el-button>
                 <el-button size="mini" type="danger" @click="handleDelete(data)"
                            v-hasPermi="['task:project:delete']">删除</el-button>
+                <el-button size="mini" @click="handleDetail(data)" v-if="data.type==='项目'"
+                           v-hasPermi="['task:project:query']">详情</el-button>
                 <i class="el-icon-share" slot="reference"></i>
                </el-popover>
             </span>
@@ -184,7 +186,6 @@ export default {
       };
     },
     treeSelectChange(val) {
-      console.log(val);
       if (val.type === '1') {
         this.form.type = '2'
       } else {
@@ -264,6 +265,10 @@ export default {
       }).catch(() => {
       });
     },
+    /** 详情按钮操作 */
+    handleDetail(row) {
+      this.$router.push({path: '/task/projectView', query: {projectId: row.id}});
+    }
 
   }
 };

+ 8 - 0
src/views/task/projectView.vue

@@ -57,18 +57,26 @@ export default {
     }
   },
   created() {
+    let queryProjectId = this.$route.query.projectId;
+    if (queryProjectId) {
+      this.queryParams.projectIds.push(parseInt(queryProjectId))
+    }
     this.initData()
   },
   methods: {
     initData() {
       listProject({type: '2'}).then(res => {
         this.projectList = res.data
+        this.getList()
       })
     },
     selectProjectChange(val) {
       this.getList()
     },
     getList() {
+      if (this.queryParams.projectIds.length === 0) {
+        return
+      }
       projectTaskList(this.queryParams).then(res => {
           this.tableData = res.data
         }

+ 32 - 27
src/views/task/task.vue

@@ -6,8 +6,9 @@
       </el-col>
       <el-col :span="19" :xs="24">
         <el-form :model="queryParams" ref="queryForm" size="mini" :inline="true">
-          <el-form-item label="任务名称" prop="taskName">
-            <el-input v-model="queryParams.taskName"></el-input>
+          <el-form-item label="执行(负责)人" prop="executors">
+            <dept-user-tree ref="dut" :userList="userList"
+                            @selected="(val)=>queryParams.executors=val"></dept-user-tree>
           </el-form-item>
           <el-form-item label="月份选择" prop="month">
             <el-date-picker
@@ -24,14 +25,14 @@
           <el-form-item label="状态" prop="status">
             <el-select
               v-model="queryParams.status"
-              placeholder="项目状态"
+              placeholder="任务状态"
               style="width: 100px">
-              <el-option label="全部" value="0"/>
-              <el-option label="未开始" value="1"/>
-              <el-option label="进行中" value="2"/>
-              <el-option label="延期" value="3"/>
-              <el-option label="完成" value="4"/>
-              <el-option label="终止" value="5"/>
+              <el-option
+                v-for="dict in dict.type.task_status"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
             </el-select>
           </el-form-item>
           <el-form-item>
@@ -59,7 +60,7 @@
                   size="mini">
           <el-table-column label="任务编号" prop="id" width="80"/>
           <el-table-column label="任务名称" prop="taskName" min-width="150" :show-overflow-tooltip="true"/>
-          <el-table-column label="执行人" prop="executorName"/>
+          <el-table-column label="执行(负责)人" prop="executorName"/>
           <el-table-column label="进度" prop="progressValue" width="80">
             <template slot-scope="scope">
               <span>{{ (scope.row.progressValue ? scope.row.progressValue : 0) + '%' }}</span>
@@ -273,6 +274,7 @@ import FileUpload from "@/components/FileUpload"
 import Project from "@/views/task/components/project";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getDictData} from "@/api/system/dict";
 
 const statusMap = {
   '0': {name: '待查看', type: 'info'},
@@ -285,6 +287,7 @@ const statusMap = {
 export default {
   name: "Task",
   components: {Project, TaskDetail, DeptUserTree, FileUpload, Treeselect},
+  dicts:['task_status'],
   data() {
     return {
       projectOptions: [],
@@ -302,15 +305,13 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        taskName: undefined,
+        executors: [],
         projectId: undefined,
         month: DateUtil.month(),
         status: '0'
       },
       // 表单参数
       form: {},
-      hnos: [],
-      hsytem: [],
       detailTitle: "",
       detailOpen: false,
       detailForm: {},
@@ -340,9 +341,18 @@ export default {
     };
   },
   created() {
-    this.getList();
+    this.initData();
   },
   methods: {
+    initData() {
+      this.getList();
+      getDeptUserTree('').then(res => {
+        this.userList = res.data
+      })
+      getDictData('task_status').then(res => {
+
+      })
+    },
     /** 查询项目列表 */
     getList() {
       if (this.queryParams.month && this.queryParams.month.length === 2) {
@@ -367,6 +377,7 @@ export default {
     /** 重置按钮操作 */
     resetQuery() {
       this.resetForm("queryForm");
+      this.$refs.dut.clearText()
       this.handleQuery();
     },
     handleSizeChange(val) {
@@ -431,9 +442,6 @@ export default {
 
     /** 新增按钮操作 */
     handleAdd() {
-      getDeptUserTree('').then(res => {
-        this.userList = res.data
-      })
       this.reset();
       this.open = true;
       this.title = "添加项目";
@@ -491,16 +499,13 @@ export default {
     },
     /** 分解按钮操作 */
     handleSplit(row) {
-      getDeptUserTree('').then(res => {
-        this.userList = res.data
-        this.splitForm = {
-          parentId: row.id,
-          taskName: row.taskName,
-          projectName: row.projectName,
-          children: []
-        }
-        this.splitOpen = true
-      })
+      this.splitForm = {
+        parentId: row.id,
+        taskName: row.taskName,
+        projectName: row.projectName,
+        children: []
+      }
+      this.splitOpen = true
     },
 
     /** 提交按钮 */