zs 1 year ago
parent
commit
c2ee944681

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

@@ -1,9 +1,12 @@
 export default {
   opera: '操作',
+  back: '返回',
   add: '添加',
   update: '修改',
   delete: '删除',
+  exam: '审核',
   dict: '字典数据',
+  sign: '报名管理',
   delete_confirm: '您确定删除该数据?',
   search: '查询',
   reset: '重置',

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

@@ -21,6 +21,7 @@ export default {
   demand_supply: '供给信息管理',
   demand_demand: '需求信息管理',
   match: '赛事管理',
+  match_sign: '报名管理',
   achievement: '成果管理',
   password: '修改密码'
 }

+ 34 - 3
src/lang/package/zh-cn/pages.js

@@ -75,27 +75,58 @@ export default {
   news: {
     addDialogTitle: '新增新闻',
     upDialogTitle: '修改新闻',
+    examDialogTitle: '审核新闻',
     title: '标题',
     person: '发布人',
     time: '发布时间',
     number: '浏览次数',
     content: '内容',
     is_use: '是否启用',
+    status: '审核状态',
     titleMessage: '请输入标题'
   },
   demand: {
     addDialogTitle: '新增需求',
     upDialogTitle: '修改需求',
+    examDialogTitle: '审核需求',
     name: '需求名称',
     field: '行业领域',
     urgent: '需求紧急度',
     method: '合作方式',
     time: '有效期',
-    money: '价格',
-    province: '省',
-    city: '市',
+    money: '价格(万元)',
+    area: '需求地区',
     brief: '简介',
+    status: '审核状态',
     is_use: '是否启用',
+    demand_status: '需求状态',
     titleMessage: '请输入需求名称'
+  },
+  match: {
+    addDialogTitle: '新增需赛事',
+    upDialogTitle: '修改赛事',
+    examDialogTitle: '审核赛事',
+    name: '赛事名称',
+    type: '赛事类型',
+    file: '封面',
+    time: '有效期',
+    money: '奖金(万元)',
+    rules: '赛事规则',
+    brief: '常见问题',
+    status: '审核状态',
+    match_status: '赛事状态',
+    is_use: '是否启用',
+    titleMessage: '请输入需求名称',
+    rules1: '大赛背景',
+    rules2: '大赛主题和目标',
+    rules3: '大赛基本情况介绍',
+    rules4: '赛题任务',
+    rules5: '赛制阶段',
+    rules6: '参赛资格',
+    rules7: '参赛报名',
+    rules8: '奖项设置与奖励办法',
+    rules9: '组织单位',
+    rules10: '赛事交流',
+    rules11: '赛事联络'
   }
 }

+ 1 - 9
src/layout/parts/Sidebar.vue

@@ -8,15 +8,7 @@
           <span class="logo-title">{{ $t('login.title') }}</span>
         </el-col>
         <el-col :span="24" class="second">
-          <el-menu
-            class="sidebar-el-menu"
-            :default-active="onRoutes"
-            unique-opened
-            router
-            background-color="#304156"
-            text-color="#bfcbd9"
-            active-text-color="#409eff"
-          >
+          <el-menu class="sidebar-el-menu" :default-active="onRoutes" unique-opened router background-color="#304156" text-color="#bfcbd9" active-text-color="#409eff">
             <menu-item :items="items"></menu-item>
           </el-menu>
         </el-col>

+ 2 - 0
src/router/guard.js

@@ -165,6 +165,8 @@ export const registerBeforeRouter = async (router) => {
       }
       // 检查目的地路由是否注册
       const hasRoute = hasNecessaryRoute(to, router)
+      console.log(to, router)
+      console.log(hasRoute);
       NProgress.inc()
       if (hasRoute || to.meta.hidden) {
         // 注册了直接进入

+ 5 - 0
src/router/index.js

@@ -38,6 +38,11 @@ export const constantRoutes = [
         },
         component: () => import('@/views/home/index.vue')
       },
+      // {
+      //   path: '/match/sign',
+      //   component: () => import('@/views/match/sign/index.vue'),
+      //   meta: { hidden: true }
+      // },
       {
         path: '401',
         component: () => import('@/views/error-page/401.vue'),

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

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/match'
+const axios = new AxiosWrapper()
+
+export const MatchStore = defineStore('match', () => {
+  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
+  }
+})

File diff suppressed because it is too large
+ 11536 - 0
src/utils/city.js


+ 232 - 0
src/views/match/info/index.vue

@@ -0,0 +1,232 @@
+<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 #type>
+        <el-option v-for="i in typeList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+    </custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @sign="toSign" @exam="toExam" @edit="toEdit" @delete="toDelete">
+      <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 #type>
+              <el-option v-for="i in typeList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #rules>
+              <div class="rules">
+                <custom-form v-model="form.rules" :fields="rulesFields" :useSave="false">
+                  <template #rules11>
+                    <WangEditor v-model="form.rules.rules11" />
+                  </template>
+                </custom-form>
+              </div>
+            </template>
+            <template #brief>
+              <WangEditor v-model="form.brief" />
+            </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 { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 路由
+const router = useRouter()
+// 接口
+import { MatchStore } from '@/store/api/platform/match'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = MatchStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.match.name'), model: 'name', isSearch: true },
+  { label: t('pages.match.type'), model: 'type', isSearch: true, type: 'select', format: (i) => getDict(i, 'type') },
+  { label: t('pages.match.money'), model: 'money' },
+  { label: t('pages.match.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.match.match_status'), model: 'match_status', format: (i) => getDict(i, 'match') },
+  { label: t('pages.match.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.sign'), method: 'sign' },
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_use === '1' },
+  { 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.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const typeList = ref([])
+const matchList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([
+  { label: t('pages.match.name'), model: 'name' },
+  { label: t('pages.match.type'), model: 'type', type: 'select' },
+  { label: t('pages.match.money'), model: 'money' },
+  { label: t('pages.match.time'), model: 'time', type: 'daterange' },
+  { label: t('pages.match.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.match.rules'), model: 'rules', custom: true },
+  { label: t('pages.match.brief'), model: 'brief', custom: true }
+])
+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 examFormFields = [{ label: t('pages.match.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ title: [{ required: true, message: t('pages.match.titleMessage'), trigger: 'blur' }] })
+const examForm = ref({})
+// 赛事规则
+const rulesFields = ref([
+  { label: t('pages.match.rules1'), model: 'rules1', type: 'textarea' },
+  { label: t('pages.match.rules2'), model: 'rules2', type: 'textarea' },
+  { label: t('pages.match.rules3'), model: 'rules3', type: 'textarea' },
+  { label: t('pages.match.rules4'), model: 'rules4', type: 'textarea' },
+  { label: t('pages.match.rules5'), model: 'rules5', type: 'textarea' },
+  { label: t('pages.match.rules6'), model: 'rules6', type: 'textarea' },
+  { label: t('pages.match.rules7'), model: 'rules7', type: 'textarea' },
+  { label: t('pages.match.rules8'), model: 'rules8', type: 'textarea' },
+  { label: t('pages.match.rules9'), model: 'rules9', type: 'textarea' },
+  { label: t('pages.match.rules10'), model: 'rules10', type: 'textarea' },
+  { label: t('pages.match.rules11'), model: 'rules11', custom: true }
+])
+// 请求
+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: 'matchType', is_use: '0' })
+  if ($checkRes(result)) typeList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 赛事状态
+  result = await dictDataStore.query({ code: 'matchStatus', is_use: '0' })
+  if ($checkRes(result)) matchList.value = result.data
+}
+const search = async (query = { skip: 0, 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) => {
+  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 == 'type') res = typeList.value.find((f) => f.value == data)
+  else if (model == 'match') res = matchList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: t('pages.match.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: t('pages.match.upDialogTitle') }
+}
+// 报名管理
+const toSign = (data) => {
+  router.push({ path: '/match/sign', query: { id: data._id } })
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, 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: 0, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.match.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  let res = await store.update({ _id: get(data, '_id'), is_use })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = { rules: {} }
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss">
+.rules {
+  width: 100%;
+  border: 1px solid #dcdfe6;
+  padding: 10px;
+  border-radius: 5px;
+  :deep(.el-form-item) {
+    margin-bottom: 18px !important;
+  }
+}
+</style>

+ 1 - 1
src/views/match/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>

+ 210 - 6
src/views/platform/demand/demand/index.vue

@@ -1,20 +1,224 @@
 <template>
-  <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>
-    </el-row>
+  <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 #field>
+        <el-option v-for="i in fieldList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+      <template #urgent>
+        <el-option v-for="i in urgentList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+      <template #method>
+        <el-option v-for="i in methodList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+    </custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
+      <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 #field>
+              <el-option v-for="i in fieldList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #urgent>
+              <el-option v-for="i in urgentList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #method>
+              <el-option v-for="i in methodList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" @change="handleChange" style="width: 100%" />
+            </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 { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.demand.name'), model: 'name', isSearch: true },
+  { label: t('pages.demand.field'), model: 'field', isSearch: true, type: 'select', format: (i) => getDict(i, 'field') },
+  { label: t('pages.demand.urgent'), model: 'urgent', isSearch: true, type: 'select', format: (i) => getDict(i, 'urgent') },
+  { label: t('pages.demand.method'), model: 'method', isSearch: true, type: 'select', format: (i) => getDict(i, 'method') },
+  { label: t('pages.demand.money'), model: 'money' },
+  { label: t('pages.demand.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.demand.demand_status'), model: 'demand_status', format: (i) => getDict(i, 'demand') },
+  { label: t('pages.demand.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_use === '1' },
+  { 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.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const cityList = ref([])
+const demandList = ref([])
 // 加载中
 const loading = ref(false)
+const formFields = ref([
+  { label: t('pages.demand.name'), model: 'name' },
+  { label: t('pages.demand.field'), model: 'field', type: 'select' },
+  { label: t('pages.demand.urgent'), model: 'urgent', type: 'select' },
+  { label: t('pages.demand.method'), model: 'method', type: 'select' },
+  { label: t('pages.demand.money'), model: 'money' },
+  { label: t('pages.demand.area'), model: 'area', custom: true },
+  { label: t('pages.demand.time'), model: 'time', type: 'daterange' },
+  { label: t('pages.demand.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.demand.brief'), model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: t('pages.demand.titleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.demand.addDialogTitle') })
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.demand.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ title: [{ required: true, message: t('pages.demand.titleMessage'), 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 dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 需求状态
+  result = await dictDataStore.query({ code: 'demandStatus', is_use: '0' })
+  if ($checkRes(result)) demandList.value = result.data
+}
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '1' }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  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 == 'method') res = methodList.value.find((f) => f.value == data)
+  else if (model == 'urgent') res = urgentList.value.find((f) => f.value == data)
+  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'demand') res = demandList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 添加
+const toAdd = () => {
+  form.value = { type: '1' }
+  dialog.value = { type: '1', show: true, title: t('pages.demand.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: t('pages.demand.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, 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: 0, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.demand.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  let res = await store.update({ _id: get(data, '_id'), is_use })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
 </script>
 <style scoped lang="scss"></style>

+ 109 - 17
src/views/platform/demand/supply/index.vue

@@ -1,11 +1,21 @@
 <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-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset">
+      <template #field>
+        <el-option v-for="i in fieldList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+      <template #urgent>
+        <el-option v-for="i in urgentList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+      <template #method>
+        <el-option v-for="i in methodList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+      </template>
+    </custom-search-bar>
     <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
-    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete">
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
       <template #is_use="{ row }">
-        <el-tag v-if="row.is_use == '0'" type="success">启用</el-tag>
-        <el-tag v-else type="info">禁用</el-tag>
+        <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">
@@ -15,6 +25,25 @@
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
             </template>
+            <template #field>
+              <el-option v-for="i in fieldList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #urgent>
+              <el-option v-for="i in urgentList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #method>
+              <el-option v-for="i in methodList" :key="i._id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" @change="handleChange" style="width: 100%" />
+            </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>
@@ -23,6 +52,8 @@
 </template>
 
 <script setup>
+// API 引用
+import { getCity } from '@/utils/city'
 import { cloneDeep, get } from 'lodash-es'
 const $checkRes = inject('$checkRes')
 const { t } = useI18n()
@@ -35,21 +66,31 @@ const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.demand.name'), model: 'name', isSearch: true },
-  { label: t('pages.demand.field'), model: 'field', isSearch: true },
-  { label: t('pages.demand.urgent'), model: 'urgent', isSearch: true },
-  { label: t('pages.demand.method'), model: 'method' },
+  { label: t('pages.demand.field'), model: 'field', isSearch: true, type: 'select', format: (i) => getDict(i, 'field') },
+  { label: t('pages.demand.urgent'), model: 'urgent', isSearch: true, type: 'select', format: (i) => getDict(i, 'urgent') },
+  { label: t('pages.demand.method'), model: 'method', isSearch: true, type: 'select', format: (i) => getDict(i, 'method') },
   { label: t('pages.demand.money'), model: 'money' },
-  { label: t('pages.demand.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i) }
+  { label: t('pages.demand.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.demand.demand_status'), model: 'demand_status', format: (i) => getDict(i, 'demand') },
+  { label: t('pages.demand.status'), model: 'status', format: (i) => getDict(i, 'status') }
 ]
 const opera = [
-  { label: t('common.update'), method: 'edit' },
-  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_use === '1' },
+  { 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.add'), method: 'add' }]
 let skip = 0
 let limit = inject('limit')
 const total = ref(20)
+// 字典表
 const isUseList = ref([])
+const statusList = ref([])
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const cityList = ref([])
+const demandList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
@@ -58,8 +99,7 @@ const formFields = ref([
   { label: t('pages.demand.urgent'), model: 'urgent', type: 'select' },
   { label: t('pages.demand.method'), model: 'method', type: 'select' },
   { label: t('pages.demand.money'), model: 'money' },
-  { label: t('pages.demand.province'), model: 'province' },
-  { label: t('pages.demand.city'), model: 'city' },
+  { label: t('pages.demand.area'), model: 'area', custom: true },
   { label: t('pages.demand.time'), model: 'time', type: 'daterange' },
   { label: t('pages.demand.is_use'), model: 'is_use', type: 'radio' },
   { label: t('pages.demand.brief'), model: 'brief', type: 'textarea' }
@@ -67,17 +107,41 @@ const formFields = ref([
 const rules = reactive({ name: [{ required: true, message: t('pages.demand.titleMessage'), trigger: 'blur' }] })
 const dialog = ref({ type: '1', show: false, title: t('pages.demand.addDialogTitle') })
 const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.demand.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ title: [{ required: true, message: t('pages.demand.titleMessage'), 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 () => {
-  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  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 dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 需求状态
+  result = await dictDataStore.query({ code: 'demandStatus', is_use: '0' })
+  if ($checkRes(result)) demandList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '0' }
@@ -88,8 +152,14 @@ const search = async (query = { skip: 0, limit }) => {
   }
 }
 // 字典数据转换
-const getDict = (data) => {
-  const res = isUseList.value.find((f) => f.value == data)
+const getDict = (data, model) => {
+  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 == 'method') res = methodList.value.find((f) => f.value == data)
+  else if (model == 'urgent') res = urgentList.value.find((f) => f.value == data)
+  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'demand') res = demandList.value.find((f) => f.value == data)
   return get(res, 'label')
 }
 // 添加
@@ -111,14 +181,36 @@ const toDelete = async (data) => {
 }
 const toSave = async () => {
   const data = cloneDeep(form.value)
+  const other = { status: '0' }
   let res
-  if (get(data, '_id')) res = await store.update(data)
-  else res = await store.create(data)
+  if (get(data, '_id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
   if ($checkRes(res, true)) {
     search({ skip: 0, limit })
     toClose()
   }
 }
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.demand.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  let res = await store.update({ _id: get(data, '_id'), is_use })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
 // 重置
 const toReset = async () => {
   searchForm.value = {}

+ 52 - 10
src/views/platform/news/index.vue

@@ -2,10 +2,10 @@
   <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"></custom-button-bar>
-    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete">
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
       <template #is_use="{ row }">
-        <el-tag v-if="row.is_use == '0'" type="success">启用</el-tag>
-        <el-tag v-else type="info">禁用</el-tag>
+        <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">
@@ -20,6 +20,13 @@
             </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>
@@ -45,17 +52,20 @@ const fields = [
   { label: t('pages.news.person'), model: 'person', isSearch: true },
   { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true },
   { label: t('pages.news.number'), model: 'number' },
-  { label: t('pages.news.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i) }
+  { label: t('pages.news.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.news.status'), model: 'status', format: (i) => getDict(i, 'status') }
 ]
 const opera = [
-  { label: t('common.update'), method: 'edit' },
-  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_use === '1' },
+  { 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.add'), method: 'add' }]
 let skip = 0
 let limit = inject('limit')
 const total = ref(20)
 const isUseList = ref([])
+const statusList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([])
@@ -75,6 +85,10 @@ const formFieldsForUpdate = [
 const rules = reactive({ title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }] })
 const dialog = ref({ type: '1', show: false, title: t('pages.news.addDialogTitle') })
 const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.news.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }] })
+const examForm = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
@@ -84,8 +98,13 @@ onMounted(async () => {
 })
 
 const searchOther = async () => {
-  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  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: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '1' }
@@ -96,8 +115,10 @@ const search = async (query = { skip: 0, limit }) => {
   }
 }
 // 字典数据转换
-const getDict = (data) => {
-  const res = isUseList.value.find((f) => f.value == data)
+const getDict = (data, model) => {
+  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')
 }
 // 添加
@@ -121,7 +142,7 @@ const toDelete = async (data) => {
 }
 const toSave = async () => {
   const data = cloneDeep(form.value)
-  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }
   let res
   if (get(data, '_id')) res = await store.update({ ...data, ...other })
   else res = await store.create({ ...data, ...other })
@@ -130,6 +151,27 @@ const toSave = async () => {
     toClose()
   }
 }
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.news.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  let res = await store.update({ _id: get(data, '_id'), is_use })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
 // 重置
 const toReset = async () => {
   searchForm.value = {}

+ 52 - 10
src/views/platform/policy/index.vue

@@ -2,10 +2,10 @@
   <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"></custom-button-bar>
-    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete">
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @exam="toExam" @edit="toEdit" @delete="toDelete">
       <template #is_use="{ row }">
-        <el-tag v-if="row.is_use == '0'" type="success">启用</el-tag>
-        <el-tag v-else type="info">禁用</el-tag>
+        <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">
@@ -20,6 +20,13 @@
             </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>
@@ -45,17 +52,20 @@ const fields = [
   { label: t('pages.news.person'), model: 'person', isSearch: true },
   { label: t('pages.news.time'), model: 'time', type: 'date', isSearch: true },
   { label: t('pages.news.number'), model: 'number' },
-  { label: t('pages.news.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i) }
+  { label: t('pages.news.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.news.status'), model: 'status', format: (i) => getDict(i, 'status') }
 ]
 const opera = [
-  { label: t('common.update'), method: 'edit' },
-  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_use === '1' },
+  { 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.add'), method: 'add' }]
 let skip = 0
 let limit = inject('limit')
 const total = ref(20)
 const isUseList = ref([])
+const statusList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([])
@@ -75,6 +85,10 @@ const formFieldsForUpdate = [
 const rules = reactive({ title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }] })
 const dialog = ref({ type: '1', show: false, title: t('pages.news.addDialogTitle') })
 const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.news.status'), model: 'status', type: 'select' }]
+const examRules = reactive({ title: [{ required: true, message: t('pages.news.titleMessage'), trigger: 'blur' }] })
+const examForm = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
@@ -84,8 +98,13 @@ onMounted(async () => {
 })
 
 const searchOther = async () => {
-  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  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: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '0' }
@@ -96,8 +115,10 @@ const search = async (query = { skip: 0, limit }) => {
   }
 }
 // 字典数据转换
-const getDict = (data) => {
-  const res = isUseList.value.find((f) => f.value == data)
+const getDict = (data, model) => {
+  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')
 }
 // 添加
@@ -121,7 +142,7 @@ const toDelete = async (data) => {
 }
 const toSave = async () => {
   const data = cloneDeep(form.value)
-  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }
   let res
   if (get(data, '_id')) res = await store.update({ ...data, ...other })
   else res = await store.create({ ...data, ...other })
@@ -130,6 +151,27 @@ const toSave = async () => {
     toClose()
   }
 }
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.news.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update(data)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 开启或禁用
+const toUse = async (data, is_use) => {
+  let res = await store.update({ _id: get(data, '_id'), is_use })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
 // 重置
 const toReset = async () => {
   searchForm.value = {}