LR пре 2 година
родитељ
комит
dc48489715

+ 4 - 0
src/api/common/index.ts

@@ -57,6 +57,10 @@ export interface IOrg {
   prjList: IProject[]
   id: number
 }
+
+export interface IOrgProjectCombine extends IProject, Omit<IOrg, 'prjList'> {
+  orgId: number
+}
 export const fetchProjects = () => axios.request<IResult<IOrg[]>>({ url: uris.project.page, method: 'get' })
 
 export const setProjectKey = (id: number) =>

+ 44 - 0
src/store/modules/project.ts

@@ -0,0 +1,44 @@
+import { IOrgProjectCombine } from '@/api/common'
+
+interface IProjectState {
+  project: Partial<IOrgProjectCombine>
+}
+
+const lsName = 'project'
+
+const getDefaultProjectData = (): IProjectState => {
+  try {
+    const data = JSON.parse(localStorage.getItem(lsName))
+    if (data) return data
+  } catch (error) {
+    console.log(error)
+  }
+  return { project: {} }
+}
+
+const state = getDefaultProjectData()
+const getters = {
+  info: (state: IProjectState) => {
+    const { id: projectId, name: projectName } = state.project || {}
+    return { projectId, projectName }
+  }
+}
+const mutations = {
+  UPDATE: (state: IProjectState, data: IOrgProjectCombine) => {
+    state.project = { ...data }
+    localStorage.setItem(lsName, JSON.stringify(state))
+  },
+  RESET: (state: IProjectState) => {
+    state = getDefaultProjectData()
+    localStorage.setItem(lsName, JSON.stringify({}))
+  }
+}
+const actions = {}
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  mutations,
+  actions
+}

+ 6 - 7
src/views/prjSelection/index.vue

@@ -24,11 +24,7 @@ import { Vue, Component } from 'vue-property-decorator'
 import BaseTable from '@/components/BaseTable/index.vue'
 import { ElTableColumn } from 'element-ui/types/table-column'
 import { elTableAlignLeft } from '@/utils/constant'
-import { fetchProjects, IOrg, IProject, setProjectKey, addMenu } from '@/api/common'
-
-interface ITableData extends IProject, Omit<IOrg, 'prjList'> {
-  orgId: number
-}
+import { fetchProjects, IOrgProjectCombine, setProjectKey, addMenu } from '@/api/common'
 
 @Component({ name: 'Project', components: { BaseTable } })
 export default class Project extends Vue {
@@ -41,7 +37,7 @@ export default class Project extends Vue {
     { prop: 'name', label: '项目名称', minWidth: '150px', ...elTableAlignLeft(), _slot: true }
   ]
   keyword: string = ''
-  tableData: Partial<ITableData>[] = []
+  tableData: Partial<IOrgProjectCombine>[] = []
 
   async fetchProject() {
     const { result } = await fetchProjects()
@@ -52,8 +48,10 @@ export default class Project extends Vue {
       .flat()
   }
 
-  async onProjectSelect(row: ITableData) {
+  async onProjectSelect(row: IOrgProjectCombine) {
     const { id } = row || {}
+    this.$store.commit('project/UPDATE', row)
+
     if (id) {
       const { result } = await setProjectKey(id)
       if (result) {
@@ -94,6 +92,7 @@ export default class Project extends Vue {
   }
 
   created() {
+    this.$store.commit('project/RESET')
     this.fetchProject()
   }
 }

+ 61 - 6
src/views/spectrum/configuration/api/common.ts

@@ -6,11 +6,34 @@ export interface ColItem extends Partial<ElTableColumn> {
   _slot?: boolean
 }
 export interface IPagination {
-  current?: string | number
-  size?: string | number
-  total?: string | number
+  current?: number
+  size?: number
+  total?: number
 }
-export interface IDeviceType {
+export interface ICreator {
+  createTime: string
+  createUser: number
+  createUserName: string
+}
+export interface IUpdater {
+  updateTime: string
+  updateUser: number
+  updateUserName: string
+}
+export interface IIConFile extends ICreator {
+  id?: number
+  projectId: number
+  tableName: string
+  fileType: number
+  filePath: string
+  statusFlag: number
+  fileName: string
+  otherName: string
+  fileFormat: string
+  fileSize: string
+}
+export interface IDeviceType extends ICreator, IUpdater {
+  id?: number
   dataUploadTime: number
   dictionaryId: number
   fileTypeID: number
@@ -18,6 +41,40 @@ export interface IDeviceType {
   deviceTypeName: string
   file: string
   projectName: string
+
+  icon?: string
+  iconFiles?: IIConFile[]
+}
+
+export interface IDeviceTypeParamDictionary extends ICreator, IUpdater {
+  id: number
+  isdel: number
+  targetCode: string
+  targetMax: number
+  targetMin: number
+  targetName: string
+  targetUnit: string
+  typeId: number
+}
+export interface IDeviceTypeDictionary extends ICreator, IUpdater {
+  dataUploadTime: number
+  deviceTypeName: string
+  id: number
+  isdel: number
+}
+
+export interface IDeviceTypeParam {
+  id?: number
+  dictionaryId: number
+  isshow: number
+  remark: string
+  targetAlias: string
+  targetCode: string
+  targetName: string
+  targetUnit: string
+  typeId: number
+  targetMin: number
+  targetMax: number
 }
 
 export interface IStandard {
@@ -46,8 +103,6 @@ export interface IDevice {
   remark: string
 }
 
-export interface IDeviceTypeParam {}
-
 export interface IPoint {
   drainageId: number
   drainageName: string

+ 33 - 10
src/views/spectrum/configuration/api/deviceType.ts

@@ -1,6 +1,7 @@
+import { serialize } from 'object-to-formdata'
 import { IQueryCommon, IRes, IResult } from '@/api/common'
 import axios from '@/utils/request'
-import { base, IDeviceType, IDeviceTypeParam } from './common'
+import { base, IDeviceType, IDeviceTypeParam, IDeviceTypeDictionary, IDeviceTypeParamDictionary } from './common'
 
 const uris = {
   base: `${base}/devicetypep`,
@@ -10,11 +11,19 @@ const uris = {
     base: `${base}/devicetargetp`,
     page: `${base}/devicetargetp/page`,
     del: `${base}/devicetargetp/deleteByIds`
+  },
+  dictonary: {
+    type: `${base}/devicedictionaryp/page`,
+    param: `${base}/targetdictionaryp/page`
   }
 }
 
 export const addDeviceType = (data: Omit<IDeviceType, 'id'>) =>
-  axios.request<IRes<boolean>>({ url: uris.base, method: 'post', data })
+  axios.request<IRes<boolean>>({
+    url: uris.base,
+    method: 'post',
+    data: serialize(data, { dotsForObjectNotation: true, noFilesWithArrayNotation: true })
+  })
 
 export const deleteDeviceType = (id: string) =>
   axios.request<IRes<boolean>>({ url: `${uris.base}/${id}`, method: 'delete' })
@@ -25,26 +34,40 @@ export const updateDeviceType = (data: IDeviceType) =>
 export const getDeviceType = (id: string) =>
   axios.request<IResult<IDeviceType>>({ url: `${uris.base}/${id}`, method: 'get' })
 
-export const deviceTypesPage = (params: IDeviceType & IQueryCommon) =>
+export const deviceTypesPage = (params: Partial<IDeviceType & IQueryCommon>) =>
   axios.request<IRes<IDeviceType[]>>({ url: uris.page, method: 'get', params })
 
 export const deleteDeviceTypeBatch = (ids: string) =>
   axios.request<IRes<boolean>>({ url: uris.del, method: 'delete', params: { ids } })
 
 export const addDeviceTypeParam = (data: Omit<IDeviceTypeParam, 'id'>) =>
-  axios.request<IRes<boolean>>({ url: uris.base, method: 'post', data })
+  axios.request<IRes<boolean>>({ url: uris.param.base, method: 'post', data })
 
 export const deleteDeviceTypeParam = (id: string) =>
-  axios.request<IRes<boolean>>({ url: `${uris.base}/${id}`, method: 'delete' })
+  axios.request<IRes<boolean>>({ url: `${uris.param.base}/${id}`, method: 'delete' })
 
 export const updateDeviceTypeParam = (data: IDeviceTypeParam) =>
-  axios.request<IRes<boolean>>({ url: uris.base, method: 'put', data })
+  axios.request<IRes<boolean>>({ url: uris.param.base, method: 'put', data })
 
 export const getDeviceTypeParam = (id: string) =>
-  axios.request<IResult<IDeviceTypeParam>>({ url: `${uris.base}/${id}`, method: 'get' })
+  axios.request<IResult<IDeviceTypeParam>>({ url: `${uris.param.base}/${id}`, method: 'get' })
 
-export const deviceTypeParamPage = (params: IDeviceTypeParam & IQueryCommon) =>
-  axios.request<IRes<IDeviceTypeParam[]>>({ url: uris.page, method: 'get', params })
+export const deviceTypeParamPage = (params: Partial<IDeviceTypeParam & IQueryCommon>) =>
+  axios.request<IRes<IDeviceTypeParam[]>>({ url: uris.param.page, method: 'get', params })
 
 export const deleteDeviceTypeParamBatch = (ids: string) =>
-  axios.request<IRes<boolean>>({ url: uris.del, method: 'delete', params: { ids } })
+  axios.request<IRes<boolean>>({ url: uris.param.del, method: 'delete', params: { ids } })
+
+export const getTypeDictionary = () =>
+  axios.request<IRes<IDeviceTypeDictionary[]>>({
+    url: uris.dictonary.type,
+    method: 'get',
+    params: { current: 1, size: 9999999 }
+  })
+
+export const getTypeParamDictionary = (typeId: number) =>
+  axios.request<IRes<IDeviceTypeParamDictionary[]>>({
+    url: uris.dictonary.param,
+    method: 'get',
+    params: { ulevel: 2, typeId, current: 1, size: 9999999 }
+  })

+ 6 - 1
src/views/spectrum/configuration/type/ParamForm.vue

@@ -46,7 +46,9 @@
           v-bind="rest"
           clearable
         >
-          <template slot="suffix" v-if="name === 'rate'"> 分钟 </template>
+          <template slot="suffix" v-if="name === 'rate'">
+            分钟
+          </template>
         </el-input>
       </el-form-item>
     </el-form>
@@ -59,6 +61,7 @@ import BaseDialog from '@/views/monitoring/components/BaseDialog/index.vue'
 import { ElForm } from 'element-ui/types/form'
 import { IDeviceTypeParam } from '@/views/monitoring/api'
 import { getDefalutNumberProp } from '@/views/monitoring/utils'
+import { IDeviceTypeParamDictionary } from '../api/common'
 
 const getDefaultData = () => ({ isDisplay: 1, sort: 0, rate: 1 })
 
@@ -66,6 +69,8 @@ const getDefaultData = () => ({ isDisplay: 1, sort: 0, rate: 1 })
 export default class ParamForm extends Vue {
   @Prop({ type: Object, default: () => ({}) }) data!: object
   @Prop({ type: Boolean, default: false }) loading!: boolean
+  @Prop({ type: Array, default: () => [] }) dictionary!: IDeviceTypeParamDictionary
+
   $refs!: { form: ElForm }
   formData: IDeviceTypeParam = getDefaultData()
 

+ 118 - 100
src/views/spectrum/configuration/type/TypeForm.vue

@@ -1,27 +1,23 @@
 <template>
-  <BaseDialog v-bind="$attrs" v-on="listeners" @submit="onSubmit" :loading="loading">
-    <el-form class="form" ref="form" v-bind="{ labelWidth: '8em', size: 'medium' }" :model="formData">
-      <el-form-item
-        v-for="{ name, label, rules, required = false, type = 'text', onChange, ...rest } of formItems"
-        :key="name"
-        :required="required"
-        :label="label"
-        :rules="rules"
-        :prop="name"
-      >
-        <el-switch v-if="type === 'switch'" v-model="formData[name]" v-bind="rest" @change="onChange" />
-        <el-input-number
-          v-else-if="type === 'number'"
-          v-model="formData[name]"
-          controls-position="right"
-          :placeholder="`请输入${label}`"
-          v-bind="rest"
-        />
-        <el-input v-else v-model="formData[name]" :placeholder="`请输入${label}`" clearable v-bind="rest">
-          <template slot="suffix" v-if="name === 'collectTime'">
-            分钟
-          </template>
-        </el-input>
+  <BaseDialog v-bind="$attrs" v-on="listeners" @submit="onSubmit" :loading="loading" @closed="onClosed">
+    <el-form class="form" ref="form" v-bind="{ labelWidth: '6em', size: 'small' }" :model="formData" :rules="rules">
+      <el-form-item required label="设备类型" prop="dictionaryId">
+        <el-select v-model="formData.dictionaryId" @change="onNameChange">
+          <el-option v-for="item of dictionary" :key="item.id" :value="item.id" :label="item.deviceTypeName" />
+        </el-select>
+      </el-form-item>
+      <el-form-item required label="图标" prop="file">
+        <el-upload
+          class="icon-uploader"
+          action="somefakeurl"
+          :show-file-list="false"
+          :auto-upload="false"
+          :on-change="onFileChange"
+          accept=".jpg,.jpeg,.png"
+        >
+          <img v-if="imageUrl" :src="imageUrl" class="icon" />
+          <i v-else class="el-icon-plus icon-uploader-icon"></i>
+        </el-upload>
       </el-form-item>
     </el-form>
   </BaseDialog>
@@ -29,100 +25,122 @@
 
 <script lang="ts">
 import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
-import BaseDialog from '@/views/monitoring/components/BaseDialog/index.vue'
+import BaseDialog from '@/components/BaseDialog/index.vue'
 import { ElForm } from 'element-ui/types/form'
+import { IDeviceTypeDictionary, IDeviceType } from '../api/common'
+import { getRemoteImg } from '@/api/ftp'
+import { ElUploadInternalFileDetail } from 'element-ui/types/upload'
 
-const getDefaultValue = () => ({ sort: 1, isCollect: 1 })
+const getDefaultValue = () => ({ dictionaryId: undefined, file: undefined, fileTypeID: 803 })
 
 @Component({ name: 'TypeForm', components: { BaseDialog } })
 export default class TypeForm extends Vue {
   @Prop({ type: Object, default: () => ({}) }) data!: object
   @Prop({ type: Boolean, default: false }) loading!: boolean
+  @Prop({ type: Array, default: () => [] }) dictionary!: IDeviceTypeDictionary[]
   $refs!: { form: ElForm }
-  formData: { [x: string]: string | number | boolean } = getDefaultValue()
+  formData: Partial<IDeviceType> & {
+    file?: ElUploadInternalFileDetail | string
+  } = getDefaultValue()
+  imageUrl: string = ''
   get listeners() {
-    const { submit, ...rest } = this.$listeners
+    const { submit, closed, ...rest } = this.$listeners
     return rest
   }
-  get formItems() {
-    return [
-      {
-        label: '设备类型名称',
-        name: 'name',
-        required: true,
-        rules: [
-          { required: true, message: '设备类型名称不能为空!' },
-          { max: 50, message: '设备类型名称不超过50个字符' },
-          { pattern: /^[\u4e00-\u9fa5\w -]+$/, message: '允许输入汉字、英文、数字' }
-        ],
-        size: 'small'
-      },
-      {
-        label: '设备类型代码',
-        name: 'typeCode',
-        rules: [
-          { required: false, message: '设备类型代码不能为空!' },
-          { max: 50, message: '设备类型代码不超过50个字符' },
-          { pattern: /^[\w-]+$/, message: '允许输入英文、数字' }
-        ],
-        size: 'small'
-      },
-      {
-        label: '是否归集',
-        name: 'isCollect',
-        required: true,
-        type: 'switch',
-        rules: [{ required: true, message: '请选择是否归集', trigger: 'change' }],
-        size: 'small',
-        activeValue: 1,
-        inactiveValue: 0,
-        onChange: this.onIsCollectChange
-      },
-      {
-        label: '数据归集时间',
-        name: 'collectTime',
-        rules: [
-          { required: !!this.formData.isCollect, message: '数据归集时间不能为空!' },
-          {
-            type: 'number',
-            message: '数据归集时间须为数字 ',
-            transform: (val) => (Number.isNaN(Number(val)) ? val : +val || -1)
-          },
-          { type: 'integer', min: -1, max: 60 * 24 * 30, message: `数据归集时间须大于0小于${60 * 24 * 30}` }
-        ],
-        size: 'small',
-        disabled: !this.formData.isCollect,
-        required: !!this.formData.isCollect,
-        maxLength: 5
-      },
-      {
-        label: '显示顺序',
-        name: 'sort',
-        type: 'number',
-        rules: [{ type: 'integer', min: -1, message: '显示顺序为数字' }],
-        min: 0,
-        max: 999999,
-        size: 'small',
-        precision: 0,
-        stepStrictly: true,
-        style: { width: '100%', textAlign: 'left' },
-        class: 'input-number'
-      }
-    ]
+
+  get projectInfo() {
+    return this.$store.getters['project/info']
   }
-  onIsCollectChange(val: 1 | 0) {
-    if (!val) {
-      this.formData.collectTime = undefined
-      this.$refs.form.clearValidate('collectTime')
-    }
+
+  rules = {
+    dictionaryId: [{ type: 'number', required: true, message: '请选择设备类型' }],
+    file: [{ validator: (rule, value, callback) => (!value ? callback(new Error('请上传图标')) : callback()) }]
   }
+
   onSubmit() {
-    this.$refs.form.validate((valid) => valid && this.$emit('submit', this.formData))
+    this.$refs.form.validate((valid) => valid && this.$emit('submit', { ...this.formData, ...this.projectInfo }))
+  }
+
+  onNameChange(id) {
+    const { dataUploadTime, deviceTypeName } = this.dictionary.find((item) => (item.id = id)) || {}
+    this.formData.dataUploadTime = dataUploadTime
+    this.formData.deviceTypeName = deviceTypeName
+  }
+
+  onFileChange(file) {
+    const allowedTypes = ['image/jpeg', 'image/png']
+    const isLt10M = file.size / 1024 / 1024 < 10
+    let pass = true
+
+    if (!allowedTypes.includes(file.raw.type)) {
+      this.$message.error('上传文件只能是 JPG/JPEG、png 格式!')
+      pass = false
+    }
+
+    if (!isLt10M) {
+      this.$message.error('上传文件大小不能超过 10MB!')
+      pass = false
+    }
+    if (pass) {
+      const reader = new FileReader()
+      reader.onload = (e) => {
+        this.imageUrl = e.target.result.toString()
+      }
+      reader.readAsDataURL(file.raw)
+      this.formData.file = file.raw
+    }
+  }
+
+  onClosed() {
+    this.imageUrl = ''
+    this.$emit('closed')
   }
 
   @Watch('data', { immediate: true })
-  setDefaultData(val: any) {
-    this.formData = val.id ? { ...val } : getDefaultValue()
+  setDefaultData(val: IDeviceType) {
+    const { iconFiles } = val || {}
+    const { filePath } = (iconFiles || [])[0] || {}
+    if (val.id) {
+      this.formData = { ...val }
+      const { iconFiles } = val || {}
+      const { filePath } = (iconFiles || [])[0]
+      this.imageUrl = getRemoteImg(filePath)
+    } else {
+      this.formData = getDefaultValue()
+      this.imageUrl = ''
+    }
+
+    if (filePath) this.imageUrl = getRemoteImg(filePath)
   }
 }
 </script>
+<style lang="scss" scoped>
+.form {
+  >>> .icon {
+    $width: 128px;
+    $height: 128px;
+    width: $width;
+    height: $height;
+    display: block;
+    &-uploader .el-upload {
+      border: 1px dashed $--border-color-base;
+      border-radius: $--border-radius-small;
+      cursor: pointer;
+      position: relative;
+      overflow: hidden;
+      background-color: rgba(#000, 0.1);
+      &:hover {
+        border-color: $--color-primary;
+      }
+    }
+    &-uploader-icon {
+      font-size: 28px;
+      color: $--color-text-secondary;
+      width: $width;
+      height: $height;
+      line-height: $height;
+      text-align: center;
+    }
+  }
+}
+</style>

+ 325 - 6
src/views/spectrum/configuration/type/widget.vue

@@ -1,15 +1,326 @@
 <template>
-  <div class="type">type</div>
+  <div class="page-container">
+    <div class="actions">
+      <div>
+        <el-button type="primary" size="small" :loading="loading.typeSubmitting" @click="onTypeAdd">
+          新增设备类型
+        </el-button>
+        <el-button
+          type="danger"
+          size="small"
+          :disabled="!selected.type.length"
+          :loading="loading.typeDeleting"
+          @click="onTypeDelete"
+        >
+          删除设备类型
+        </el-button>
+      </div>
+      <div>
+        <el-button
+          type="primary"
+          size="small"
+          :loading="loading.paramSubmitting"
+          :disabled="!current.type.id"
+          @click="onParamAdd"
+          >新增参数</el-button
+        >
+        <el-button
+          type="danger"
+          size="small"
+          :disabled="!selected.param.length"
+          :loading="loading.paramDeleting"
+          @click="onParamDelete"
+        >
+          删除参数
+        </el-button>
+      </div>
+    </div>
+    <el-row :gutter="15">
+      <el-col :span="12">
+        <BaseTable
+          :data="types"
+          :columns="deviceTypeCols"
+          :pagination="pagination.type"
+          highlight-current-row
+          current-row-key="id"
+          v-loading="loading.type"
+          @row-dblclick="onTypeRowDblClick"
+          @selection-change="onTypeSelectionChange"
+          @page-change="onTypeQuery"
+          @current-change="onCurrentTypeChange"
+        >
+          <template v-slot:icon="{ row }">
+            <el-image :src="getRemoteImg(row)" style="width:100%" />
+          </template>
+        </BaseTable>
+      </el-col>
+      <el-col :span="12">
+        <BaseTable
+          :data="params"
+          :columns="deviceTypeParamCols"
+          :pagination="pagination.param"
+          v-loading="loading.param"
+          @row-dblclick="onParamRowDblClick"
+          @selection-change="onParamSelectionChange"
+          @page-change="onParamQuery"
+        >
+          <template v-for="(_, index) of params" v-slot:[`isDisplay-${index}`]="{ row }">
+            <el-switch
+              :key="`${index}-${row.id}`"
+              :active-value="true"
+              :inactive-value="false"
+              :value="row.isDisplay"
+              size="small"
+              style="user-select: none"
+              @change="($event) => onParamSubmit({ ...row, isDisplay: $event })"
+            />
+          </template>
+        </BaseTable>
+      </el-col>
+    </el-row>
+    <TypeForm
+      :visible.sync="visible.type"
+      :title="`${current.type.id ? '修改' : '新增'}设备类型`"
+      :data="current.type"
+      :dictionary="dictonary.type"
+      :loading="loading.typeSubmitting"
+      @submit="onTypeSubmit"
+      @closed="onRecoverLastCurrentType"
+    />
+    <ParamForm
+      :visible.sync="visible.param"
+      :title="`${current.param.id ? '修改' : '新增'}设备参数`"
+      :data="current.param"
+      :dictionary="dictonary.param"
+      :loading="loading.paramSubmitting"
+      @submit="onParamSubmit"
+    />
+  </div>
 </template>
 
 <script lang="ts">
-import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
+import { Vue, Component, Watch, Prop } from 'vue-property-decorator'
+import BaseTable from '@/components/BaseTable/index.vue'
+import TypeForm from './TypeForm.vue'
+import ParamForm from './ParamForm.vue'
+import {
+  deviceTypesPage,
+  deviceTypeParamPage,
+  addDeviceType,
+  updateDeviceType,
+  deleteDeviceTypeBatch,
+  updateDeviceTypeParam,
+  addDeviceTypeParam,
+  deleteDeviceTypeParamBatch,
+  getTypeParamDictionary,
+  getTypeDictionary
+} from '../api/deviceType'
 
-@Component({})
-export default class Type extends Vue {
+import { getDefaultPagination } from '@/utils/constant'
+import {
+  IPagination,
+  IDeviceType,
+  IDeviceTypeParam,
+  IDeviceTypeDictionary,
+  IDeviceTypeParamDictionary
+} from '../api/common'
+import { deviceTypeCols, deviceTypeParamCols } from '../utils'
+import { getRemoteImg } from '@/api/ftp'
+
+@Component({ name: 'DeviceTypes', components: { BaseTable, TypeForm, ParamForm } })
+export default class DeviceTypes extends Vue {
   @Prop({ type: Boolean, default: false }) isActive!: boolean
+  deviceTypeCols = deviceTypeCols
+  deviceTypeParamCols = deviceTypeParamCols
+
+  getRemoteImg(row: IDeviceType) {
+    const { iconFiles } = row || {}
+    const { filePath } = (iconFiles || [])[0]
+    return getRemoteImg(filePath)
+  }
+
+  visible = { type: false, param: false }
+  loading = {
+    type: false,
+    param: false,
+    typeDeleting: false,
+    paramDeleting: false,
+    typeSubmitting: false,
+    paramSubmitting: false
+  }
+
+  current: { type: Partial<IDeviceType>; param: Partial<IDeviceTypeParam>; lastType: Partial<IDeviceType> } = {
+    type: {},
+    param: {},
+    lastType: {}
+  }
+
+  selected: { type: Partial<IDeviceType>[]; param: Partial<IDeviceTypeParam>[] } = { type: [], param: [] }
+
+  pagination: { type: IPagination; param: IPagination } = {
+    type: getDefaultPagination(),
+    param: getDefaultPagination()
+  }
+
+  types: IDeviceType[] = []
+  params: IDeviceTypeParam[] = []
+
+  dictonary: { type: IDeviceTypeDictionary[]; param: IDeviceTypeParamDictionary[] } = { type: [], param: [] }
+
+  onTypeAdd() {
+    this.current = { ...this.current, lastType: this.current.type, type: {} }
+    this.visible.type = true
+  }
+
+  onRecoverLastCurrentType() {
+    this.current = { ...this.current, type: this.current.lastType }
+  }
+
+  onParamAdd() {
+    this.current = { ...this.current, param: {} }
+    this.visible.param = true
+  }
+
+  onTypeRowDblClick(row) {
+    this.current = { ...this.current, lastType: this.current.type, type: { ...row } }
+    this.visible.type = true
+  }
+
+  onParamRowDblClick(row) {
+    this.current = { ...this.current, param: { ...row } }
+    this.visible.param = true
+  }
+
+  onTypeSelectionChange(selections) {
+    this.selected = { ...this.selected, type: selections }
+  }
+
+  onParamSelectionChange(selections) {
+    this.selected = { ...this.selected, param: selections }
+  }
+
+  async onTypeQuery(query = {}) {
+    this.loading.type = true
+    try {
+      const {
+        result: { records, size, total, current }
+      } = await deviceTypesPage({ ...this.pagination.type, ...query })
+      this.pagination = { ...this.pagination, type: { current, size, total } }
+      this.types = records || []
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.type = false
+  }
+
+  async onParamQuery(query = {}) {
+    const { id: typeId } = this.current.type
+    if (!typeId) return
+    this.loading.param = true
+    try {
+      const {
+        result: { records, size, total, current }
+      } = await deviceTypeParamPage({ typeId, ...this.pagination.param, ...query })
+      this.pagination = { ...this.pagination, param: { current, size, total } }
+      this.params = records || []
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.param = false
+  }
+
+  async onTypeSubmit(data) {
+    this.loading.typeSubmitting = true
+    try {
+      const { result } = await (data.id ? updateDeviceType(data) : addDeviceType(data))
+      this.$message[result ? 'success' : 'error'](`${data.id ? '修改' : '新增'}设备类型${result ? '成功!' : '失败!'}`)
+      if (result) {
+        this.visible.type = false
+        this.onTypeQuery()
+      }
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.typeSubmitting = false
+  }
 
-  preparing() {}
+  async onParamSubmit(data) {
+    this.loading.paramSubmitting = true
+    try {
+      const { result } = await (data.id
+        ? updateDeviceTypeParam(data)
+        : addDeviceTypeParam({ ...data, typeId: this.current.type.id }))
+      this.$message[result ? 'success' : 'error'](`${data.id ? '修改' : '新增'}参数${result ? '成功!' : '失败!'}`)
+      if (result) {
+        this.visible.param = false
+        this.onParamQuery()
+      }
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.paramSubmitting = false
+  }
+
+  async onTypeDelete() {
+    await this.$confirm(`是否确认删除这${this.selected.type.length}项设备类型?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    this.loading.typeDeleting = true
+    try {
+      const { result } = await deleteDeviceTypeBatch(this.selected.type.map(({ id }) => id).join())
+      this.$message[result ? 'success' : 'error'](`删除设备类型${result ? '成功!' : '失败!'}`)
+      result && this.onTypeQuery()
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.typeDeleting = false
+  }
+
+  async onParamDelete() {
+    await this.$confirm(`是否确认删除这${this.selected.param.length}项设备参数?`, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    })
+    this.loading.paramDeleting = true
+    try {
+      const { result } = await deleteDeviceTypeParamBatch(this.selected.param.map(({ id }) => id).join())
+      this.$message[result ? 'success' : 'error'](`删除设备参数${result ? '成功!' : '失败!'}`)
+      result && this.onParamQuery()
+    } catch (error) {
+      console.log(error)
+    }
+    this.loading.paramDeleting = false
+  }
+
+  onCurrentTypeChange(row) {
+    this.current = { ...this.current, type: row ? { ...row } : {} }
+    !row && (this.params = [])
+    this.onParamQuery(getDefaultPagination())
+  }
+
+  async fetchTypeDictionary() {
+    const {
+      result: { records: type }
+    } = await getTypeDictionary()
+
+    this.dictonary = { ...this.dictonary, type }
+  }
+
+  async fetchParamDictionary(id: number) {
+    const {
+      result: { records: param }
+    } = await getTypeParamDictionary(id)
+
+    this.dictonary = { ...this.dictonary, param }
+  }
+
+  preparing() {
+    this.onTypeQuery()
+    this.fetchTypeDictionary()
+  }
 
   mounted() {
     this.preparing()
@@ -24,4 +335,12 @@ export default class Type extends Vue {
 }
 </script>
 
-<style lang="scss"></style>
+<style scoped>
+.actions {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding-bottom: 22px;
+  margin-bottom: 15px;
+}
+</style>

+ 32 - 2
src/views/spectrum/configuration/utils.ts

@@ -1,5 +1,6 @@
+import { getIntervalValue } from '@/utils/constant'
 import { ElTableColumn } from 'element-ui/types/table-column'
-import { ColItem } from './api/common'
+import { ColItem, IDeviceTypeParam } from './api/common'
 
 export const alignLeft = (tooltips: boolean = true): Partial<ElTableColumn> => ({
   align: 'left',
@@ -7,7 +8,7 @@ export const alignLeft = (tooltips: boolean = true): Partial<ElTableColumn> => (
   showOverflowTooltip: tooltips
 })
 
-export const deviceTypeCols: ColItem[] = [
+export const deviceCols: ColItem[] = [
   { type: 'selection', width: '50px' },
   { type: 'index', label: '序号', width: '60px' },
   { prop: 'name', label: '排水分区', minWidth: '78px', ...alignLeft() },
@@ -23,3 +24,32 @@ export const deviceTypeCols: ColItem[] = [
   { prop: 'name', label: '安装时间', minWidth: '68px', ...alignLeft() },
   { prop: 'name', label: '站点状态', minWidth: '68px' }
 ]
+
+export const deviceTypeCols: ColItem[] = [
+  { type: 'selection', width: '50px' },
+  { type: 'index', label: '序号', width: '60px' },
+  { prop: 'deviceTypeName', label: '设备类型', minWidth: '120px', ...alignLeft() },
+  { prop: 'dataUploadTime', label: '数据刷新时间(min)', width: '140px' },
+  { prop: 'icon', label: '图标', width: '100px', _slot: true }
+]
+
+export const deviceTypeParamCols: ColItem[] = [
+  { type: 'selection', width: '50px' },
+  { type: 'index', label: '序号', width: '60px' },
+  { prop: 'targetName', label: '参数名称', minWidth: '120px', ...alignLeft() },
+  { prop: 'targetCode', label: '字段名称', minWidth: '120px', ...alignLeft() },
+  { prop: 'targetUnit', label: '单位', minWidth: '120px', ...alignLeft() },
+  {
+    prop: 'range',
+    label: '量程',
+    minWidth: '120px',
+    ...alignLeft(),
+    formatter: ({ targetMin, targetMax }: IDeviceTypeParam) => getIntervalValue(targetMin, targetMax)
+  },
+  {
+    prop: 'isshow',
+    label: '是否显示',
+    width: '80px',
+    formatter: ({ isshow }: IDeviceTypeParam) => (isshow ? '是' : '否')
+  }
+]