Browse Source

修改行研产研

zs 10 months ago
parent
commit
02e77894b4

+ 1 - 0
src/lang/package/zh-cn/common.js

@@ -10,6 +10,7 @@ export default {
   exam: '审核',
   dict: '字典数据',
   sign: '报名',
+  journal: '期刊',
   delete_confirm: '您确定删除该数据?',
   search: '查询',
   reset: '重置',

+ 5 - 1
src/lang/package/zh-cn/menus.js

@@ -12,7 +12,8 @@ export default {
   system_tags: '导航设置',
   system_design: '平台设置',
   system_dept: '部门管理',
-  system_sector: '行业设置',
+  system_sector: '产业设置',
+  system_region: '地区设置',
   user_admin: '管理员用户',
   user_user: '平台用户',
   platform: '信息管理',
@@ -30,6 +31,9 @@ export default {
   achievement: '成果管理',
   project: '项目管理',
   footplate: '平台管理',
+  support: '服务管理',
+  journal: '行研产研管理',
+  notes: '期刊管理',
   log: '日志管理',
   log_opera: '操作日志'
 }

+ 45 - 0
src/lang/package/zh-cn/pages.js

@@ -264,6 +264,7 @@ export default {
     addDialogTitle: '新增平台',
     upDialogTitle: '修改平台',
     examDialogTitle: '审核平台',
+    file: '封面',
     user: '用户',
     tags: '标签',
     industry: '所属产业',
@@ -280,6 +281,50 @@ export default {
     is_use: '是否启用',
     titleMessage: '请输入平台名称'
   },
+  support: {
+    addDialogTitle: '新增服务',
+    upDialogTitle: '修改服务',
+    examDialogTitle: '审核服务',
+    user: '用户',
+    tags: '标签',
+    industry: '所属产业',
+    name: '名称',
+    field: '服务领域',
+    time: '登记时间',
+    area: '所在地区',
+    address: '地址',
+    contacts: '联系人',
+    phone: '联系电话',
+    brief: '简介',
+    status: '审核状态',
+    is_use: '是否启用',
+    titleMessage: '请输入服务名称'
+  },
+  journal: {
+    addDialogTitle: '新增行研产研',
+    upDialogTitle: '修改行研产研',
+    examDialogTitle: '审核行研产研',
+    file: '封面',
+    name: '名称',
+    brief: '简介',
+    status: '审核状态',
+    is_use: '是否启用',
+    titleMessage: '请输入行研产研名称'
+  },
+  notes: {
+    addDialogTitle: '新增期刊',
+    upDialogTitle: '修改期刊',
+    examDialogTitle: '审核期刊',
+    journal: '所属期刊',
+    client: '委托方',
+    partner: '合作方',
+    file: '封面',
+    name: '名称',
+    brief: '简介',
+    status: '审核状态',
+    is_use: '是否启用',
+    titleMessage: '请输入期刊名称'
+  },
   sign: {
     name: '姓名',
     phone: '手机号',

+ 40 - 0
src/store/api/platform/journal.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/journal'
+const axios = new AxiosWrapper()
+
+export const JournalStore = defineStore('journal', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/platform/notes.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/notes'
+const axios = new AxiosWrapper()
+
+export const NotesStore = defineStore('notes', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 40 - 0
src/store/api/system/region.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/region'
+const axios = new AxiosWrapper()
+
+export const RegionStore = defineStore('region', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, 'id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 12 - 2
src/views/footplate/index.vue

@@ -16,6 +16,9 @@
       <el-row>
         <el-col :span="24" v-if="dialog.type == '1'">
           <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/template_footplate/upload" @change="onUpload"></custom-upload>
+            </template>
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
             </template>
@@ -95,6 +98,7 @@ const selectList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
+  { label: t('pages.footplate.file'), model: 'file', custom: true },
   { label: t('pages.footplate.name'), model: 'name' },
   { label: t('pages.footplate.tags'), model: 'tags', custom: true },
   { label: t('pages.footplate.industry'), model: 'industry', type: 'select' },
@@ -110,7 +114,7 @@ const formFields = ref([
 ])
 const rules = reactive({ name: [{ required: true, message: t('pages.footplate.titleMessage'), trigger: 'blur' }] })
 const dialog = ref({ type: '1', show: false, title: t('pages.footplate.addDialogTitle') })
-const form = ref({})
+const form = ref({ file: [] })
 // 审核
 const examFormFields = [{ label: t('pages.footplate.status'), model: 'status', type: 'select' }]
 const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
@@ -187,6 +191,7 @@ const toAdd = () => {
 }
 // 修改
 const toEdit = (data) => {
+  if (!data.file) data.file = []
   form.value = data
   dialog.value = { type: '1', show: true, title: t('pages.footplate.upDialogTitle') }
 }
@@ -197,6 +202,11 @@ const toDelete = async (data) => {
     search({ skip, limit })
   }
 }
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
 const toSave = async () => {
   const data = cloneDeep(form.value)
   const other = { status: '0' }
@@ -239,7 +249,7 @@ const toReset = async () => {
   await search({ skip, limit })
 }
 const toClose = () => {
-  form.value = {}
+  form.value = { file: [] }
   dialog.value = { show: false }
 }
 </script>

+ 214 - 0
src/views/journal/index.vue

@@ -0,0 +1,214 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"> </custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd" @select="toMoreDelect"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @journal="toJournal" @exam="toExam" @edit="toEdit" @delete="toDelete" @toSelect="toSelect" :select="true">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">启用</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">禁用</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/template_journal/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { JournalStore } from '@/store/api/platform/journal'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = JournalStore()
+const dictDataStore = DictDataStore()
+// 路由
+const router = useRouter()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.journal.name'), model: 'name', isSearch: true },
+  { label: t('pages.journal.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.journal.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.journal'), method: 'journal' },
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger', display: (i) => i.is_use === '1' }
+]
+const buttonFields = [
+  { label: t('common.create'), method: 'add' },
+  { label: t('common.select'), method: 'select', type: 'danger' }
+]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+// 多选列表
+const selectList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([
+  { label: t('pages.journal.file'), model: 'file', custom: true },
+  { label: t('pages.journal.name'), model: 'name' },
+  { label: t('pages.journal.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.journal.brief'), model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: t('pages.journal.titleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.journal.addDialogTitle') })
+const form = ref({ file: [] })
+// 审核
+const examFormFields = [{ label: t('pages.journal.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 多选
+const toSelect = (val) => {
+  selectList.value = val
+}
+// 批量删除
+const toMoreDelect = () => {
+  if (selectList.value.length > 0) {
+    ElMessageBox.confirm(`确定批量删除数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+      .then(async () => {
+        console.log(selectList.value)
+      })
+      .catch(() => {})
+  } else {
+    ElMessage({
+      message: '未选择要处理的数据!',
+      type: 'warning'
+    })
+  }
+}
+// 添加
+const toAdd = () => {
+  form.value = { type: '1' }
+  dialog.value = { type: '1', show: true, title: t('pages.journal.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  if (!data.file) data.file = []
+  form.value = data
+  dialog.value = { type: '1', show: true, title: t('pages.journal.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+// 期刊管理
+const toJournal = (data) => {
+  router.push({ path: '/journal/notes', query: { id: data.id } })
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0' }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.journal.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.name}】数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = { file: [] }
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 213 - 0
src/views/journal/notes.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"> </custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd" @select="toMoreDelect"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete" @toSelect="toSelect" :select="true">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">启用</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">禁用</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/template_notes/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { NotesStore } from '@/store/api/platform/notes'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = NotesStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.notes.name'), model: 'name', isSearch: true },
+  { label: t('pages.notes.client'), model: 'client', isSearch: true },
+  { label: t('pages.notes.partner'), model: 'partner', isSearch: true },
+  { label: t('pages.notes.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.notes.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger', display: (i) => i.is_use === '1' }
+]
+const buttonFields = [
+  { label: t('common.create'), method: 'add' },
+  { label: t('common.select'), method: 'select', type: 'danger' }
+]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+// 路由
+const route = useRoute()
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+// 多选列表
+const selectList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([
+  { label: t('pages.notes.file'), model: 'file', custom: true },
+  { label: t('pages.notes.name'), model: 'name' },
+  { label: t('pages.notes.client'), model: 'client' },
+  { label: t('pages.notes.partner'), model: 'partner' },
+  { label: t('pages.notes.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.notes.brief'), model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: t('pages.notes.titleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.notes.addDialogTitle') })
+const form = ref({ file: [], journal: route.query.id })
+// 审核
+const examFormFields = [{ label: t('pages.notes.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, journal: route.query.id }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 多选
+const toSelect = (val) => {
+  selectList.value = val
+}
+// 批量删除
+const toMoreDelect = () => {
+  if (selectList.value.length > 0) {
+    ElMessageBox.confirm(`确定批量删除数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+      .then(async () => {
+        console.log(selectList.value)
+      })
+      .catch(() => {})
+  } else {
+    ElMessage({
+      message: '未选择要处理的数据!',
+      type: 'warning'
+    })
+  }
+}
+// 添加
+const toAdd = () => {
+  form.value = { type: '1' }
+  dialog.value = { type: '1', show: true, title: t('pages.notes.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  if (!data.file) data.file = []
+  form.value = data
+  dialog.value = { type: '1', show: true, title: t('pages.notes.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', journal: route.query.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.notes.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.name}】数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = { file: [], journal: route.query.id }
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 7 - 2
src/views/match/info/index.vue

@@ -19,6 +19,9 @@
       <el-row>
         <el-col :span="24" v-if="dialog.type == '1'">
           <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/template_match/upload" @change="onUpload"></custom-upload>
+            </template>
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
             </template>
@@ -153,6 +156,7 @@ const sectorList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
+  { label: t('pages.match.file'), model: 'file', custom: true },
   { label: t('pages.match.name'), model: 'name' },
   { label: t('pages.match.tags'), model: 'tags', custom: true },
   { label: t('pages.match.type'), model: 'type', type: 'select' },
@@ -169,7 +173,7 @@ const rules = reactive({
   name: [{ required: true, message: t('pages.match.titleMessage'), trigger: 'blur' }]
 })
 const dialog = ref({ type: '1', show: false, title: t('pages.match.addDialogTitle') })
-const form = ref({ rules: {} })
+const form = ref({ rules: {}, file: [] })
 // 审核
 const examFormFields = [{ label: t('pages.match.status'), model: 'status', type: 'select' }]
 const examRules = reactive({
@@ -251,6 +255,7 @@ const toAdd = () => {
 }
 // 修改
 const toEdit = (data) => {
+  if (!data.file) data.file = []
   form.value = data
   dialog.value = { type: '1', show: true, title: t('pages.match.upDialogTitle') }
 }
@@ -311,7 +316,7 @@ const toReset = async () => {
   await search({ skip, limit })
 }
 const toClose = () => {
-  form.value = { rules: {} }
+  form.value = { rules: {}, file: [] }
   dialog.value = { show: false }
 }
 </script>

+ 243 - 0
src/views/support/index.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset">
+      <template #industry>
+        <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+      </template>
+    </custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd" @select="toMoreDelect"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete" @toSelect="toSelect" :select="true">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">启用</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">禁用</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #industry>
+              <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'label', label: 'label' }" :options="cityList" style="width: 100%" />
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+// API 引用
+import { getCity } from '@/utils/city'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { SupportStore } from '@/store/api/platform/support'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+import { SectorStore } from '@/store/api/system/sector'
+const store = SupportStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const sectorStore = SectorStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.support.name'), model: 'name', isSearch: true },
+  { label: t('pages.support.industry'), model: 'industry', isSearch: true, type: 'select' },
+  { label: t('pages.support.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.support.field'), model: 'build', isSearch: true },
+  { label: t('pages.support.time'), model: 'operate', isSearch: true },
+  { label: t('pages.support.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.support.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger', display: (i) => i.is_use === '1' }
+]
+const buttonFields = [
+  { label: t('common.create'), method: 'add' },
+  { label: t('common.select'), method: 'select', type: 'danger' }
+]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const cityList = ref([])
+const tagsList = ref([])
+const sectorList = ref([])
+// 多选列表
+const selectList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([
+  { label: t('pages.support.name'), model: 'name' },
+  { label: t('pages.support.tags'), model: 'tags', custom: true },
+  { label: t('pages.support.industry'), model: 'industry', type: 'select' },
+  { label: t('pages.support.field'), model: 'field' },
+  { label: t('pages.support.time'), model: 'time', type: 'date' },
+  { label: t('pages.support.area'), model: 'area', custom: true },
+  { label: t('pages.support.address'), model: 'address', type: 'textarea' },
+  { label: t('pages.support.contacts'), model: 'contacts' },
+  { label: t('pages.support.phone'), model: 'phone' },
+  { label: t('pages.support.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.support.brief'), model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: t('pages.support.titleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.support.addDialogTitle') })
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.support.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  getCity().then((response) => {
+    cityList.value = response.address
+  })
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'tags') return data.join(',')
+    return get(res, 'label')
+  }
+}
+// 多选
+const toSelect = (val) => {
+  selectList.value = val
+}
+// 批量删除
+const toMoreDelect = () => {
+  if (selectList.value.length > 0) {
+    ElMessageBox.confirm(`确定批量删除数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+      .then(async () => {
+        console.log(selectList.value)
+      })
+      .catch(() => {})
+  } else {
+    ElMessage({
+      message: '未选择要处理的数据!',
+      type: 'warning'
+    })
+  }
+}
+// 添加
+const toAdd = () => {
+  form.value = { type: '1' }
+  dialog.value = { type: '1', show: true, title: t('pages.support.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: t('pages.support.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0' }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.support.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  ElMessageBox.confirm(`确定修改【${data.name}】数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss"></style>

+ 1 - 1
src/views/system/config/index.vue

@@ -2,7 +2,7 @@
   <div id="index">
     <el-row>
       <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 系统设置 </el-col>
+        <el-col :span="24" class="one"> 地区设置 </el-col>
       </el-col>
     </el-row>
   </div>