Browse Source

修改 需求 项目 赛事 新闻 成果 显示

zs 9 months ago
parent
commit
34aaca460f

+ 1 - 1
.prettierrc.json

@@ -3,6 +3,6 @@
   "semi": false,
   "tabWidth": 2,
   "singleQuote": true,
-  "printWidth": 100,
+  "printWidth": 400,
   "trailingComma": "none"
 }

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

@@ -3,6 +3,7 @@ export default {
   opera: '操作',
   back: '返回',
   create: '添加',
+  select: '批量删除',
   update: '修改',
   delete: '删除',
   detail: '详情',

+ 4 - 3
src/lang/package/zh-cn/menus.js

@@ -15,9 +15,10 @@ export default {
   user_admin: '管理员用户',
   user_user: '平台用户',
   platform: '信息管理',
-  platform_policy: '政策法规',
-  platform_news: '新闻资讯',
-  platform_demand: '供需管理',
+  platform_policy: '政策信息',
+  platform_news: '新闻通知',
+  platform_trends: '行业动态',
+  demand: '供需管理',
   platform_match: '赛事管理',
   platform_achievement: '成果管理',
   demand_supply: '供给信息管理',

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

@@ -103,14 +103,15 @@ export default {
     sortfMessage: '请输入排序'
   },
   news: {
-    addDialogTitle: '新增新闻',
-    upDialogTitle: '修改新闻',
-    examDialogTitle: '审核新闻',
+    addDialogTitle: '新增',
+    upDialogTitle: '修改',
+    examDialogTitle: '审核',
     title: '标题',
     person: '发布人',
     time: '发布时间',
     number: '浏览次数',
     content: '内容',
+    tags: '标签',
     logo: '封面',
     is_use: '是否启用',
     status: '审核状态',
@@ -121,6 +122,7 @@ export default {
     upDialogTitle: '修改需求',
     examDialogTitle: '审核需求',
     name: '需求名称',
+    tags: '标签',
     field: '行业领域',
     urgent: '需求紧急度',
     method: '合作方式',
@@ -129,15 +131,45 @@ export default {
     area: '需求地区',
     brief: '简介',
     status: '审核状态',
+    industry: '所属产业',
+    company: '所属企业',
+    company_brief: '企业简况',
+    contacts: '联系人',
+    tel: '联系电话',
+    year: '年份',
+    month: '月份',
+    tec_name: '技术需求名称',
+    question: '待解决问题',
     is_use: '是否启用',
     demand_status: '需求状态',
     titleMessage: '请输入需求名称'
   },
+  supply: {
+    addDialogTitle: '新增供给',
+    upDialogTitle: '修改供给',
+    examDialogTitle: '审核供给',
+    name: '供给名称',
+    tags: '标签',
+    field: '行业领域',
+    urgent: '供给紧急度',
+    method: '合作方式',
+    time: '有效期',
+    money: '价格(万元)',
+    area: '供给地区',
+    brief: '简介',
+    industry: '所属产业',
+    source: '项目来源',
+    status: '审核状态',
+    is_use: '是否启用',
+    supply_status: '供给状态',
+    titleMessage: '请输入供给名称'
+  },
   match: {
     addDialogTitle: '新增需赛事',
     upDialogTitle: '修改赛事',
     examDialogTitle: '审核赛事',
     name: '赛事名称',
+    tags: '标签',
     type: '赛事类型',
     work: '组织单位',
     industry: '赛事行业',
@@ -169,6 +201,7 @@ export default {
     upDialogTitle: '修改成果',
     examDialogTitle: '审核成果',
     name: '成果名称',
+    tags: '标签',
     patent: '专利号',
     field: '行业领域',
     type: '类型',
@@ -180,6 +213,9 @@ export default {
     money: '价格(万元)',
     area: '成果地区',
     brief: '简介',
+    source: '项目来源',
+    person: '联系人',
+    tel: '联系电话',
     file: '附件',
     status: '审核状态',
     is_use: '是否启用',
@@ -191,12 +227,18 @@ export default {
     upDialogTitle: '修改项目',
     examDialogTitle: '审核项目',
     name: '项目名称',
+    tags: '标签',
     field: '行业领域',
     type: '行业分类',
     skill: '技术类型',
     maturity: '成熟度',
     cooperate: '合作类型',
     time: '发布时间',
+    main: '项目主体',
+    progress: '项目进展',
+    track_unit: '跟踪支持单位',
+    source: '项目来源',
+    industry: '产业分类',
     brief: '简介',
     status: '审核状态',
     is_use: '是否启用',

+ 0 - 2
src/router/guard.js

@@ -31,14 +31,12 @@ export const registerBeforeRouter = async (router) => {
       const userStore = UserStore()
       const userMenus = toRaw(userStore.menus)
       if (!userMenus || userMenus.length <= 0) {
-        console.log('has token but no menus')
         // 没有目录,说明路由没有注册,需要注册路由,记录当前去哪里再跳转至路由
         next({ path: '/route/loading', query: { redirectPath: getRedirectUri(to) } })
         return
       } else {
         // 路由注册了,直接gogogo
         // 检查有没有redirectPath
-        console.log('continue')
         let redirectPath = get(to, 'query.redirectPath')
         if (redirectPath) next(redirectPath)
         next()

+ 0 - 1
src/router/register.js

@@ -48,7 +48,6 @@ export const addUserRoutes = (menus, router) => {
     // 将默认注册的路由平铺成一维数组
     const routesOneDimensional = toOneDimensional(router.getRoutes())
     routesRegister(menuOneDimensional, routesOneDimensional, router)
-    console.log('route adding complete')
     resolve()
   })
 }

+ 0 - 1
src/utils/axios-wrapper.js

@@ -94,7 +94,6 @@ export class AxiosWrapper {
       }
       const res = await axios.request(requestOptions)
       let returnRes = res.data
-      console.log(returnRes)
       const { errcode, errmsg, details } = returnRes
       if (errcode) {
         console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`)

+ 17 - 12
src/views/account/center/parts/basic.vue

@@ -23,25 +23,25 @@
 </template>
 
 <script setup>
-// import { UserStore } from '@/store/user'
+import { UserStore } from '@/store/user'
 import { RoleStore } from '@/store/api/system/role'
 const roleStore = RoleStore()
-// const userStore = UserStore()
-// const user = computed(() => userStore.user)
-// const router = useRouter()
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const router = useRouter()
 const $checkRes = inject('$checkRes')
 // 加载中
 const loading = ref(false)
 // 表单
 const form = ref({})
 const fields = ref([
-  { label: '昵称', model: 'name' },
+  { label: '昵称', model: 'nick_name' },
   { label: '账号', model: 'account', options: { readonly: true } },
   { label: '角色', model: 'role', custom: true },
   { label: '超级管理员', model: 'is_super', options: { readonly: true }, custom: true }
 ])
 const rules = ref({
-  name: [{ required: true, message: '请输入用户昵称', trigger: 'blur' }],
+  nick_name: [{ required: true, message: '请输入用户昵称', trigger: 'blur' }],
   is_super: [{ required: true, message: '请输入超级管理员', trigger: 'blur' }],
   account: [{ required: true, message: '请输入用户账号', trigger: 'blur' }]
 })
@@ -59,14 +59,19 @@ const searchOther = async () => {
   result = await roleStore.query({ is_use: '0' })
   if ($checkRes(result)) roleList.value = result.data
 }
-const search = async () => {}
+const search = async () => {
+  console.log(user.value);
+  form.value = user.value
+}
 const getRole = (i) => {
-  const arr = []
-  for (const val of i) {
-    const r = this.roleList.find((f) => f.id === val)
-    if (r) arr.push(r.name)
+  if (i && i.length > 0) {
+    const arr = []
+    for (const val of i) {
+      const r = roleList.value.find((f) => f.code === val)
+      if (r) arr.push(r.name)
+    }
+    return arr.join(';')
   }
-  return arr.join(';')
 }
 // 提交保存
 const toSave = async (data) => {

+ 58 - 15
src/views/achievement/index.vue

@@ -11,8 +11,8 @@
         <el-option v-for="i in sellList" :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">
+    <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>
@@ -46,6 +46,11 @@
             <template #file>
               <custom-upload model="file" :list="form.file" :limit="1" url="/files/web/template_achievement/upload" @change="onUpload"></custom-upload>
             </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'">
@@ -69,14 +74,16 @@ const { t } = useI18n()
 // 接口
 import { AchievementStore } from '@/store/api/platform/achievement'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = AchievementStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.achievement.name'), model: 'name', isSearch: true },
-  { label: t('pages.achievement.patent'), model: 'patent', isSearch: true },
-  { label: t('pages.achievement.field'), model: 'field', isSearch: true, type: 'select', format: (i) => getDict(i, 'field') },
+  { label: t('pages.achievement.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.achievement.field'), model: 'field', isSearch: true },
   { label: t('pages.achievement.mature'), model: 'mature', isSearch: true, type: 'select', format: (i) => getDict(i, 'mature') },
   { label: t('pages.achievement.sell'), model: 'sell', isSearch: true, type: 'select', format: (i) => getDict(i, 'sell') },
   { label: t('pages.achievement.money'), model: 'money' },
@@ -90,7 +97,10 @@ const opera = [
   { 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' }]
+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)
@@ -104,12 +114,16 @@ const sellList = ref([])
 const technologyList = ref([])
 const cityList = ref([])
 const achievementList = ref([])
+const tagsList = ref([])
+// 多选列表
+const selectList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
   { label: t('pages.achievement.name'), model: 'name' },
+  { label: t('pages.achievement.tags'), model: 'tags', custom: true },
   { label: t('pages.achievement.patent'), model: 'patent' },
-  { label: t('pages.achievement.field'), model: 'field', type: 'select' },
+  { label: t('pages.achievement.field'), model: 'field' },
   { label: t('pages.achievement.attribute'), model: 'attribute', type: 'select' },
   { label: t('pages.achievement.mature'), model: 'mature', type: 'select' },
   { label: t('pages.achievement.sell'), model: 'sell', type: 'select' },
@@ -117,13 +131,16 @@ const formFields = ref([
   { label: t('pages.achievement.area'), model: 'area', custom: true },
   { label: t('pages.achievement.time'), model: 'time', type: 'date' },
   { label: t('pages.achievement.money'), model: 'money' },
+  { label: t('pages.achievement.source'), model: 'source' },
+  { label: t('pages.achievement.person'), model: 'person' },
+  { label: t('pages.achievement.tel'), model: 'tel' },
   { label: t('pages.achievement.is_use'), model: 'is_use', type: 'radio' },
   { label: t('pages.achievement.brief'), model: 'brief', type: 'textarea' },
   { label: t('pages.achievement.file'), model: 'file', custom: true }
 ])
 const rules = reactive({ name: [{ required: true, message: t('pages.achievement.titleMessage'), trigger: 'blur' }] })
 const dialog = ref({ type: '1', show: false, title: t('pages.achievement.addDialogTitle') })
-const form = ref({})
+const form = ref({ file: [] })
 // 审核
 const examFormFields = [{ label: t('pages.achievement.status'), model: 'status', type: 'select' }]
 const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
@@ -165,6 +182,9 @@ const searchOther = async () => {
   // 成果状态
   result = await dictDataStore.query({ code: 'demandStatus', is_use: '0' })
   if ($checkRes(result)) achievementList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
@@ -176,14 +196,36 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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 == 'mature') res = matureList.value.find((f) => f.value == data)
-  else if (model == 'sell') res = sellList.value.find((f) => f.value == data)
-  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
-  else if (model == 'achievement') res = achievementList.value.find((f) => f.value == data)
-  return get(res, 'label')
+  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 == 'mature') res = matureList.value.find((f) => f.value == data)
+    else if (model == 'sell') res = sellList.value.find((f) => f.value == data)
+    else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+    else if (model == 'achievement') res = achievementList.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 = () => {
@@ -191,6 +233,7 @@ const toAdd = () => {
 }
 // 修改
 const toEdit = (data) => {
+  if (!data.file) data.file = []
   form.value = data
   dialog.value = { type: '1', show: true, title: t('pages.achievement.upDialogTitle') }
 }

+ 66 - 17
src/views/platform/demand/demand/index.vue

@@ -11,8 +11,8 @@
         <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">
+    <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>
@@ -37,6 +37,11 @@
             <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'">
@@ -60,16 +65,19 @@ const { t } = useI18n()
 // 接口
 import { DemandStore } from '@/store/api/platform/demand'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = DemandStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 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.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.demand.field'), model: 'field', isSearch: true },
+  { label: t('pages.demand.industry'), model: 'industry', isSearch: true },
+  { label: t('pages.demand.company'), model: 'company', isSearch: true },
+  { label: t('pages.demand.year'), model: 'year', isSearch: true },
   { 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') }
@@ -79,7 +87,10 @@ const opera = [
   { 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' }]
+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)
@@ -91,14 +102,27 @@ const urgentList = ref([])
 const fieldList = ref([])
 const cityList = ref([])
 const demandList = ref([])
+const tagsList = ref([])
+// 多选列表
+const selectList = 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.tags'), model: 'tags', custom: true },
+  { label: t('pages.demand.field'), model: 'field' },
   { 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.industry'), model: 'industry' },
+  { label: t('pages.demand.company'), model: 'company' },
+  { label: t('pages.demand.company_brief'), model: 'company_brief', type: 'textarea' },
+  { label: t('pages.demand.contacts'), model: 'contacts' },
+  { label: t('pages.demand.tel'), model: 'tel' },
+  { label: t('pages.demand.year'), model: 'year', type: 'year' },
+  { label: t('pages.demand.month'), model: 'month', type: 'month' },
+  { label: t('pages.demand.tec_name'), model: 'tec_name' },
+  { label: t('pages.demand.question'), model: 'question', type: 'textarea' },
   { 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' },
@@ -142,9 +166,12 @@ const searchOther = async () => {
   // 需求状态
   result = await dictDataStore.query({ code: 'demandStatus', is_use: '0' })
   if ($checkRes(result)) demandList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
-  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '1' }
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
   const res = await store.query(info)
   if (res.errcode == '0') {
     data.value = res.data
@@ -153,14 +180,36 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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')
+  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 == '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)
+    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 = () => {

+ 80 - 38
src/views/platform/demand/supply/index.vue

@@ -11,8 +11,8 @@
         <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">
+    <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>
@@ -37,6 +37,11 @@
             <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'">
@@ -60,26 +65,32 @@ const { t } = useI18n()
 // 接口
 import { SupplyStore } from '@/store/api/platform/supply'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = SupplyStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 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') }
+  { label: t('pages.supply.name'), model: 'name', isSearch: true },
+  { label: t('pages.supply.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { label: t('pages.supply.field'), model: 'field', isSearch: true },
+  { label: t('pages.supply.source'), model: 'source', isSearch: true },
+  { label: t('pages.supply.industry'), model: 'industry', isSearch: true },
+  { label: t('pages.supply.money'), model: 'money' },
+  { label: t('pages.supply.is_use'), model: 'is_use', custom: true, format: (i) => getDict(i, 'is_use') },
+  { label: t('pages.supply.supply_status'), model: 'supply_status', format: (i) => getDict(i, 'supply') },
+  { label: t('pages.supply.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' }]
+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)
@@ -90,25 +101,31 @@ const methodList = ref([])
 const urgentList = ref([])
 const fieldList = ref([])
 const cityList = ref([])
-const demandList = ref([])
+const supplyList = ref([])
+const tagsList = ref([])
+// 多选列表
+const selectList = 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' }
+  { label: t('pages.supply.name'), model: 'name' },
+  { label: t('pages.supply.tags'), model: 'tags', custom: true },
+  { label: t('pages.supply.field'), model: 'field' },
+  { label: t('pages.supply.urgent'), model: 'urgent', type: 'select' },
+  { label: t('pages.supply.method'), model: 'method', type: 'select' },
+  { label: t('pages.supply.money'), model: 'money' },
+  { label: t('pages.supply.industry'), model: 'industry' },
+  { label: t('pages.supply.source'), model: 'source' },
+  { label: t('pages.supply.area'), model: 'area', custom: true },
+  { label: t('pages.supply.time'), model: 'time', type: 'daterange' },
+  { label: t('pages.supply.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.supply.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 rules = reactive({ name: [{ required: true, message: t('pages.supply.titleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.supply.addDialogTitle') })
 const form = ref({})
 // 审核
-const examFormFields = [{ label: t('pages.demand.status'), model: 'status', type: 'select' }]
+const examFormFields = [{ label: t('pages.supply.status'), model: 'status', type: 'select' }]
 const examRules = reactive({ status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }] })
 const examForm = ref({})
 // 请求
@@ -140,11 +157,14 @@ const searchOther = async () => {
   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
+  result = await dictDataStore.query({ code: 'supplyStatus', is_use: '0' })
+  if ($checkRes(result)) supplyList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
-  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '0' }
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
   const res = await store.query(info)
   if (res.errcode == '0') {
     data.value = res.data
@@ -153,24 +173,46 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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')
+  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 == '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 == 'supply') res = supplyList.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: '0' }
-  dialog.value = { type: '1', show: true, title: t('pages.demand.addDialogTitle') }
+  dialog.value = { type: '1', show: true, title: t('pages.supply.addDialogTitle') }
 }
 // 修改
 const toEdit = (data) => {
   form.value = data
-  dialog.value = { type: '1', show: true, title: t('pages.demand.upDialogTitle') }
+  dialog.value = { type: '1', show: true, title: t('pages.supply.upDialogTitle') }
 }
 // 删除
 const toDelete = async (data) => {
@@ -193,7 +235,7 @@ const toSave = async () => {
 // 审核
 const toExam = (data) => {
   examForm.value = data
-  dialog.value = { type: '2', show: true, title: t('pages.demand.examDialogTitle') }
+  dialog.value = { type: '2', show: true, title: t('pages.supply.examDialogTitle') }
 }
 // 审核保存
 const toExamSave = async () => {

+ 0 - 1
src/views/home/index.vue

@@ -24,7 +24,6 @@ onMounted(async () => {
 const hasView = (roleCode) => {
   const rc = upperFirst(roleCode)
   const ru = toRaw(user)
-  console.log(ru)
   if (isArray(ru.role) && ru.role.includes(rc)) return true
 }
 </script>

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

@@ -31,6 +31,11 @@
             <template #form>
               <el-option v-for="i in formList" :key="i.id" :label="i.label" :value="i.value"></el-option>
             </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>
             <template #rules>
               <div class="rules">
                 <custom-form v-model="form.rules" :fields="rulesFields" :useSave="false">
@@ -100,12 +105,15 @@ const router = useRouter()
 // 接口
 import { MatchStore } from '@/store/api/platform/match'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = MatchStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.match.name'), model: 'name', isSearch: true },
+  { label: t('pages.match.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
   {
     label: t('pages.match.type'),
     model: 'type',
@@ -160,10 +168,12 @@ const typeList = ref([])
 const matchList = ref([])
 const formList = ref([])
 const industryList = ref([])
+const tagsList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
   { 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' },
   { label: t('pages.match.work'), model: 'work' },
   { label: t('pages.match.industry'), model: 'industry', type: 'select' },
@@ -228,6 +238,9 @@ const searchOther = async () => {
   // 赛事行业
   result = await dictDataStore.query({ code: 'matchIndustry', is_use: '0' })
   if ($checkRes(result)) industryList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
@@ -239,13 +252,16 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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 == 'industry') res = industryList.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')
+  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 == 'industry') res = industryList.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)
+    else if (model == 'tags') return data.join(',')
+    return get(res, 'label')
+  }
 }
 // 添加
 const toAdd = () => {

+ 27 - 7
src/views/platform/news/index.vue

@@ -4,8 +4,8 @@
     <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>
+        <el-tag v-if="row.is_use == '0'" type="success" @click="toUse(row, '1')">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">{{ $t('common.is_use_disabled') }}</el-tag>
       </template>
     </custom-table>
     <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
@@ -18,6 +18,11 @@
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
             </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>
             <template #content>
               <WangEditor v-model="form.content" />
             </template>
@@ -46,12 +51,15 @@ const { t } = useI18n()
 // 接口
 import { NewsStore } from '@/store/api/platform/news'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = NewsStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true , format: (i) => getDict(i, 'tags')},
   { 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' },
@@ -80,18 +88,21 @@ let limit = inject('limit')
 const total = ref(0)
 const isUseList = ref([])
 const statusList = ref([])
+const tagsList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([])
 const formFieldsForCreate = [
   { label: t('pages.news.logo'), model: 'logo', custom: true },
   { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
   { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
   { label: t('pages.news.content'), model: 'content', custom: true }
 ]
 const formFieldsForUpdate = [
   { label: t('pages.news.logo'), model: 'logo', custom: true },
   { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
   { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
   { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
   { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
@@ -125,6 +136,9 @@ const searchOther = async () => {
   // 状态
   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
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '1' }
@@ -136,21 +150,25 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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')
+  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 toAdd = () => {
   formFields.value = cloneDeep(formFieldsForCreate)
-  form.value = { type: '1' }
+  form.value = { type: '1', logo: [] }
   dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
 }
 // 修改
 const toEdit = (data) => {
   formFields.value = cloneDeep(formFieldsForUpdate)
   form.value = data
+  if (!form.value.logo) form.value.logo = []
   dialog.value = { type: '1', show: true, title: t('pages.news.upDialogTitle') }
 }
 // 删除
@@ -160,11 +178,13 @@ const toDelete = async (data) => {
     search({ skip: 0, limit })
   }
 }
+
 // 上传图片
 const onUpload = (e) => {
   const { model, value } = e
   form.value[model] = value
 }
+
 const toSave = async () => {
   const data = cloneDeep(form.value)
   const other = { time: moment().format('YYYY-MM-DD'), person: user.value.nick_name, status: '0' }

+ 21 - 4
src/views/platform/policy/index.vue

@@ -18,6 +18,11 @@
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
             </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>
             <template #content>
               <WangEditor v-model="form.content" />
             </template>
@@ -46,12 +51,15 @@ const { t } = useI18n()
 // 接口
 import { NewsStore } from '@/store/api/platform/news'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = NewsStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
   { 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' },
@@ -80,18 +88,21 @@ let limit = inject('limit')
 const total = ref(0)
 const isUseList = ref([])
 const statusList = ref([])
+const tagsList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([])
 const formFieldsForCreate = [
   { label: t('pages.news.logo'), model: 'logo', custom: true },
   { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
   { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
   { label: t('pages.news.content'), model: 'content', custom: true }
 ]
 const formFieldsForUpdate = [
   { label: t('pages.news.logo'), model: 'logo', custom: true },
   { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
   { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
   { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
   { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
@@ -125,6 +136,9 @@ const searchOther = async () => {
   // 状态
   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
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '0' }
@@ -136,10 +150,13 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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')
+  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 toAdd = () => {

+ 238 - 0
src/views/platform/trends/index.vue

@@ -0,0 +1,238 @@
+<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"></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')">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info" @click="toUse(row, '0')">{{ $t('common.is_use_disabled') }}</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 #logo>
+              <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/template_news/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </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>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </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 moment from 'moment'
+import { UserStore } from '@/store/user'
+import { cloneDeep, get } from 'lodash-es'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+// 接口
+import { NewsStore } from '@/store/api/platform/news'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: t('pages.news.title'), model: 'title', isSearch: true },
+  { label: t('pages.news.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
+  { 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, '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.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' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const isUseList = ref([])
+const statusList = ref([])
+const tagsList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.news.logo'), model: 'logo', custom: true },
+  { label: t('pages.news.title'), model: 'title' },
+  { label: t('pages.news.tags'), model: 'tags', custom: true },
+  { label: t('pages.news.person'), model: 'person', options: { disabled: true } },
+  { label: t('pages.news.time'), model: 'time', type: 'date', options: { disabled: true } },
+  { label: t('pages.news.number'), model: 'number', options: { disabled: true } },
+  { label: t('pages.news.is_use'), model: 'is_use', type: 'radio' },
+  { label: t('pages.news.content'), model: 'content', custom: true }
+]
+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({
+  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
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+}
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value, type: '2' }
+  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 toAdd = () => {
+  formFields.value = cloneDeep(formFieldsForCreate)
+  form.value = { type: '2', logo: [] }
+  dialog.value = { type: '1', show: true, title: t('pages.news.addDialogTitle') }
+}
+// 修改
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  if (!form.value.logo) form.value.logo = []
+  dialog.value = { type: '1', show: true, title: t('pages.news.upDialogTitle') }
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  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 })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    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) => {
+  ElMessageBox.confirm(`确定修改【${data.title}】数据?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      let res = await store.update({ id: get(data, 'id'), is_use })
+      if ($checkRes(res, true)) {
+        search({ skip: 0, 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>

+ 30 - 9
src/views/project/index.vue

@@ -28,6 +28,11 @@
       <el-row>
         <el-col :span="24" v-if="dialog.type == '1'">
           <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <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>
             <template #is_use>
               <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
             </template>
@@ -72,12 +77,15 @@ const { t } = useI18n()
 // 接口
 import { ProjectStore } from '@/store/api/platform/project'
 import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
 const store = ProjectStore()
 const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
   { label: t('pages.project.name'), model: 'name', isSearch: true },
+  { label: t('pages.project.tags'), model: 'tags', isSearch: true, format: (i) => getDict(i, 'tags') },
   { label: t('pages.project.type'), model: 'type', isSearch: true, type: 'select', format: (i) => getDict(i, 'type') },
   { label: t('pages.project.field'), model: 'field', isSearch: true, type: 'select', format: (i) => getDict(i, 'field') },
   { label: t('pages.project.maturity'), model: 'maturity', isSearch: true, type: 'select', format: (i) => getDict(i, 'maturity') },
@@ -105,10 +113,12 @@ const maturityList = ref([])
 const skillList = ref([])
 const cityList = ref([])
 const cooperateList = ref([])
+const tagsList = ref([])
 // 加载中
 const loading = ref(false)
 const formFields = ref([
   { label: t('pages.project.name'), model: 'name' },
+  { label: t('pages.project.tags'), model: 'tags', custom: true },
   { label: t('pages.project.type'), model: 'type', type: 'select' },
   { label: t('pages.project.maturity'), model: 'maturity', type: 'select' },
   { label: t('pages.project.skill'), model: 'skill', type: 'select' },
@@ -116,6 +126,11 @@ const formFields = ref([
   { label: t('pages.project.cooperate'), model: 'cooperate', type: 'select' },
   { label: t('pages.demand.area'), model: 'area', custom: true },
   { label: t('pages.project.time'), model: 'time', type: 'date' },
+  { label: t('pages.project.main'), model: 'main' },
+  { label: t('pages.project.progress'), model: 'progress' },
+  { label: t('pages.project.track_unit'), model: 'track_unit' },
+  { label: t('pages.project.source'), model: 'source' },
+  { label: t('pages.project.industry'), model: 'industry' },
   { label: t('pages.project.is_use'), model: 'is_use', type: 'radio' },
   { label: t('pages.project.brief'), model: 'brief', type: 'textarea' }
 ])
@@ -160,6 +175,9 @@ const searchOther = async () => {
   // 合作类型
   result = await dictDataStore.query({ code: 'projectType', is_use: '0' })
   if ($checkRes(result)) cooperateList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
 }
 const search = async (query = { skip: 0, limit }) => {
   const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
@@ -171,15 +189,18 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 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 == 'skill') res = skillList.value.find((f) => f.value == data)
-  else if (model == 'cooperate') res = cooperateList.value.find((f) => f.value == data)
-  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
-  else if (model == 'maturity') res = maturityList.value.find((f) => f.value == data)
-  else if (model == 'type') res = typeList.value.find((f) => f.value == data)
-  return get(res, 'label')
+  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 == 'skill') res = skillList.value.find((f) => f.value == data)
+    else if (model == 'cooperate') res = cooperateList.value.find((f) => f.value == data)
+    else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+    else if (model == 'maturity') res = maturityList.value.find((f) => f.value == data)
+    else if (model == 'type') res = typeList.value.find((f) => f.value == data)
+    else if (model == 'tags') return data.join(',')
+    return get(res, 'label')
+  }
 }
 // 添加
 const toAdd = () => {

+ 8 - 18
src/views/system/design/index.vue

@@ -11,19 +11,16 @@
               <WangEditor v-model="form.brief" />
             </template>
             <template #logoUrl>
-              <custom-upload model="logoUrl" :list="form.logoUrl" :limit="1" url="/files/web/template_design/upload" @change="onUpload"></custom-upload>
+              <custom-upload model="logoUrl" :list="form.logoUrl" :limit="1" listType="picture-card" url="/files/web/template_design/upload" @change="onUpload"></custom-upload>
             </template>
-            <template #videoUrl>
-              <custom-upload model="videoUrl" :list="form.videoUrl" :limit="1" url="/files/web/template_design/upload" @change="onUpload"></custom-upload>
+            <template #carouselUrl>
+              <custom-upload model="carouselUrl" :list="form.carouselUrl" :limit="6" listType="picture-card" url="/files/web/template_design/upload" @change="onUpload"></custom-upload>
             </template>
             <template #footInfo>
               <div class="footInfo">
                 <custom-form v-model="form.footInfo" :fields="footFields" :rules="footRules" :useSave="false">
                   <template #Code>
-                    <custom-upload model="Code" :list="form.footInfo.Code" :limit="1" url="/files/web/template_design/upload" @change="onFUpload"></custom-upload>
-                  </template>
-                  <template #Unit>
-                    <custom-upload model="Unit" :list="form.footInfo.videoUrl" :limit="1" url="/files/web/template_design/upload" @change="onFUpload"></custom-upload>
+                    <custom-upload model="Code" :list="form.footInfo.Code" listType="picture-card" :limit="1" url="/files/web/template_design/upload" @change="onFUpload"></custom-upload>
                   </template>
                 </custom-form>
               </div>
@@ -46,20 +43,16 @@ const loading = ref(false)
 const form = ref({ footInfo: {} })
 const fields = ref([
   { label: '标题', model: 'zhTitle' },
-  { label: '英文标题', model: 'zhEnglish' },
-  { label: '短简介', model: 'zhBrief' },
   { label: 'logo', model: 'logoUrl', custom: true },
-  { label: '视频', model: 'videoUrl', custom: true },
+  { label: '轮播图', model: 'carouselUrl', custom: true },
   { label: '网站底部信息', model: 'footInfo', custom: true },
   { label: '使用协议', model: 'agreement', custom: true },
   { label: '关于我们', model: 'brief', custom: true }
 ])
 const rules = ref({
   zhTitle: [{ required: true, message: '请输入标题' }],
-  zhEnglish: [{ required: true, message: '请输入英文标题' }],
-  zhBrief: [{ required: true, message: '请输入短简介' }],
   agreement: [{ required: true, message: '请输入使用协议' }],
-  logoUrl: [{ required: true, message: '请上传logo图片' }]
+  logoUrl: [{ required: false, message: '请上传logo图片' }]
 })
 const footFields = ref([
   { label: '联系电话', model: 'Phone' },
@@ -67,8 +60,7 @@ const footFields = ref([
   { label: '地址', model: 'Address' },
   { label: '底部导航', model: 'Copyright' },
   { label: '公司', model: 'Company' },
-  { label: '二维码图片', model: 'Code', custom: true },
-  { label: '事业编图片', model: 'Unit', custom: true }
+  { label: '二维码图片', model: 'Code', custom: true }
 ])
 const footRules = ref({
   Phone: [{ required: true, message: '请输入联系电话' }],
@@ -76,8 +68,7 @@ const footRules = ref({
   Address: [{ required: true, message: '请输入地址' }],
   Copyright: [{ required: true, message: '请输入底部导航' }],
   Company: [{ required: true, message: '请输入公司' }],
-  Code: [{ required: true, message: '请上传二维码图片' }],
-  Unit: [{ required: true, message: '请上传事业编图片' }]
+  Code: [{ required: true, message: '请上传二维码图片' }]
 })
 // 请求
 onMounted(async () => {
@@ -89,7 +80,6 @@ const search = async () => {
   const res = await store.query()
   if (res.errcode == '0') {
     form.value = res.data[0] || { footInfo: {} }
-    console.log(form.value)
   }
 }
 // 提交保存

+ 2 - 2
src/views/system/role/parts/form.vue

@@ -80,7 +80,7 @@ const dealMenu = (list, route_names = []) => {
     if (route_name === 'home') obj.disabled = true
     const nextList = []
     // 先处理该页面配置
-    if (config.length >= 0) {
+    if (config && config.length >= 0) {
       // 如果有配置: 目录,子页面两种情况,都统一处理
       for (const c of config) {
         const codeRouteNameArr = [...thisRouteNameArr]
@@ -90,7 +90,7 @@ const dealMenu = (list, route_names = []) => {
       }
     }
     // 再处理目录/子页面
-    if (children.length > 0) {
+    if (children && children.length > 0) {
       const nextRouteNames = [...thisRouteNameArr]
       const midResult = dealMenu(children, nextRouteNames)
       nextList.push(...midResult)

+ 6 - 4
src/views/system/tags/index.vue

@@ -132,10 +132,12 @@ const search = async (query = { skip: 0, limit }) => {
 }
 // 字典数据转换
 const getDict = (data, model) => {
-  let res
-  if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
-  else if (model == 'type') res = typeList.find((f) => f.value == data)
-  return get(res, 'label')
+  if (data) {
+    let res
+    if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    else if (model == 'type') res = typeList.find((f) => f.value == data)
+    return get(res, 'label')
+  }
 }
 // 添加
 const toAdd = () => {

+ 0 - 2
src/views/user/admin/index.vue

@@ -208,8 +208,6 @@ const getRole = (data) => {
   return list.join(';')
 }
 const getDict = (data) => {
-  console.log(data)
-  console.log(isUseList.value)
   const res = isUseList.value.find((f) => f.value == data)
   return get(res, 'label')
 }

+ 18 - 2
src/views/user/user/index.vue

@@ -225,10 +225,25 @@ const toChang = async (name) => {
     } else if (name == 'State') {
       result = await stateStore.query({ user: form.value.id })
     }
-    if ($checkRes(result)) form.value = result.data[0] || {}
+    if ($checkRes(result)) form.value = result.data[0] || { user: form.value.id }
+  }
+}
+const onSubmit = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (type.value == 'User') res = await store.update(data)
+  else if (type.value == 'Expert') res = await expertStore.update(data)
+  else if (type.value == 'Company') res = await companyStore.update(data)
+  else if (type.value == 'Unit') res = await unitStore.update(data)
+  else if (type.value == 'Association') res = await associationStore.update(data)
+  else if (type.value == 'Competition') res = await competitionStore.update(data)
+  else if (type.value == 'Incubator') res = await incubatorStore.update(data)
+  else if (type.value == 'Investment') res = await investmentStore.update(data)
+  else if (type.value == 'State') res = await stateStore.update(data)
+  if ($checkRes(res, true)) {
+    toChang(type.value)
   }
 }
-
 // 审核
 const toExam = (data) => {
   examForm.value = data
@@ -258,6 +273,7 @@ const toReset = async () => {
 provide('cloneDeep', cloneDeep)
 provide('ruleFormRef ', ruleFormRef)
 provide('form', form)
+provide('onSubmit', onSubmit)
 // 字典
 provide('statusList', statusList)
 provide('genderList', genderList)

+ 7 - 1
src/views/user/user/parts/company.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="index">
-    <el-form ref="ruleFormRef" :model="form" label-width="100px" class="form" label-position="left" disabled>
+    <el-form ref="ruleFormRef" :model="form" label-width="100px" class="form" label-position="left">
       <el-row>
         <el-form-item label="企业Logo" prop="logo">
           <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/template_company/upload" @change="onFUpload" listType="picture-card"></custom-upload>
@@ -96,6 +96,11 @@
           <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
         </el-form-item>
       </el-col>
+      <el-row justify="center" style="text-align: center">
+        <el-col :span="6">
+          <el-button @click="onSubmit" type="primary">保存</el-button>
+        </el-col>
+      </el-row>
     </el-form>
   </div>
 </template>
@@ -108,6 +113,7 @@ const scaleList = inject('scaleList')
 const IndustryList = inject('IndustryList')
 const cityList = inject('cityList')
 const isUseList = inject('isUseList')
+const onSubmit = inject('onSubmit')
 // 上传图片
 const onFUpload = (e) => {
   const { model, value } = e

+ 7 - 1
src/views/user/user/parts/expert.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="index">
-    <el-form ref="ruleFormRef" :model="form" label-width="80px" class="form" label-position="left" disabled>
+    <el-form ref="ruleFormRef" :model="form" label-width="80px" class="form" label-position="left">
       <el-row>
         <el-form-item label="头像" prop="icon">
           <custom-upload model="icon" :list="form.icon" :limit="1" url="/files/web/template_expert/upload" @change="onUpload" listType="picture-card"></custom-upload>
@@ -79,6 +79,11 @@
           <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
         </el-form-item>
       </el-col>
+      <el-row justify="center" style="text-align: center">
+        <el-col :span="6">
+          <el-button @click="onSubmit" type="primary">保存</el-button>
+        </el-col>
+      </el-row>
     </el-form>
   </div>
 </template>
@@ -91,6 +96,7 @@ const educationList = inject('educationList')
 const cityList = inject('cityList')
 const isUseList = inject('isUseList')
 const cardTypeList = inject('cardTypeList')
+const onSubmit = inject('onSubmit')
 // 上传图片
 const onUpload = (e) => {
   const { model, value } = e