lrf 8 kuukautta sitten
vanhempi
commit
b82886ca49
55 muutettua tiedostoa jossa 3624 lisäystä ja 235 poistoa
  1. 2 1
      src/lang/package/zh-cn/common.js
  2. 3 1
      src/lang/package/zh-cn/menus.js
  3. 5 1
      src/lang/package/zh-cn/pages.js
  4. 2 1
      src/layout/parts/sidebar/items.vue
  5. 10 74
      src/router/guard.js
  6. 9 1
      src/router/index.js
  7. 14 0
      src/router/modules/center.js
  8. 14 0
      src/router/modules/journal.js
  9. 14 0
      src/router/modules/log.js
  10. 14 0
      src/router/modules/message.js
  11. 20 19
      src/utils/directives.js
  12. 43 0
      src/views/center/index.vue
  13. 94 0
      src/views/center/parts/basic.vue
  14. 62 0
      src/views/center/parts/password.vue
  15. 19 22
      src/views/information/index.vue
  16. 25 14
      src/views/information/parts/info.vue
  17. 8 0
      src/views/information/parts/info/company.vue
  18. 8 0
      src/views/information/parts/info/expert.vue
  19. 8 0
      src/views/information/parts/info/project.vue
  20. 8 0
      src/views/information/parts/info/sd.vue
  21. 21 6
      src/views/information/parts/news.vue
  22. 22 10
      src/views/information/parts/platform.vue
  23. 26 16
      src/views/information/parts/role.vue
  24. 223 0
      src/views/journal/directory.vue
  25. 215 0
      src/views/journal/index.vue
  26. 242 0
      src/views/journal/notes.vue
  27. 9 0
      src/views/log/index.vue
  28. 135 0
      src/views/log/opera/opera.vue
  29. 48 0
      src/views/log/opera/parts/dataView.vue
  30. 24 0
      src/views/log/opera/parts/paramsTable.vue
  31. 19 0
      src/views/log/opera/parts/tableView.vue
  32. 146 0
      src/views/message/index.vue
  33. 72 0
      src/views/message/parts/to.vue
  34. 13 0
      src/views/message/parts/view.vue
  35. 16 31
      src/views/platform/index.vue
  36. 1 1
      src/views/platform/parts/dept.vue
  37. 85 0
      src/views/platform/parts/import.vue
  38. 1 1
      src/views/platform/parts/role/table.vue
  39. 15 21
      src/views/system/index.vue
  40. 1 1
      src/views/system/parts/admin-menus.vue
  41. 2 2
      src/views/system/parts/admin-menus/menu-table.vue
  42. 15 8
      src/views/system/parts/admin-menus/parts/info.vue
  43. 55 4
      src/views/user/index.vue
  44. 226 0
      src/views/user/parts/admin.vue
  45. 348 0
      src/views/user/parts/user.vue
  46. 63 0
      src/views/user/parts/user/association.vue
  47. 336 0
      src/views/user/parts/user/company.vue
  48. 71 0
      src/views/user/parts/user/competition.vue
  49. 114 0
      src/views/user/parts/user/expert.vue
  50. 353 0
      src/views/user/parts/user/incubator.vue
  51. 81 0
      src/views/user/parts/user/investment.vue
  52. 63 0
      src/views/user/parts/user/school.vue
  53. 72 0
      src/views/user/parts/user/state.vue
  54. 63 0
      src/views/user/parts/user/unit.vue
  55. 46 0
      src/views/user/parts/user/user.vue

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

@@ -37,5 +37,6 @@ export default {
   upload_btn: '选择文件',
   has_error: '发生错误',
   agree: '通过',
-  refuse: '拒绝'
+  refuse: '拒绝',
+  null: '暂无'
 }

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

@@ -7,6 +7,9 @@ export default {
   board: '数据看板',
   center: '个人中心',
   password: '修改密码',
+  log: '日志管理',
+  message: '站内消息',
+  journal: '行研产研'
 
   // system_menus: '菜单设置',
   // system_userMenus: '用户目录',
@@ -46,7 +49,6 @@ export default {
   // incubator: '全省孵化基地情况',
   // elevenHatch: '孵化基地情况',
   // import: '数据导入',
-  // log: '日志管理',
   // log_opera: '操作日志',
   // system_message: '站内消息'
 }

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

@@ -46,6 +46,7 @@ export default {
     account: '账号',
     nick_name: '名称',
     role: '角色',
+    dept: '所属部门',
     is_super: '是否是超级管理员',
     is_use: '是否启用',
     bind: '绑定用户',
@@ -53,7 +54,10 @@ export default {
     rpConfirm: '您确定要重置密码?',
     changeToAbled: '您确定要启用该用户?',
     changeToDisabled: '您确定要禁用该用户?',
-    password: '密码'
+    password: '密码',
+    confirmPassword: '新密码为: ',
+    confirmPasswordTitle: '请确认',
+    dialogTitle: '管理员信息'
   },
   user: {
     dialogTitle: '用户信息',

+ 2 - 1
src/layout/parts/sidebar/items.vue

@@ -1,6 +1,7 @@
 <template>
   <template v-for="item in items" :key="item.id">
-    <template v-if="item.type === '0'">
+    <!-- 目录和子页面不显示,且只显示1级 -->
+    <template v-if="item.type === '1'">
       <el-menu-item :index="item.path" :key="item.path">
         <component class="icon" :is="item.icon"></component>
         <span>{{ $t(item.name) }}</span>

+ 10 - 74
src/router/guard.js

@@ -2,8 +2,8 @@ import { get } from 'lodash-es'
 import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
 import { UserStore } from '@/store/user'
-
-const whiteList = ['/redirect', '/login', '/401', '/404', '/route/loading', '/test']
+import { ElMessage } from 'element-plus'
+const whiteList = ['/redirect', '/login', '/401', '/404', '/test']
 NProgress.configure({ showSpinner: false }) // 进度条
 const dontRedirectList = ['/login', '/', '/401', '/404']
 const getRedirectUri = (route) => {
@@ -24,88 +24,24 @@ export const registerBeforeRouter = async (router) => {
     }
     if (!token) {
       // 没有登录信息,返回login,并记录当前路由
-      console.log('no token & not in whiteList')
       next({ path: '/login', query: { redirectPath: getRedirectUri(to) } })
       return
     } else {
       // token兑换信息
       const userStore = UserStore()
       const result = await userStore.tokenView(token)
+      let menus
       if (result.errcode === 0) {
         userStore.setUser(result.data)
-        const menus = [
-          {
-            id: 1,
-            name: '首页',
-            order_num: 1,
-            path: '/',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-          {
-            id: 2,
-            name: '系统管理',
-            order_num: 2,
-            path: '/system/index',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-          {
-            id: 3,
-            name: '平台设置',
-            order_num: 3,
-            path: '/platform/index',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-          {
-            id: 4,
-            name: '信息管理',
-            order_num: 4,
-            path: '/information/index',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-          {
-            id: 5,
-            name: '用户管理',
-            order_num: 1,
-            path: '/user/index',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-          {
-            id: 6,
-            name: '数据看板',
-            order_num: 6,
-            path: '/board/index',
-            type: '0',
-            is_default: '0',
-            is_use: '0'
-          },
-        ]
+        menus = get(result, 'data.menus', [])
         userStore.setMenus(menus)
       }
-      next()
-      // const userStore = UserStore()
-      // const userMenus = toRaw(userStore.menus)
-      // if (!userMenus || userMenus.length <= 0) {
-      //   // 没有目录,说明路由没有注册,需要注册路由,记录当前去哪里再跳转至路由
-      //   next({ path: '/route/loading', query: { redirectPath: getRedirectUri(to) } })
-      //   return
-      // } else {
-      //   // 路由注册了,直接gogogo
-      //   // 检查有没有redirectPath
-      //   let redirectPath = get(to, 'query.redirectPath')
-      //   if (redirectPath) next(redirectPath)
-      //   next()
-      //   return
-      // }
+      const hasMenu = menus.find((f) => f.path === to.path)
+      if (hasMenu) next()
+      else {
+        ElMessage.error('您没有进入该页面的权限!')
+        next('/')
+      }
     }
   })
 }

+ 9 - 1
src/router/index.js

@@ -5,6 +5,10 @@ import { routes as platform_routes } from './modules/platform'
 import { routes as information_routes } from './modules/information'
 import { routes as user_routes } from './modules/user'
 import { routes as board_routes } from './modules/board'
+import { routes as log_routes } from './modules/log'
+import { routes as message_routes } from './modules/message'
+import { routes as journal_routes } from './modules/journal'
+import { routes as center_routes } from './modules/center'
 export const homeIndex = () => import('@/views/home/index.vue')
 export const Layout = () => import('@/layout/index.vue')
 
@@ -63,7 +67,11 @@ export const constantRoutes = [
       ...platform_routes,
       ...information_routes,
       ...user_routes,
-      ...board_routes
+      ...board_routes,
+      ...log_routes,
+      ...message_routes,
+      ...journal_routes,
+      ...center_routes,
     ]
   }
 ]

+ 14 - 0
src/router/modules/center.js

@@ -0,0 +1,14 @@
+import i18n from '@/lang'
+export const routes = [
+  {
+    path: '/center',
+    name: 'center_index',
+    meta: {
+      title: i18n.global.t('menus.center'),
+      affix: true,
+      keepAlive: true,
+      alwaysShow: false
+    },
+    component: () => import('@/views/center/index.vue')
+  }
+]

+ 14 - 0
src/router/modules/journal.js

@@ -0,0 +1,14 @@
+import i18n from '@/lang'
+export const routes = [
+  {
+    path: '/journal/index',
+    name: 'journal_index',
+    meta: {
+      title: i18n.global.t('menus.journal'),
+      affix: true,
+      keepAlive: true,
+      alwaysShow: false
+    },
+    component: () => import('@/views/journal/index.vue')
+  }
+]

+ 14 - 0
src/router/modules/log.js

@@ -0,0 +1,14 @@
+import i18n from '@/lang'
+export const routes = [
+  {
+    path: '/log/index',
+    name: 'log_index',
+    meta: {
+      title: i18n.global.t('menus.log'),
+      affix: true,
+      keepAlive: true,
+      alwaysShow: false
+    },
+    component: () => import('@/views/log/index.vue')
+  }
+]

+ 14 - 0
src/router/modules/message.js

@@ -0,0 +1,14 @@
+import i18n from '@/lang'
+export const routes = [
+  {
+    path: '/message/index',
+    name: 'message_index',
+    meta: {
+      title: i18n.global.t('menus.message'),
+      affix: true,
+      keepAlive: true,
+      alwaysShow: false
+    },
+    component: () => import('@/views/message/index.vue')
+  }
+]

+ 20 - 19
src/utils/directives.js

@@ -4,25 +4,26 @@ import router from '@/router'
 const InitDirective = (app) => {
   app.directive('method', {
     mounted(el, binding) {
-      const { user } = UserStore()
-      const { value: code } = binding
-      const rUser = toRaw(user)
-      // 超级管理员不进行检查
-      if (get(rUser, 'is_super') === '0') return
-      const roleCode = get(rUser, 'role_code')
-      // 需要判断roleCode中是否有这个权限.但是权限又需要路由拼接起来,最少也得有上层级组合
-      const cr = router.currentRoute.value
-      // 拼接当前路由和层级的name
-      const rArr = cr.matched.filter((f) => f.name !== 'Layout').map((i) => i.name)
-      rArr.push(code)
-      const thisMethodCode = `${rArr.join('.')}`
-      console.log(thisMethodCode)
-      if (!isArray(roleCode)) {
-        el.parentNode.removeChild(el)
-      }
-      if (!roleCode.includes(thisMethodCode)) {
-        el.parentNode.removeChild(el)
-      }
+      // 目前先不用针对元素,针对路由和组件进行控制即可
+      // const { user } = UserStore()
+      // const { value: code } = binding
+      // const rUser = toRaw(user)
+      // // 超级管理员不进行检查
+      // if (get(rUser, 'is_super') === '0') return
+      // const roleCode = get(rUser, 'role_code')
+      // // 需要判断roleCode中是否有这个权限.但是权限又需要路由拼接起来,最少也得有上层级组合
+      // const cr = router.currentRoute.value
+      // // 拼接当前路由和层级的name
+      // const rArr = cr.matched.filter((f) => f.name !== 'Layout').map((i) => i.name)
+      // rArr.push(code)
+      // const thisMethodCode = `${rArr.join('.')}`
+      // console.log(thisMethodCode)
+      // if (!isArray(roleCode)) {
+      //   el.parentNode.removeChild(el)
+      // }
+      // if (!roleCode.includes(thisMethodCode)) {
+      //   el.parentNode.removeChild(el)
+      // }
     }
   })
 }

+ 43 - 0
src/views/center/index.vue

@@ -0,0 +1,43 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-card>
+          <template #header>
+            <div class="card-header">
+              <span>基本信息</span>
+            </div>
+          </template>
+          <el-col :span="24" class="one">
+            <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+              <el-tab-pane label="基本信息" name="first">
+                <basic></basic>
+              </el-tab-pane>
+              <el-tab-pane label="修改密码" name="second">
+                <password></password>
+              </el-tab-pane>
+            </el-tabs>
+          </el-col>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import basic from './parts/basic.vue'
+import password from './parts/password.vue'
+// 加载中
+const loading = ref(false)
+// 请求
+onMounted(async () => {
+  loading.value = true
+  loading.value = false
+})
+const activeName = ref('first')
+
+const handleClick = (tab, event) => {
+  console.log(tab, event)
+}
+</script>
+<style scoped lang="scss"></style>

+ 94 - 0
src/views/center/parts/basic.vue

@@ -0,0 +1,94 @@
+<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">
+          <custom-form v-model="form" :fields="fields" :rules="rules" @save="toSave">
+            <template #is_super="{ item }">
+              <template v-if="item.model === 'is_super'">
+                <el-col :span="24" class="one_1">{{ form.is_super == '0' ? '是' : '否' }}</el-col>
+              </template>
+            </template>
+            <template #role="{ item }">
+              <template v-if="item.model === 'role'">
+                <el-col :span="24" class="one_1" v-if="form.role?.length > 0">{{ getRole(form.role) }}</el-col>
+                <el-col :span="24" class="one_1" v-else>暂无角色</el-col>
+              </template>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+const { t } = useI18n()
+import { get, cloneDeep } from 'lodash-es'
+import { UserStore } from '@/store/user'
+import { RoleStore } from '@/store/api/system/role'
+import { AdminStore } from '@/store/api/user/admin'
+const store = AdminStore()
+const roleStore = RoleStore()
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+const $checkRes = inject('$checkRes')
+// 加载中
+const loading = ref(false)
+// 表单
+const form = ref({})
+const fields = ref([
+  { 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({
+  nick_name: [{ required: true, message: '请输入用户昵称', trigger: 'blur' }],
+  is_super: [{ required: true, message: '请输入超级管理员', trigger: 'blur' }]
+  // account: [{ required: true, message: '请输入用户账号', trigger: 'blur' }]
+})
+const roleList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await roleStore.query({ is_use: '0' })
+  if ($checkRes(result)) roleList.value = result.data
+}
+const search = async () => {
+  const id = get(user, 'value.id')
+  if (!id) return
+  const res = await store.fetch(id)
+  if ($checkRes(res)) {
+    form.value = res.data
+  }
+}
+const getRole = (i) => {
+  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(';')
+  }
+}
+// 提交保存
+const toSave = async (data) => {
+  console.log(data)
+  const obj = {}
+  obj.id = get(data, 'id')
+  obj.nick_name = get(data, 'nick_name')
+  if (!obj.nick_name) return
+  const res = await store.update(obj)
+  $checkRes(res, t('common.opera_success'), t('common.opera_fail'))
+}
+</script>
+<style scoped lang="scss"></style>

+ 62 - 0
src/views/center/parts/password.vue

@@ -0,0 +1,62 @@
+<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">
+          <custom-form v-model="form" :fields="fields" :rules="rules" @save="toSave"></custom-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { UserStore } from '@/store/user'
+import { useTagsViewStore } from '@/store'
+import { LoginStore } from '@/store/api/login'
+const store = LoginStore()
+const userStore = UserStore()
+const tagsViewStore = useTagsViewStore()
+const user = computed(() => userStore.user)
+const router = useRouter()
+const $checkRes = inject('$checkRes')
+// 加载中
+const loading = ref(false)
+// 表单
+const form = ref({})
+const fields = ref([
+  { label: '新密码', model: 'password', type: 'password' },
+  { label: '确认新密码', model: 'ispassword', type: 'password' }
+])
+const rules = ref({
+  password: [{ required: true, message: '请输入新密码' }],
+  ispassword: [
+    { required: true, message: '请输入确认新密码' },
+    {
+      trigger: 'blur',
+      validator: (rule, value, callback) => {
+        if (form.value.password !== value) {
+          callback(new Error('两次输入的密码不一致'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ]
+})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  loading.value = false
+})
+// 提交保存
+const toSave = async (data) => {
+  let res = await store.rp({ id: user.id, password: data.password, type: user.role })
+  if ($checkRes(res, true)) {
+    userStore.logOut()
+    tagsViewStore.delAllViews()
+    router.push('/login')
+  }
+}
+</script>
+<style scoped lang="scss"></style>

+ 19 - 22
src/views/information/index.vue

@@ -14,7 +14,7 @@
           <el-alert title="请在上方选择您要查看的数据" type="warning" effect="dark" :closable="false" />
         </template>
         <template v-else>
-          <component :title="title" @back="toBack" :is="viewComponent"></component>
+          <component :title="title" @back="toBack" :is="viewComponent" :parentName="parentName"></component>
         </template>
       </el-col>
     </el-row>
@@ -33,6 +33,7 @@ const componentList = ref({
 const value = ref()
 const title = ref()
 const viewComponent = ref()
+const parentName = ref()
 const showBar = ref(true)
 /**
  * 子页面设置
@@ -40,28 +41,22 @@ const showBar = ref(true)
  * @param value 子页面id
  * @param component 子页面文件名,需要与代码中的页面对应,上面componentList设置的key
  */
-const options = [
-  {
-    label: '平台数据',
-    value: 1,
-    component: 'platform'
-  },
-  {
-    label: '角色数据',
-    value: 2,
-    component: 'role'
-  },
-  {
-    label: '信息库',
-    value: 3,
-    component: 'info'
-  },
-  {
-    label: '平台新闻',
-    value: 4,
-    component: 'news'
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+const thisRouteConfig = menus.find((f) => f.path === path)
+const children = get(thisRouteConfig, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
   }
-]
+  return obj
+})
+
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return
@@ -73,12 +68,14 @@ const viewChange = (val) => {
   const component = componentList.value[componentName]
   if (!component) return
   viewComponent.value = component
+  parentName.value = componentName
 }
 const toBack = () => {
   showBar.value = true
   value.value = undefined
   title.value = undefined
   viewComponent.value = undefined
+  parentName.value = undefined
 }
 </script>
 <style scoped></style>

+ 25 - 14
src/views/information/parts/info.vue

@@ -27,26 +27,37 @@ import { Back as BackIcon } from '@element-plus/icons-vue'
 import { get } from 'lodash-es'
 import { defineAsyncComponent } from 'vue'
 const props = defineProps({
-  title: { type: String }
+  title: { type: String },
+  parentName: { type: String }
 })
 const emits = defineEmits(['back'])
 const value = ref()
 const viewComponent = ref()
 const componentList = ref({
-  // supply: defineAsyncComponent(() => import('./platform/supply.vue')),
-  // demand: defineAsyncComponent(() => import('./platform/demand.vue')),
-  // achievement: defineAsyncComponent(() => import('./platform/achievement.vue')),
-  // match: defineAsyncComponent(() => import('./platform/match.vue')),
-  // project: defineAsyncComponent(() => import('./platform/project.vue')),
-  // footplate: defineAsyncComponent(() => import('./platform/footplate.vue')),
-  // support: defineAsyncComponent(() => import('./platform/support.vue'))
+  expert: defineAsyncComponent(() => import('./info/expert.vue')),
+  company: defineAsyncComponent(() => import('./info/company.vue')),
+  project: defineAsyncComponent(() => import('./info/project.vue')),
+  sd: defineAsyncComponent(() => import('./info/sd.vue'))
+})
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+// 找页面
+const thisRouteConfig = menus.find((f) => f.path === path)
+const pagesChildren = get(thisRouteConfig, 'children', [])
+// 找子页面中的children
+const thisComponent = pagesChildren.find((f) => f.component === props.parentName)
+const children = get(thisComponent, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
+  }
+  return obj
 })
-const options = [
-  { label: '专家库', value: 1 },
-  { label: '企业库', value: 2 },
-  { label: '项目库', value: 3 },
-  { label: '供需库', value: 4 }
-]
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 8 - 0
src/views/information/parts/info/company.vue

@@ -0,0 +1,8 @@
+<template>
+  <div id="company">
+    <p>company</p>
+  </div>
+</template>
+
+<script setup></script>
+<style scoped></style>

+ 8 - 0
src/views/information/parts/info/expert.vue

@@ -0,0 +1,8 @@
+<template>
+  <div id="expert">
+    <p>expert</p>
+  </div>
+</template>
+
+<script setup></script>
+<style scoped></style>

+ 8 - 0
src/views/information/parts/info/project.vue

@@ -0,0 +1,8 @@
+<template>
+  <div id="project">
+    <p>project</p>
+  </div>
+</template>
+
+<script setup></script>
+<style scoped></style>

+ 8 - 0
src/views/information/parts/info/sd.vue

@@ -0,0 +1,8 @@
+<template>
+  <div id="sd">
+    <p>sd</p>
+  </div>
+</template>
+
+<script setup></script>
+<style scoped></style>

+ 21 - 6
src/views/information/parts/news.vue

@@ -27,7 +27,8 @@ import { Back as BackIcon } from '@element-plus/icons-vue'
 import { get } from 'lodash-es'
 import { defineAsyncComponent } from 'vue'
 const props = defineProps({
-  title: { type: String }
+  title: { type: String },
+  parentName: { type: String }
 })
 const emits = defineEmits(['back'])
 const value = ref()
@@ -37,11 +38,25 @@ const componentList = ref({
   policy: defineAsyncComponent(() => import('./news/policy.vue')),
   trends: defineAsyncComponent(() => import('./news/trends.vue'))
 })
-const options = [
-  { label: '政策信息', value: 1, component: 'policy' },
-  { label: '新闻通知', value: 2, component: 'news' },
-  { label: '行业动态', value: 3, component: 'trends' }
-]
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+// 找页面
+const thisRouteConfig = menus.find((f) => f.path === path)
+const pagesChildren = get(thisRouteConfig, 'children', [])
+// 找子页面中的children
+const thisComponent = pagesChildren.find((f) => f.component === props.parentName)
+const children = get(thisComponent, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
+  }
+  return obj
+})
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 22 - 10
src/views/information/parts/platform.vue

@@ -27,7 +27,8 @@ import { Back as BackIcon } from '@element-plus/icons-vue'
 import { get } from 'lodash-es'
 import { defineAsyncComponent } from 'vue'
 const props = defineProps({
-  title: { type: String }
+  title: { type: String }, // 当前视图选中的上级
+  parentName: { type: String } // 当前视图上级的组件名
 })
 const emits = defineEmits(['back'])
 const value = ref()
@@ -41,15 +42,26 @@ const componentList = ref({
   footplate: defineAsyncComponent(() => import('./platform/footplate.vue')),
   support: defineAsyncComponent(() => import('./platform/support.vue'))
 })
-const options = [
-  { label: '供方信息', value: 1, component: 'supply' },
-  { label: '需求信息', value: 2, component: 'demand' },
-  { label: '技术成果', value: 3, component: 'achievement' },
-  { label: '双创活动', value: 5, component: 'match' },
-  { label: '项目信息', value: 6, component: 'project' },
-  { label: '中试信息', value: 7, component: 'footplate' },
-  { label: '服务支撑', value: 8, component: 'support' }
-]
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+// 找页面
+const thisRouteConfig = menus.find((f) => f.path === path)
+const pagesChildren = get(thisRouteConfig, 'children', [])
+// 找子页面中的children
+const thisComponent = pagesChildren.find((f) => f.component === props.parentName)
+const children = get(thisComponent, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
+  }
+  return obj
+})
+
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 26 - 16
src/views/information/parts/role.vue

@@ -26,6 +26,13 @@
 import { Back as BackIcon } from '@element-plus/icons-vue'
 import { get } from 'lodash-es'
 import { defineAsyncComponent } from 'vue'
+const props = defineProps({
+  title: { type: String },
+  parentName: { type: String } // 当前视图上级的组件名
+})
+const emits = defineEmits(['back'])
+const value = ref()
+const viewComponent = ref()
 const componentList = ref({
   competition: defineAsyncComponent(() => import('./role/competition.vue')),
   investment: defineAsyncComponent(() => import('./role/investment.vue')),
@@ -37,23 +44,26 @@ const componentList = ref({
   unit: defineAsyncComponent(() => import('./role/unit.vue')),
   incubator: defineAsyncComponent(() => import('./role/incubator.vue')),
 })
-const props = defineProps({
-  title: { type: String }
+
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+// 找页面
+const thisRouteConfig = menus.find((f) => f.path === path)
+const pagesChildren = get(thisRouteConfig, 'children', [])
+// 找子页面中的children
+const thisComponent = pagesChildren.find((f) => f.component === props.parentName)
+const children = get(thisComponent, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
+  }
+  return obj
 })
-const emits = defineEmits(['back'])
-const value = ref()
-const viewComponent = ref()
-const options = [
-  { label: '创业大赛', value: 1, component: 'competition' },
-  { label: '投资人', value: 2, component: 'investment' },
-  { label: '商协会', value: 3, component: 'association' },
-  { label: '政府部门', value: 4, component: 'state' },
-  { label: '院校', value: 5, component: 'school' },
-  { label: '企业', value: 6, component: 'company' },
-  { label: '专家', value: 7, component: 'expert' },
-  { label: '科研机构', value: 8, component: 'unit' },
-  { label: '孵化基地', value: 9, component: 'incubator' }
-]
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 223 - 0
src/views/journal/directory.vue

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

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

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

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

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

+ 9 - 0
src/views/log/index.vue

@@ -0,0 +1,9 @@
+<template>
+  <opera-component></opera-component>
+</template>
+
+<script setup>
+import operaComponent from './opera/opera.vue'
+
+</script>
+<style scoped></style>

+ 135 - 0
src/views/log/opera/opera.vue

@@ -0,0 +1,135 @@
+<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-table :data="list" :fields="fields" @query="search" :total="total" :opera="opera" @detail="toDetail" height="75vh"> </custom-table>
+
+    <el-dialog v-model="dialogShow" :title="dialogTitle" :destroy-on-close="false" @close="toClose" :top="dialogTop">
+      <el-form :data="form" label-width="150px" label-position="left">
+        <el-tabs v-model="tabs" type="card">
+          <el-tab-pane :label="$t('pages.log_opera.viewTabs1')" name="1">
+            <el-form-item :label="$t('pages.log_opera.operator_id')">
+              {{ getProp('operator_id') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.operator_name')">
+              {{ getProp('operator_name') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.device')">
+              {{ getProp('device') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.time')">
+              {{ getProp('time') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.referer')">
+              {{ getProp('referer') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.path')">
+              {{ getProp('path') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.opera')">
+              {{ getProp('opera') }}
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.ip')">
+              {{ getProp('ip') }}
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane :label="$t('pages.log_opera.viewTabs2')" name="2">
+            <el-form-item :label="$t('pages.log_opera.params')">
+              <params-table :data="form.params"></params-table>
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.query')">
+              <params-table :data="form.query"></params-table>
+            </el-form-item>
+            <el-form-item :label="$t('pages.log_opera.body')">
+              <params-table :data="form.body"></params-table>
+            </el-form-item>
+          </el-tab-pane>
+          <el-tab-pane :label="$t('pages.log_opera.viewTabs3')" name="3">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-row>
+                  <el-col :span="24">{{ $t('pages.log_opera.origin_data') }}</el-col>
+                  <el-col :span="24">
+                    <data-view :data="form.origin_data"></data-view>
+                  </el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="12">
+                <el-row>
+                  <el-col :span="24">{{ $t('pages.log_opera.new_data') }}</el-col>
+                  <el-col :span="24">
+                    <data-view :data="form.new_data"></data-view>
+                  </el-col>
+                </el-row>
+              </el-col>
+            </el-row>
+          </el-tab-pane>
+        </el-tabs>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import dataView from './parts/dataView.vue'
+import paramsTable from './parts/paramsTable.vue'
+import { OperaLogsStore } from '@/store/api/log/opera'
+import { get } from 'lodash-es'
+import { onMounted } from 'vue'
+const { t } = useI18n()
+const store = OperaLogsStore()
+const dialogShow = ref(false)
+const dialogTitle = t('pages.log_opera.dialogTitle')
+const dialogTop = '15vh'
+const loading = ref(false)
+const $checkRes = inject('$checkRes')
+const searchForm = ref({})
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const tabs = ref('1')
+onMounted(async () => {
+  loading.value = true
+  // await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if ($checkRes(res)) {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+
+const total = ref(0)
+const form = ref({})
+const fields = [
+  { label: t('pages.log_opera.operator_name'), model: 'operator_name' }, // , isSearch: true
+  { label: t('pages.log_opera.opera'), model: 'opera' },
+  { label: t('pages.log_opera.ip'), model: 'ip' },
+  { label: t('pages.log_opera.time'), model: 'time' },
+  { label: t('pages.log_opera.referer'), model: 'referer' },
+  { label: t('pages.log_opera.path'), model: 'path' }
+]
+const opera = [{ label: t('common.detail'), method: 'detail' }]
+
+const toDetail = (data) => {
+  form.value = data
+  dialogShow.value = true
+}
+const getProp = (prop) => {
+  return get(form.value, prop)
+}
+
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialogShow.value = false
+}
+</script>
+<style scoped lang="scss"></style>

+ 48 - 0
src/views/log/opera/parts/dataView.vue

@@ -0,0 +1,48 @@
+<template>
+  <el-collapse v-model="acts" @change="handleChange">
+    <el-collapse-item v-for="i in list" :key="i.title" :title="i.title" :name="i.title">
+      <table-view :data="i.list"></table-view>
+    </el-collapse-item>
+  </el-collapse>
+</template>
+
+<script setup>
+import { isArray } from 'lodash-es'
+import tableView from './tableView.vue'
+const props = defineProps({
+  data: Object
+})
+const { data } = toRefs(props)
+const list = ref([])
+const acts = ref([])
+const setData = () => {
+  const arr = []
+  for (const key in data.value) {
+    let list = data.value[key]
+    if (isArray(list) && list.length > 0) {
+      const nl = []
+      let index = 1
+      for (const i of list) {
+        const ldobj = {}
+        const ldarr = []
+        for (const key in i) {
+          if (key === 'id') ldobj.key = i.id
+          ldarr.push({ key, value: i[key] })
+        }
+        if (!ldobj.key) {
+          ldobj.key = `new data ${index}`
+          index++
+        }
+        ldobj.list = ldarr
+        nl.push(ldobj)
+      }
+      list = nl
+    }
+    const obj = { title: key, list }
+    arr.push(obj)
+  }
+  list.value = arr
+}
+setData()
+</script>
+<style scoped></style>

+ 24 - 0
src/views/log/opera/parts/paramsTable.vue

@@ -0,0 +1,24 @@
+<template>
+  <el-table :data="list">
+    <el-table-column align="center" :label="$t('pages.log_opera.key')" prop="key"></el-table-column>
+    <el-table-column align="center" :label="$t('pages.log_opera.value')" prop="value"></el-table-column>
+  </el-table>
+</template>
+
+<script setup>
+const props = defineProps({
+  data: Object
+})
+const { data } = toRefs(props)
+const list = ref([])
+const setData = () => {
+  const arr = []
+  for (const key in data.value) {
+    const obj = { key, value: data.value[key] }
+    arr.push(obj)
+  }
+  list.value = arr
+}
+setData()
+</script>
+<style scoped></style>

+ 19 - 0
src/views/log/opera/parts/tableView.vue

@@ -0,0 +1,19 @@
+<template>
+  <el-tabs v-model="act" type="card">
+    <el-tab-pane v-for="i in data" :key="i.key" :label="i.key" :name="i.key">
+      <el-table :data="i.list">
+        <el-table-column align="center" label="键名" prop="key"></el-table-column>
+        <el-table-column align="center" label="值" prop="value"></el-table-column>
+      </el-table>
+    </el-tab-pane>
+  </el-tabs>
+</template>
+
+<script setup>
+const act = ref()
+const props = defineProps({
+  data: Array
+})
+const { data } = toRefs(props)
+</script>
+<style scoped></style>

+ 146 - 0
src/views/message/index.vue

@@ -0,0 +1,146 @@
+<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" @create="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @remind="toRemind" @view="toView">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info">{{ $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" :top="dialog.top">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave">
+            <template #type>
+              <el-radio v-for="i in msgTypeList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #to>
+              <to-select v-model="form"></to-select>
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-else="dialog.type == '2'">
+          <to-view-com :data="viewData"></to-view-com>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import toSelect from './parts/to.vue'
+import toViewCom from './parts/view.vue'
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { MessageStore } from '@/store/api/system/message'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = MessageStore()
+const dictDataStore = DictDataStore()
+const data = ref([])
+const searchForm = ref({})
+const viewData = ref([])
+const fields = [
+  { label: t('pages.message.content'), model: 'content' },
+  { label: t('pages.message.type'), model: 'type', format: (i) => getDict(i) }
+]
+const opera = [
+  { label: t('pages.message.view'), method: 'view', type: 'primary' },
+  { label: t('pages.message.remind'), method: 'remind', type: 'primary' }
+]
+const buttonFields = [{ label: t('common.create'), method: 'create' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const isReadList = ref([])
+const msgTypeList = ref([])
+// 加载中
+const loading = ref(false)
+const formFields = [
+  { label: t('pages.message.content'), model: 'content', type: 'textarea', options: { rows: 4 } },
+  { label: t('pages.message.type'), model: 'type', type: 'radio' },
+  { label: t('pages.message.to'), model: 'to', custom: true }
+]
+const rules = reactive({ content: [{ required: true, message: t('pages.message.contentRuleMessage'), trigger: 'blur' }], type: [{ required: true, message: t('pages.message.typeRuleMessage'), trigger: 'blur' }] })
+const dialog = ref({ type: '1', show: false, title: t('pages.message.dialogTitle'), top: '15vh' })
+const form = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const searchOther = async () => {
+  let result = await dictDataStore.query({ code: 'messageIsRead', is_use: '0' })
+  if ($checkRes(result)) isReadList.value = result.data
+  result = await dictDataStore.query({ code: 'messageType', is_use: '0' })
+  if ($checkRes(result)) msgTypeList.value = result.data
+}
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data) => {
+  const res = msgTypeList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+const toRemind = async (data) => {
+  const id = get(data, 'id')
+  if (!id) return
+  const res = await store.remind(id)
+  $checkRes(res, t('pages.message.remindSuccess'))
+}
+
+// 查看发送对象
+const toView = (data) => {
+  let values = get(data, 'to', [])
+  values = values.map((i) => {
+    const is_read = isReadList.value.find((f) => f.value === i.is_read)
+    if (is_read) i.is_read = get(is_read, 'label')
+    return i
+  })
+  viewData.value = values
+  dialog.value = { type: '2', show: true, title: t('pages.message.viewTitle'), top: '15vh' }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: t('pages.message.addDialogTitle'), top: '15vh' }
+  form.value.to = []
+  form.value.type = '0'
+}
+
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, 'id')) res = await store.update(data)
+  else res = await store.create(data)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+// provide
+provide('isReadList', isReadList)
+provide('codeInfo', form)
+</script>
+<style scoped lang="scss"></style>

+ 72 - 0
src/views/message/parts/to.vue

@@ -0,0 +1,72 @@
+<template>
+  <el-row>
+    <el-col :span="24">
+      <el-autocomplete v-model="inputName" :fetch-suggestions="querySearchAsync" :placeholder="$t('pages.message.inputNamePla')" @select="handleSelect">
+        <template #default="{ item }">
+          {{ item.nick_name }}
+        </template>
+      </el-autocomplete>
+    </el-col>
+  </el-row>
+  <el-table :data="form.to">
+    <el-table-column align="center" :label="$t('pages.message.to_user')" prop="user_nick_name"></el-table-column>
+    <el-table-column align="center" :label="$t('common.opera')">
+      <template #="{ row }">
+        <el-button text type="danger" @click="clean(row)">删除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script setup>
+import { UserStore } from '@/store/api/user/user'
+import { ElMessage } from 'element-plus'
+import { get } from 'lodash-es'
+const props = defineProps({
+  modelValue: { type: Object }
+})
+const emits = defineEmits(['update:modelValue'])
+const form = computed({
+  get() {
+    return props.modelValue
+  },
+  set(value) {
+    emits('update:modelValue', value)
+  }
+})
+const $checkRes = inject('$checkRes')
+const { t } = useI18n()
+const inputName = ref()
+const store = UserStore()
+
+const querySearchAsync = async (qs, cb) => {
+  let list = []
+  const result = await store.query({ nick_name: qs })
+  if ($checkRes(result)) list = result.data
+  cb(list)
+}
+
+const handleSelect = (item) => {
+  const obj = {
+    user: get(item, 'id'),
+    user_nick_name: get(item, 'nick_name'),
+    user_type: 'user',
+    is_read: '0'
+  }
+  const value = get(form, 'value.to', [])
+  const has = value.find((f) => f.user === obj.user)
+  if (has) {
+    ElMessage({
+      message: t('pages.message.has_user'),
+      type: 'warning'
+    })
+    return
+  }
+  form.value.to.push(obj)
+}
+const clean = (data) => {
+  const i = form.value.to.findIndex((f) => f.user === data.user)
+  if (i >= 0) form.value.to.splice(i, 1)
+}
+</script>
+<style scoped></style>

+ 13 - 0
src/views/message/parts/view.vue

@@ -0,0 +1,13 @@
+<template>
+  <el-table :data="data" max-height="50vh">
+    <el-table-column align="center" :label="$t('pages.message.to_user')" prop="user_nick_name"></el-table-column>
+    <el-table-column align="center" :label="$t('pages.message.is_read')" prop="is_read"></el-table-column>
+  </el-table>
+</template>
+
+<script setup>
+const props = defineProps({
+  data: { type: Array }
+})
+</script>
+<style scoped></style>

+ 16 - 31
src/views/platform/index.vue

@@ -25,6 +25,7 @@ const componentList = ref({
   tags: defineAsyncComponent(() => import('./parts/tags.vue')),
   sector: defineAsyncComponent(() => import('./parts/sector.vue')),
   friend: defineAsyncComponent(() => import('./parts/friend.vue')),
+  import: defineAsyncComponent(() => import('./parts/import.vue'))
 })
 const value = ref()
 const viewComponent = ref()
@@ -34,38 +35,22 @@ const viewComponent = ref()
  * @param value 子页面id
  * @param component 子页面文件名,需要与代码中的页面对应,上面componentList设置的key
  */
-const options = [
-  {
-    label: '基础设置 ',
-    value: 1,
-    component: 'basic'
-  },
-  {
-    label: '部门管理',
-    value: 2,
-    component: 'dept'
-  },
-  {
-    label: '角色管理',
-    value: 3,
-    component: 'role'
-  },
-  {
-    label: '标签管理',
-    value: 4,
-    component: 'tags'
-  },
-  {
-    label: '产业管理',
-    value: 5,
-    component: 'sector'
-  },
-  {
-    label: '合作伙伴',
-    value: 6,
-    component: 'friend'
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+const thisRouteConfig = menus.find((f) => f.path === path)
+const children = get(thisRouteConfig, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
   }
-]
+  return obj
+})
+
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 1 - 1
src/views/platform/parts/dept.vue

@@ -132,7 +132,7 @@ const toResource = (row) => {
 provide('toResource', toResource)
 const menuList = ref([])
 const searchMenus = async () => {
-  const res = await menuStore.query()
+  const res = await menuStore.query({ is_use: '0' })
   if ($checkRes(res)) {
     menuList.value = res.data
   }

+ 85 - 0
src/views/platform/parts/import.vue

@@ -0,0 +1,85 @@
+<template>
+  <div id="index">
+    <el-row justify="space-around" style="padding: 10px">
+      <el-col :span="12">
+        <el-upload class="button" action="/files/web/cxyy_import/upload" :show-file-list="false" :on-success="onSuccess" accept=".xlsx">
+          <el-button type="primary">选择导入文件</el-button>
+        </el-upload>
+      </el-col>
+      <el-col :span="12" style="text-align: right">
+        <el-button type="primary" @click="toDownload()">下载模板文件</el-button>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="24">
+        <el-table :data="resultList" border>
+          <el-table-column align="center" label="处理内容" prop="key"></el-table-column>
+          <el-table-column align="center" label="处理条数" prop="num">
+            <template #default="{ row }">
+              {{ getText(row) }}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-col>
+    </el-row>
+    <c-dialog :dialog="dialog" @toClose="toClose">
+      <template v-slot:info>
+        <el-col :span="24" class="dialog_one" v-if="dialog.type == '1'">
+          {{ errorList }}
+        </el-col>
+      </template>
+    </c-dialog>
+  </div>
+</template>
+
+<script setup>
+import { get, isNumber } from 'lodash-es'
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+// 加载中
+const loading = ref(false)
+
+const dialog = ref({ title: '信息管理', show: false, type: '1' })
+
+const resultList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  loading.value = false
+})
+// 下载导入模板
+const toDownload = () => {
+  window.open('/cxyyAdmin/导入模板.xlsx')
+}
+const getText = (data) => {
+  const num = get(data, 'num')
+  const errorList = get(data, 'errorList')
+  if (errorList || num === 'error') {
+    return '处理发生错误'
+  } else {
+    if (isNumber(num)) return `成功处理 ${num} 条数据`
+  }
+}
+// 上传Excel
+const onSuccess = async (response, file) => {
+  const msgbox = ElMessage({ message: '正在导入中,请稍后...', center: true, duration: 0 })
+  try {
+    const res = await utilStore.toImport({ url: response.uri })
+    if (res.errcode == '0') {
+      if (res.data[0].errorList) {
+        ElMessageBox.alert(res.data[0].errorList, '错误提示', {
+          confirmButtonText: 'OK'
+        })
+      } else {
+        ElMessage({ message: '导入成功', type: 'success' })
+      }
+      resultList.value = res.data
+    }
+  } catch (error) {
+    console.error(error)
+  } finally {
+    msgbox.close()
+  }
+}
+</script>
+<style scoped lang="scss"></style>

+ 1 - 1
src/views/platform/parts/role/table.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-table :data="data" row-key="id" border height="70vh">
+  <el-table :data="data" row-key="id" border height="60vh">
     <el-table-column align="center" :label="$t('pages.role.name')" prop="name"></el-table-column>
     <el-table-column align="center" :label="$t('pages.role.code')" prop="code"></el-table-column>
     <el-table-column align="center" :label="$t('pages.role.is_use')" prop="is_use">

+ 15 - 21
src/views/system/index.vue

@@ -32,28 +32,22 @@ const viewComponent = ref()
  * @param value 子页面id
  * @param component 子页面文件名,需要与代码中的页面对应,上面componentList设置的key
  */
-const options = [
-  {
-    label: '管理员目录',
-    value: 1,
-    component: 'admin-menus'
-  },
-  {
-    label: '用户目录',
-    value: 2,
-    component: 'user-menus'
-  },
-  {
-    label: '字典管理',
-    value: 3,
-    component: 'dict'
-  },
-  {
-    label: '地区管理',
-    value: 4,
-    component: 'region'
+const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+const thisRouteConfig = menus.find((f) => f.path === path)
+const children = get(thisRouteConfig, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
   }
-]
+  return obj
+})
+
 const viewChange = (val) => {
   const i = options.find((f) => f.value === val)
   if (!i) return

+ 1 - 1
src/views/system/parts/admin-menus.vue

@@ -29,7 +29,7 @@ const dialog = ref(false)
 const list = ref([])
 const form = ref({})
 const typeList = [
-  { label: '目录', value: '0' },
+  // { label: '目录', value: '0' },
   { label: '页面', value: '1' },
   { label: '子页面', value: '2' }
 ]

+ 2 - 2
src/views/system/parts/admin-menus/menu-table.vue

@@ -9,11 +9,11 @@
       </el-table-column>
       <el-table-column align="center" :label="$t('pages.menus.name')" prop="name"></el-table-column>
       <el-table-column align="center" :label="$t('pages.menus.route_name')" prop="route_name"></el-table-column>
-      <el-table-column align="center" :label="$t('pages.menus.i18n_code')" prop="i18n_code"></el-table-column>
+      <!-- <el-table-column align="center" :label="$t('pages.menus.i18n_code')" prop="i18n_code"></el-table-column> -->
       <el-table-column align="center" :label="$t('pages.menus.parentName')" prop="parent_name"></el-table-column>
 
       <el-table-column align="center" :label="$t('pages.menus.path')" prop="path"></el-table-column>
-      <el-table-column align="center" :label="$t('pages.menus.component')" prop="component"></el-table-column>
+      <!-- <el-table-column align="center" :label="$t('pages.menus.component')" prop="component"></el-table-column> -->
       <el-table-column align="center" :label="$t('pages.menus.type')" prop="type" width="100">
         <template #default="{ row }">{{ getType(row) }} </template>
       </el-table-column>

+ 15 - 8
src/views/system/parts/admin-menus/parts/info.vue

@@ -7,18 +7,18 @@
         <el-radio label="1">{{ $t('common.no') }}</el-radio>
       </el-radio-group>
     </el-form-item>
-    <el-form-item :label="$t('pages.menus.name')">
+    <el-form-item :label="$t('pages.menus.name')" required>
       <el-input v-model="form.name" :placeholder="$t('pages.menus.namePh')"></el-input>
     </el-form-item>
     <el-form-item :label="$t('pages.menus.route_name')">
       <el-input v-model="form.route_name" :placeholder="$t('pages.menus.route_namePh')"></el-input>
     </el-form-item>
-    <el-form-item :label="$t('pages.menus.i18n_code')">
+    <!-- <el-form-item :label="$t('pages.menus.i18n_code')">
       <el-input v-model="form.i18n_code" :placeholder="$t('pages.menus.i18n_codePh')"></el-input>
-    </el-form-item>
-    <el-form-item :label="$t('pages.menus.type')">
+    </el-form-item> -->
+    <el-form-item :label="$t('pages.menus.type')" required>
       <el-select v-model="form.type" :placeholder="$t('pages.menus.typePh')">
-        <el-option v-for="(i, index) in typeList" :key="`t${index}`" :label="i.label" :value="i.value"></el-option>
+        <el-option v-for="(i, index) in getTypeList()" :key="`t${index}`" :label="i.label" :value="i.value"></el-option>
       </el-select>
     </el-form-item>
     <el-form-item :label="$t('pages.menus.parentName')">
@@ -30,9 +30,9 @@
       <el-form-item :label="$t('pages.menus.path')">
         <el-input v-model="form.path" :placeholder="$t('pages.menus.pathPh')"></el-input>
       </el-form-item>
-      <el-form-item :label="$t('pages.menus.component')">
+      <!-- <el-form-item :label="$t('pages.menus.component')">
         <el-input v-model="form.component" :placeholder="$t('pages.menus.componentPh')"></el-input>
-      </el-form-item>
+      </el-form-item> -->
     </template>
     <el-form-item :label="$t('pages.menus.order_num')">
       <el-input-number v-model="form.order_num"></el-input-number>
@@ -60,7 +60,7 @@
 </template>
 
 <script setup>
-import { cloneDeep } from 'lodash-es'
+import { cloneDeep, get } from 'lodash-es'
 const menuTree = inject('menuTree')
 const typeList = inject('typeList')
 const form = inject('form')
@@ -87,6 +87,13 @@ const getAllChild = (children) => {
   return arr
 }
 // #endregion
+const getTypeList = () => {
+  let olist = cloneDeep(typeList)
+  let list = []
+  if (get(form, 'value.parent_id')) list = olist.filter((f) => f.value === '2')
+  else list = olist
+  return list
+}
 </script>
 <style scoped lang="scss">
 .icon {

+ 55 - 4
src/views/user/index.vue

@@ -1,8 +1,59 @@
 <template>
-  <div id="index">
-    <p>index</p>
-  </div>
+  <el-row>
+    <el-col :span="24">
+      <el-segmented v-model="value" :options="options" @change="viewChange" block>
+        <template #default="{ item }"> {{ item.label }} </template>
+      </el-segmented>
+      <el-divider />
+      <template v-if="!value">
+        <el-alert title="请在上方选择您要查看的数据" type="warning" effect="dark" :closable="false" />
+      </template>
+      <template v-else>
+        <component :is="viewComponent"></component>
+      </template>
+    </el-col>
+  </el-row>
 </template>
 
-<script setup></script>
+<script setup>
+import { get } from 'lodash-es'
+import { defineAsyncComponent } from 'vue'
+const componentList = ref({
+  admin: defineAsyncComponent(() => import('./parts/admin.vue')),
+  user: defineAsyncComponent(() => import('./parts/user.vue'))
+})
+const value = ref()
+const viewComponent = ref()
+/**
+ * 子页面设置
+ * @param label 子页面名称
+ * @param value 子页面id
+ * @param component 子页面文件名,需要与代码中的页面对应,上面componentList设置的key
+ */
+ const route = useRoute()
+const path = get(route, 'path')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const menus = userStore.menus
+const thisRouteConfig = menus.find((f) => f.path === path)
+const children = get(thisRouteConfig, 'children', [])
+const options = children.map((i) => {
+  const obj = {
+    label: get(i, 'name'),
+    value: get(i, 'id'),
+    component: get(i, 'component')
+  }
+  return obj
+})
+
+const viewChange = (val) => {
+  const i = options.find((f) => f.value === val)
+  if (!i) return
+  const componentName = get(i, 'component')
+  if (!componentName) return
+  const component = componentList.value[componentName]
+  if (!component) return
+  viewComponent.value = component
+}
+</script>
 <style scoped></style>

+ 226 - 0
src/views/user/parts/admin.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="main animate__animated animate__backInRight">
+    <custom-search-bar v-model="searchForm" :fields="fields.filter((f) => f.filter)" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @search="search" :total="total" :opera="opera" @edit="toEdit" @changeUse="toChangeUse" @delete="toDelete" @rp="toResetPwd">
+      <template #is_use="{ row }">
+        <el-tag v-if="row.is_use == '0'" type="success">{{ $t('common.is_use_abled') }}</el-tag>
+        <el-tag v-else type="info">{{ $t('common.is_use_disabled') }}</el-tag>
+      </template>
+    </custom-table>
+    <el-dialog v-model="dialog" :title="$t('pages.admin.dialogTitle')" :destroy-on-close="false" @close="toClose">
+      <custom-form v-model="form" :fields="formFields" @save="toSave">
+        <template #is_use>
+          <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+        </template>
+        <template #role>
+          <el-option v-for="i in roleList" :key="i.id" :label="i.name" :value="i.code"></el-option>
+        </template>
+        <template #dept>
+          <el-tree-select :props="defaultProps" node-key="id" accordion v-model="form.dept" :data="deptList" check-strictly :render-after-expand="false" />
+        </template>
+      </custom-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { DeptStore } from '@/store/api/system/dept'
+import { AdminStore } from '@/store/api/user/admin'
+import { LoginStore } from '@/store/api/login'
+import { RoleStore } from '@/store/api/system/role'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { cloneDeep, get, omit, isArray } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+const store = AdminStore()
+const loginStore = LoginStore()
+const dictDataStore = DictDataStore()
+const roleStore = RoleStore()
+const deptStore = DeptStore()
+const { t } = useI18n()
+const loading = ref(false)
+let skip = 0
+let limit = inject('limit')
+const data = ref([])
+const total = ref(0)
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+
+const fields = [
+  { label: t('pages.admin.account'), model: 'account', filter: true },
+  { label: t('pages.admin.nick_name'), model: 'nick_name' },
+  { label: t('pages.admin.role'), model: 'role', format: (i) => getRole(i) },
+  { label: t('pages.admin.dept'), model: 'dept', format: (i) => getDept(i) },
+  { label: t('pages.admin.is_super'), model: 'is_super', format: (i) => (i === '0' ? t('common.yes') : t('common.no')) },
+  { label: t('pages.admin.is_use'), model: 'is_use', format: (i) => getDict(i), custom: true }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit', display: (i) => i.is_super !== '0' },
+  // { label: t('pages.admin.bind'), method: 'bind' },
+  { label: t('pages.admin.rp'), method: 'rp', type: 'warning', confirm: true, confirmWord: t('pages.admin.rpConfirm') },
+  {
+    label: t('common.is_use_disabled'),
+    method: 'changeUse',
+    type: 'warning',
+    confirm: true,
+    confirmWord: t('pages.admin.changeToDisabled'),
+    display: (i) => i.is_use === '0' && i.is_super !== '0'
+  },
+  {
+    label: t('common.is_use_abled'),
+    method: 'changeUse',
+    type: 'success',
+    confirm: true,
+    confirmWord: t('pages.admin.changeToAbled'),
+    display: (i) => i.is_use === '1' && i.is_super !== '0'
+  },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger', display: (i) => i.is_super !== '0' }
+]
+const buttonFields = [{ label: t('common.create'), method: 'add' }]
+const searchForm = ref({})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+const isUseList = ref([])
+const roleList = ref([])
+const deptList = ref([])
+const defaultProps = {
+  children: 'children',
+  label: 'name'
+}
+const searchOther = async () => {
+  const result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) {
+    isUseList.value = result.data
+  }
+  const roleResult = await roleStore.query({ is_use: '0', is_admin_role: '1' })
+  if ($checkRes(roleResult)) {
+    roleList.value = roleResult.data
+  }
+  const deptResult = await deptStore.query({ status: '0' })
+  if ($checkRes(deptResult)) {
+    deptList.value = deptResult.data
+  }
+}
+
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+const userType = 'Admin'
+const toResetPwd = async (data) => {
+  const res = await loginStore.rpNoNewPassword({ type: userType, id: data.id })
+  if ($checkRes(res, true)) {
+    ElMessageBox.confirm(`${t('pages.admin.confirmPassword')}${res.data}`, t('pages.admin.confirmPassword'), {
+      confirmButtonText: t('common.confirm'),
+      cancelButtonText: t('common.cancel'),
+      type: 'warning'
+    })
+  }
+}
+const toChangeUse = async (data) => {
+  const udata = { id: data.id, is_use: data.is_use === '0' ? '1' : '0' }
+  const res = await store.update(udata)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+const dialog = ref(false)
+const form = ref({})
+const defaultForm = { is_use: '0', role: [] }
+
+const formFields = ref([])
+const formFieldsForCreate = [
+  { label: t('pages.admin.account'), model: 'account' },
+  { label: t('pages.admin.nick_name'), model: 'nick_name' },
+  { label: t('pages.admin.password'), model: 'password', type: 'password' },
+  { label: t('pages.admin.is_use'), model: 'is_use', type: 'radio' }
+]
+const formFieldsForUpdate = [
+  { label: t('pages.admin.account'), model: 'account' },
+  { label: t('pages.admin.nick_name'), model: 'nick_name' },
+  { label: t('pages.admin.is_use'), model: 'is_use', type: 'radio' }
+]
+const notSuperFields = [
+  { label: t('pages.admin.dept'), model: 'dept', custom: true },
+  { label: t('pages.admin.role'), model: 'role', type: 'selectMany' }
+]
+const toAdd = () => {
+  formFields.value = formFieldsForCreate
+  formFields.value.push(...notSuperFields)
+  form.value = cloneDeep(defaultForm)
+  dialog.value = true
+}
+const toEdit = (data) => {
+  formFields.value = cloneDeep(formFieldsForUpdate)
+  form.value = data
+  if (data.is_super !== '0') formFields.value.push(...notSuperFields)
+  dialog.value = true
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, 'id')) res = await store.update(data)
+  else res = await store.create(data)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const getDept = (data) => {
+  const resetList = (list) => {
+    const result = []
+    for (const i of list) {
+      const d = omit(i, ['children'])
+      result.push(d)
+      if (isArray(i.children) && i.children.length > 0) {
+        const ml = resetList(i.children)
+        result.push(...ml)
+      }
+    }
+    return result
+  }
+  const odDeptList = resetList(deptList.value)
+  const result = odDeptList.find((f) => f.id === data)
+  if (!result) return t('common.null')
+  return result.name
+}
+const getRole = (data) => {
+  const rd = toRaw(data)
+  if (!isArray(rd)) return
+  const list = []
+  for (const i of rd) {
+    const res = roleList.value.find((f) => f.code === i)
+    if (res) list.push(get(res, 'name'))
+  }
+
+  return list.join(';')
+}
+const getDict = (data) => {
+  const res = isUseList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+</script>
+<style scoped></style>

+ 348 - 0
src/views/user/parts/user.vue

@@ -0,0 +1,348 @@
+<template>
+  <div class="main animate__animated animate__backInRight">
+    <custom-search-bar v-model="searchForm" :fields="fields.filter((f) => f.filter)" @search="search" @reset="toReset">
+      <template #industry>
+        <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+      </template>
+    </custom-search-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @view="toView" @exam="toExam" @delete="toDelete">
+      <template #role="{ row }">
+        <div class="tags">
+          <el-tag v-for="(item, index) in row.role" :key="index" type="primary">{{ getRole(item) }}</el-tag>
+        </div>
+      </template>
+      <template #is_audit="{ row }">
+        <div class="audit" v-if="row && row.is_audit.length > 0">
+          <div v-for="(item, index) in row.is_audit" :key="index">{{ item }}</div>
+        </div>
+        <div v-else>已通过</div>
+      </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'">
+          <el-tabs v-model="type" type="card" @tab-change="toChang">
+            <el-tab-pane v-for="(item, index) in role" :key="index" :label="getRole(item)" :name="item"></el-tab-pane>
+          </el-tabs>
+          <user v-if="type == 'User'"></user>
+          <expert v-if="type == 'Expert'"></expert>
+          <company v-if="type == 'Company'"></company>
+          <incubator v-if="type == 'Incubator'"></incubator>
+          <competition v-if="type == 'Competition'"></competition>
+          <investment v-if="type == 'Investment'"></investment>
+          <association v-if="type == 'Association'"></association>
+          <school v-if="type == 'School'"></school>
+          <state v-if="type == 'State'"></state>
+          <unit v-if="type == 'Unit'"></unit>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { cloneDeep, get } from 'lodash-es'
+// 组件
+import user from './user/user.vue'
+import association from './user/association.vue'
+import school from './user/school.vue'
+import company from './user/company.vue'
+import competition from './user/competition.vue'
+import expert from './user/expert.vue'
+import incubator from './user/incubator.vue'
+import state from './user/state.vue'
+import unit from './user/unit.vue'
+import investment from './user/investment.vue'
+import { UserStore } from '@/store/api/user/user'
+import { RoleStore } from '@/store/api/system/role'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { RegionStore } from '@/store/api/system/region'
+const regionStore = RegionStore()
+const $checkRes = inject('$checkRes')
+const store = UserStore()
+const dictDataStore = DictDataStore()
+const roleStore = RoleStore()
+// 接口
+import { UnitStore } from '@/store/api/user/unit'
+const unitStore = UnitStore()
+import { ExpertStore } from '@/store/api/user/expert'
+const expertStore = ExpertStore()
+import { AssociationStore } from '@/store/api/user/association'
+const associationStore = AssociationStore()
+import { SchoolStore } from '@/store/api/user/school'
+const schoolStore = SchoolStore()
+import { CompanyStore } from '@/store/api/user/company'
+const companyStore = CompanyStore()
+import { CompetitionStore } from '@/store/api/user/competition'
+const competitionStore = CompetitionStore()
+import { IncubatorStore } from '@/store/api/user/incubator'
+const incubatorStore = IncubatorStore()
+import { InvestmentStore } from '@/store/api/user/investment'
+const investmentStore = InvestmentStore()
+import { StateStore } from '@/store/api/user/state'
+const stateStore = StateStore()
+import { SectorStore } from '@/store/api/system/sector'
+const sectorStore = SectorStore()
+const { t } = useI18n()
+const loading = ref(false)
+let skip = 0
+let limit = inject('limit')
+const data = ref([])
+const total = ref(0)
+const type = ref('User')
+const user_id = ref('')
+const role = ref([])
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const fields = [
+  { label: t('pages.user.openid'), model: 'openid' },
+  { label: t('pages.user.account'), model: 'account', filter: true },
+  { label: t('pages.user.industry'), model: 'industry', filter: true, type: 'select', format: (i) => getDict(i, 'industry') },
+  { label: t('pages.user.nick_name'), model: 'nick_name', filter: true },
+  { label: t('pages.user.phone'), model: 'phone', filter: true },
+  { label: t('pages.user.role'), model: 'role', custom: true },
+  { label: t('pages.user.is_audit'), model: 'is_audit', custom: true }
+  // { label: t('pages.user.status'), model: 'status', format: (i) => getDict(i, 'status') }
+]
+const opera = [
+  { label: t('common.view'), method: 'view' },
+  { label: t('common.exam'), method: 'exam', type: 'warning', display: (i) => i.status === '0' },
+  {
+    label: t('common.delete'),
+    method: 'delete',
+    confirm: true,
+    type: 'danger'
+  }
+]
+const searchForm = ref({})
+const dialog = ref({ type: '1', show: false, title: t('pages.user.dialogTitle') })
+// 查询
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.list(info)
+  if (res.errcode == '0') {
+    data.value = res.data
+    total.value = res.total
+  }
+}
+// 表单验证
+const ruleFormRef = ref()
+const form = ref({})
+// 审核
+const examFormFields = [{ label: t('pages.user.status'), model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: t('common.statusMessage'), trigger: 'blur' }]
+})
+const examForm = ref({})
+// 字典表
+const statusList = ref([])
+const roleList = ref([])
+const genderList = ref([])
+const fieldList = ref([])
+const educationList = ref([])
+const cityList = ref([])
+const isUseList = ref([])
+const patternList = ref([])
+const scaleList = ref([])
+const IndustryList = ref([])
+const cardTypeList = ref([])
+const contributionList = ref([])
+const sectorList = ref([])
+const modeList = ref([])
+
+const searchOther = async () => {
+  let result
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 性别
+  result = await dictDataStore.query({ code: 'gender', is_use: '0' })
+  if ($checkRes(result)) genderList.value = result.data
+  // 角色
+  result = await roleStore.query({ is_use: '0' })
+  if ($checkRes(result)) roleList.value = result.data
+  // 专家领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 企业类型
+  result = await dictDataStore.query({ code: 'companyType', is_use: '0' })
+  if ($checkRes(result)) patternList.value = result.data
+  // 企业规模
+  result = await dictDataStore.query({ code: 'companyScale', is_use: '0' })
+  if ($checkRes(result)) scaleList.value = result.data
+  // 企业所属行业
+  result = await dictDataStore.query({ code: 'companyIndustry', is_use: '0' })
+  if ($checkRes(result)) IndustryList.value = result.data
+  // 学历
+  result = await dictDataStore.query({ code: 'education', is_use: '0' })
+  if ($checkRes(result)) educationList.value = result.data
+  // 证件类型
+  result = await dictDataStore.query({ code: 'cardType', is_use: '0' })
+  if ($checkRes(result)) cardTypeList.value = result.data
+  // 出资方式
+  result = await dictDataStore.query({ code: 'contribution', is_use: '0' })
+  if ($checkRes(result)) contributionList.value = result.data
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 城市
+  result = await regionStore.area({ level: 'province', code: 22 })
+  if ($checkRes(result)) cityList.value = result.data
+  // 角色
+  result = await roleStore.query({ is_use: '0' })
+  if ($checkRes(result)) roleList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+  // 盈利模式
+  result = await dictDataStore.query({ code: 'modeType', is_use: '0' })
+  if ($checkRes(result)) modeList.value = result.data
+}
+
+const toDelete = async (data) => {
+  const res = await store.del(data.id)
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+  }
+}
+const getRole = (data) => {
+  const res = roleList.value.find((f) => f.code === data)
+  return get(res, 'name')
+}
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'industry') {
+      if (Array.isArray(data)) res = { label: data.join(',') }
+      else res = { label: data }
+    }
+    return get(res, 'label')
+  }
+}
+// 查看
+const toView = async (data) => {
+  form.value = data
+  user_id.value = data.id
+  role.value = get(data, 'role')
+  dialog.value = { type: '1', show: true, title: t('pages.user.dialogTitle') }
+}
+// 标签改变
+const toChang = async (name) => {
+  let result
+  if (name == 'User') {
+    result = await store.fetch(user_id.value)
+    if ($checkRes(result)) form.value = result.data
+  } else {
+    if (name == 'Expert') {
+      result = await expertStore.query({ user: user_id.value })
+    } else if (name == 'Company') {
+      result = await companyStore.query({ user: user_id.value })
+    } else if (name == 'Unit') {
+      result = await unitStore.query({ user: user_id.value })
+    } else if (name == 'Association') {
+      result = await associationStore.query({ user: user_id.value })
+    } else if (name == 'School') {
+      result = await schoolStore.query({ user: user_id.value })
+    } else if (name == 'Competition') {
+      result = await competitionStore.query({ user: user_id.value })
+    } else if (name == 'Incubator') {
+      result = await incubatorStore.query({ user: user_id.value })
+    } else if (name == 'Investment') {
+      result = await investmentStore.query({ user: user_id.value })
+    } else if (name == 'State') {
+      result = await stateStore.query({ user: user_id.value })
+    }
+    if ($checkRes(result)) form.value = result.data[0] || {}
+  }
+}
+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 == 'School') res = await schoolStore.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)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: t('pages.user.examDialogTitle') }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toClose = () => {
+  form.value = {}
+  type.value = 'User'
+  role.value = []
+  dialog.value = { show: false }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+// provide
+provide('cloneDeep', cloneDeep)
+provide('ruleFormRef ', ruleFormRef)
+provide('form', form)
+provide('onSubmit', onSubmit)
+// 字典
+provide('sectorList', sectorList)
+provide('statusList', statusList)
+provide('genderList', genderList)
+provide('fieldList', fieldList)
+provide('educationList', educationList)
+provide('cityList', cityList)
+provide('isUseList', isUseList)
+provide('patternList', patternList)
+provide('scaleList', scaleList)
+provide('IndustryList', IndustryList)
+provide('cardTypeList', cardTypeList)
+provide('contributionList', contributionList)
+provide('modeList', modeList)
+// 方法
+provide('getRole', getRole)
+</script>
+<style scoped lang="scss">
+.tags {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  grid-gap: 0.5rem;
+  gap: 0.5rem;
+}
+.audit {
+  color: red;
+}
+</style>

+ 63 - 0
src/views/user/parts/user/association.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="商协会名称" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入商协会名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人姓名" prop="person">
+            <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="负责人电话" prop="person_phone">
+            <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 336 - 0
src/views/user/parts/user/company.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="index">
+    <el-tabs v-model="activeName" type="card" @tab-change="handleClick">
+      <el-tab-pane label="基本信息" name="first">
+        <el-form ref="ruleFormRef" :model="form" 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/cxyy_company/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </el-form-item>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="企业名称" prop="name">
+                <el-input clearable v-model="form.name" placeholder="请输入企业名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="企业类型" prop="pattern">
+                <el-select clearable v-model="form.pattern" placeholder="请选择企业类型">
+                  <el-option v-for="(item, index) in patternList" :key="index" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="企业规模" prop="scale">
+                <el-select clearable v-model="form.scale" placeholder="请选择企业规模">
+                  <el-option v-for="item in scaleList" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="统一信用代码" prop="code">
+                <el-input clearable v-model="form.code" placeholder="请输入统一信用代码" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="所属行业" prop="type">
+                <el-select clearable v-model="form.type" placeholder="请选择所属行业">
+                  <el-option v-for="(item, index) in IndustryList" :key="index" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="所在地区" prop="area">
+                <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" clearable placeholder="请选择所在地区" style="width: 100%" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="法定代表人" prop="representative">
+                <el-input clearable v-model="form.representative" placeholder="请输入法定代表人名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="电子邮箱" prop="email">
+                <el-input clearable v-model="form.email" type="email" placeholder="请输入电子邮箱" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="员工人数" prop="person">
+                <el-input size="large" clearable v-model="form.person" placeholder="请输入员工人数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="注册资本" prop="register">
+                <el-input size="large" clearable v-model="form.register" placeholder="请输入注册资本(万元)" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="硕士研究生人数" prop="graduate_num">
+                <el-input size="large" clearable type="number" v-model="form.graduate_num" placeholder="请输入硕士研究生人数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="博士人数" prop="doctor_num">
+                <el-input size="large" clearable type="number" v-model="form.doctor_num" placeholder="请输入博士人数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="海归人数" prop="returnee_num">
+                <el-input size="large" clearable type="number" v-model="form.returnee_num" placeholder="请输入海归人数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="知识产权数" prop="knowledge">
+                <el-input size="large" clearable type="number" v-model="form.knowledge" placeholder="请输入知识产权数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="发明专利数" prop="patent">
+                <el-input size="large" clearable type="number" v-model="form.patent" placeholder="请输入发明专利数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="实用新型数" prop="utility">
+                <el-input size="large" clearable type="number" v-model="form.utility" placeholder="请输入实用新型数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="软件著作权数" prop="copyright">
+                <el-input size="large" clearable type="number" v-model="form.copyright" placeholder="请输入软件著作权数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="是否为高新技术企业" prop="is_tech">
+                <el-radio-group size="large" v-model="form.is_tech">
+                  <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="是否为专精特新企业" prop="is_new">
+                <el-radio-group size="large" v-model="form.is_new">
+                  <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="企业地址" prop="address">
+                <el-input size="large" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" v-model="form.address" placeholder="请输入企业地址" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="是否公开" prop="is_show">
+                <el-radio-group size="large" v-model="form.is_show">
+                  <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="成立时间" prop="create_time">
+                <el-date-picker size="large" format="YYYY-MM-DD" value-format="YYYY-MM-DD" v-model="form.create_time" type="date" placeholder="请选择成立时间" style="width: 100%" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-col :span="24">
+            <el-form-item label="企业产品" prop="products">
+              <el-input size="large" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" v-model="form.products" placeholder="请输入企业产品" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="简介" prop="brief">
+              <el-input size="large" v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="审核状态" prop="status">
+              <el-select clearable v-model="form.status" placeholder="请选择">
+                <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+              </el-select>
+            </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>
+      </el-tab-pane>
+      <el-tab-pane label="年度信息" name="second" v-if="form && form.id">
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="year" align="center" label="年度" />
+            <el-table-column prop="time" align="center" label="填写时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link :underline="false" type="primary" size="mini" @click="toView(row)" style="margin-right: 10px">查看</el-link>
+                <el-link v-if="row.status == '0'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">审核</el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+  <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="yearForm" :fields="formFields" :rules="yearRules" :useSave="false">
+          <template #is_use>
+            <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+          </template>
+          <template #year>
+            <el-option v-for="i in yearList" :key="i.id" :label="i.label" :value="i.label"></el-option>
+          </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>
+</template>
+<script setup>
+// 基础
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 接口
+import { CompanyYearStore } from '@/store/api/user/companyYear'
+const yearStore = CompanyYearStore()
+const activeName = ref('first')
+// 表单
+const form = inject('form')
+// 字典表
+const patternList = inject('patternList')
+const scaleList = inject('scaleList')
+const IndustryList = inject('IndustryList')
+const cityList = inject('cityList')
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+const yearForm = ref({})
+const dialog = ref({ type: '1', show: false, title: '发布年度信息' })
+const formFields = ref([
+  { label: '年度', model: 'year', type: 'select' },
+  { label: '预计营业收入(万元)', model: 'esincome_money', type: 'number' },
+  { label: '预计利润(万元)', model: 'esprofit_money', type: 'number' },
+  { label: '预计税金(万元)', model: 'estax_money', type: 'number' },
+  { label: '预计研发费用(万元)', model: 'essearch_money', type: 'number' },
+  { label: '是否使用', model: 'is_use', type: 'radio', mark: 'dict', code: 'isUse' }
+])
+const yearRules = reactive({ year: [{ required: true, message: '请选择年度', trigger: 'blur' }] })
+
+// 审核
+const examFormFields = [{ label: '审核状态', model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
+})
+const examForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+const handleClick = async (event) => {
+  if (event == 'second') await searchYear({ skip, limit })
+}
+const searchYear = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    company: form.value.id
+  }
+  if (form.value.id) {
+    const res = await yearStore.query(info)
+    if (res.errcode == '0') {
+      list.value = res.data
+      total.value = res.total
+    }
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 查看
+const toView = (data) => {
+  yearForm.value = data
+  dialog.value = { type: '1', show: true, title: '修改年度信息' }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: '年度信息审核' }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await yearStore.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    searchYear({ skip, limit })
+    toClose()
+  }
+}
+const toClose = () => {
+  yearForm.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss">
+.index {
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 71 - 0
src/views/user/parts/user/competition.vue

@@ -0,0 +1,71 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人姓名" prop="person">
+            <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="负责人电话" prop="person_phone">
+            <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="盈利模式" prop="mode">
+          <el-radio-group size="large" v-model="form.mode">
+            <el-radio v-for="i in modeList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const modeList = inject('modeList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 114 - 0
src/views/user/parts/user/expert.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" 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/cxyy_expert/upload" @change="onUpload" listType="picture-card"></custom-upload>
+        </el-form-item>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="专家姓名" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入专家姓名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="出生年月" prop="birth">
+            <el-date-picker format="YYYY-MM-DD" value-format="YYYY-MM-DD" v-model="form.birth" type="date" placeholder="请选择出生年月" style="width: 100%" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="证件类型" prop="cardType">
+            <el-select clearable v-model="form.cardType" placeholder="请选择证件类型">
+              <el-option v-for="(item, index) in cardTypeList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="证件号码" prop="card">
+            <el-input clearable v-model="form.card" placeholder="请输入证件号码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="擅长领域" prop="field">
+            <el-select clearable v-model="form.field" placeholder="请选择擅长领域">
+              <el-option v-for="(item, index) in fieldList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="研究方向" prop="direction">
+            <el-input clearable v-model="form.direction" placeholder="请输入研究方向" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="最高学历" prop="education">
+            <el-select clearable v-model="form.education" placeholder="请选择最高学历">
+              <el-option v-for="(item, index) in educationList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="所在地区" prop="region">
+            <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" clearable placeholder="请选择所在地区" style="width: 100%" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="工作单位" prop="work">
+            <el-input clearable v-model="form.work" placeholder="请输入工作单位" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const fieldList = inject('fieldList')
+const educationList = inject('educationList')
+const cityList = inject('cityList')
+const isUseList = inject('isUseList')
+const cardTypeList = inject('cardTypeList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+</script>
+<style scoped lang="scss"></style>

+ 353 - 0
src/views/user/parts/user/incubator.vue

@@ -0,0 +1,353 @@
+<template>
+  <div class="index">
+    <el-tabs v-model="activeName" type="card" @tab-change="handleClick">
+      <el-tab-pane label="基本信息" name="first">
+        <el-form ref="ruleFormRef" :model="form" :rules="rules" 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/cxyy_incubator/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </el-form-item>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="孵化基地名称" prop="name">
+                <el-input clearable v-model="form.name" placeholder="请输入孵化基地名称" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="运营单位名称" prop="unit">
+                <el-input clearable v-model="form.unit" placeholder="请输入运营单位名称" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="负责人姓名" prop="person">
+                <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="负责人电话" prop="person_phone">
+                <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="所在地区" prop="area">
+                <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" clearable placeholder="请选择所在地区" style="width: 100%" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="地址" prop="address">
+                <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="占地面积(平方米)" prop="cover_area">
+                <el-input clearable v-model="form.cover_area" placeholder="请输入占地面积(平方米)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="建筑面积(平方米)" prop="build_area">
+                <el-input clearable v-model="form.build_area" placeholder="请输入建筑面积(平方米)" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="剩余面积(平方米)" prop="residue_area">
+                <el-input clearable v-model="form.residue_area" placeholder="请输入剩余面积(平方米)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="房租(元/平方米/月)" prop="rent">
+                <el-input clearable v-model="form.rent" placeholder="请输入房租(元/平方米/月)" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="物业费(元/平方米/月)" prop="wy_money">
+                <el-input clearable v-model="form.wy_money" placeholder="请输入物业费(元/平方米/月)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="是否具备中试场地" prop="is_have">
+                <el-radio-group v-model="form.is_have">
+                  <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="载体运营单位人数" prop="unit_num">
+                <el-input type="number" clearable v-model="form.unit_num" placeholder="请输入载体运营单位人数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="载体运营单位的省级以上导师数" prop="teacher_num">
+                <el-input type="number" clearable v-model="form.teacher_num" placeholder="请输入载体运营单位的省级以上导师数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="中试场地面积(平方米)" prop="site_area">
+                <el-input clearable v-model="form.site_area" placeholder="请输入中试场地面积(平方米)" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="累计参加活动人次" prop="activity_num">
+                <el-input type="number" clearable v-model="form.activity_num" placeholder="请输入累计参加活动人次" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="累计参加活动企业数量" prop="actCompany_num">
+                <el-input type="number" clearable v-model="form.actCompany_num" placeholder="请输入累计参加活动企业数量" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="市级以上活动数" prop="actCity_num">
+                <el-input type="number" clearable v-model="form.actCity_num" placeholder="请输入市级以上活动数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="培训辅导类活动数" prop="actTrain_num">
+                <el-input type="number" clearable v-model="form.actTrain_num" placeholder="请输入培训辅导类活动数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="高校院所类活动数" prop="actSchool_num">
+                <el-input type="number" clearable v-model="form.actSchool_num" placeholder="请输入高校院所类活动数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="投资机构类活动数" prop="actInstitution_num">
+                <el-input type="number" clearable v-model="form.actInstitution_num" placeholder="请输入投资机构类活动数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="中介服务类活动数" prop="actService_num">
+                <el-input type="number" clearable v-model="form.actService_num" placeholder="请输入中介服务类活动数" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="入驻企业数" prop="company_num">
+                <el-input type="number" clearable v-model="form.company_num" placeholder="请输入入驻企业数" />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="是否和平台合作标识" prop="cooperate">
+                <el-radio-group v-model="form.cooperate">
+                  <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-col :span="24">
+            <el-form-item label="基地风采" prop="file">
+              <custom-upload model="file" :list="form.file" :limit="4" url="/files/web/cxyy_incubator/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="简介" prop="brief">
+              <WangEditor v-model="form.brief" />
+            </el-form-item>
+          </el-col>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="是否公开" prop="is_show">
+                <el-radio-group v-model="form.is_show">
+                  <el-radio v-for="i in isUseList" :key="i._id" :label="i.value">{{ i.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="审核状态" prop="status">
+                <el-select clearable v-model="form.status" placeholder="请选择">
+                  <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <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>
+      </el-tab-pane>
+      <el-tab-pane label="年度信息" name="second" v-if="form && form.id">
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="year" align="center" label="年度" />
+            <el-table-column prop="act_num" align="center" label="活动总数" width="180" />
+            <el-table-column prop="time" align="center" label="填写时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link :underline="false" type="primary" size="mini" @click="toView(row)" style="margin-right: 10px">查看</el-link>
+                <el-link v-if="row.status == '0'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">审核</el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+  <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="yearForm" :fields="formFields" :rules="yearRules" :useSave="false">
+          <template #is_use>
+            <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+          </template>
+          <template #year>
+            <el-option v-for="i in yearList" :key="i.id" :label="i.label" :value="i.label"></el-option>
+          </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>
+</template>
+<script setup>
+// 基础
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 接口
+import { IncubatorYearStore } from '@/store/api/user/incubatorYear'
+const yearStore = IncubatorYearStore()
+
+const activeName = ref('first')
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const cityList = inject('cityList')
+const onSubmit = inject('onSubmit')
+
+const yearForm = ref({})
+const dialog = ref({ type: '1', show: false, title: '发布年度信息' })
+const formFields = ref([
+  { label: '年度', model: 'year', type: 'select' },
+  { label: '活动总数', model: 'act_num', type: 'number' },
+  { label: '企业实现收入(万元)', model: 'income_money', type: 'number' },
+  { label: '企业利润(万元)', model: 'profit_money', type: 'number' },
+  { label: '企业税金(万元)', model: 'tax_money', type: 'number' },
+  { label: '企业预计收入(万元)', model: 'esincome_money', type: 'number' },
+  { label: '企业预计利润(万元)', model: 'esprofit_money', type: 'number' },
+  { label: '企业预计税金(万元)', model: 'estax_money', type: 'number' },
+  { label: '获得荣誉情况', model: 'content', type: 'textarea' },
+  { label: '是否使用', model: 'is_use', type: 'radio', mark: 'dict', code: 'isUse' }
+])
+const yearRules = reactive({ year: [{ required: true, message: '请选择年度', trigger: 'blur' }] })
+
+// 审核
+const examFormFields = [{ label: '审核状态', model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
+})
+const examForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+const handleClick = async (event) => {
+  if (event == 'second') await searchYear({ skip, limit })
+}
+const searchYear = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    incubator: form.value.id
+  }
+  if (form.value.id) {
+    const res = await yearStore.query(info)
+    if (res.errcode == '0') {
+      list.value = res.data
+      total.value = res.total
+    }
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    else if (model == 'is_use') res = isUseList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 查看
+const toView = (data) => {
+  yearForm.value = data
+  dialog.value = { type: '1', show: true, title: '修改年度信息' }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: '年度信息审核' }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await yearStore.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    searchYear({ skip, limit })
+    toClose()
+  }
+}
+const toClose = () => {
+  yearForm.value = {}
+  dialog.value = { show: false }
+}
+</script>
+<style scoped lang="scss">
+.index {
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 81 - 0
src/views/user/parts/user/investment.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="姓名" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入姓名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="出资额" prop="money">
+            <el-input clearable v-model="form.money" placeholder="请输入出资额(万元)" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="证件类型" prop="cardType">
+            <el-select clearable v-model="form.cardType" placeholder="请选择证件类型">
+              <el-option v-for="(item, index) in cardTypeList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="证件号码" prop="card">
+            <el-input clearable v-model="form.card" placeholder="请输入证件号码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="出资方式" prop="type">
+            <el-select clearable v-model="form.type" placeholder="请选择出资方式">
+              <el-option v-for="(item, index) in contributionList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const cardTypeList = inject('cardTypeList')
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const contributionList = inject('contributionList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 63 - 0
src/views/user/parts/user/school.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="院校名称" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入院校名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人姓名" prop="person">
+            <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="负责人电话" prop="person_phone">
+            <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 72 - 0
src/views/user/parts/user/state.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人姓名" prop="person">
+            <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="负责人电话" prop="person_phone">
+            <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="部门类型" prop="type">
+            <el-select clearable v-model="form.type" placeholder="请选择部门类型">
+              <el-option v-for="(item, index) in typeList" :key="index" :label="item.label" :value="item.value" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 63 - 0
src/views/user/parts/user/unit.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="index">
+    <el-form ref="ruleFormRef" :model="form" class="form" label-position="left">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="名称" prop="name">
+            <el-input clearable v-model="form.name" placeholder="请输入名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人姓名" prop="person">
+            <el-input clearable v-model="form.person" placeholder="请输入负责人姓名" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="负责人电话" prop="person_phone">
+            <el-input clearable v-model="form.person_phone" placeholder="请输入负责人电话" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="是否公开" prop="is_show">
+            <el-radio-group v-model="form.is_show">
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-col :span="24">
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入地址" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="简介" prop="brief">
+          <el-input v-model="form.brief" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" placeholder="请输入简介" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="审核状态" prop="status">
+          <el-select clearable v-model="form.status" placeholder="请选择">
+            <el-option v-for="(item, index) in statusList" :key="index" :label="item.label" :value="item.value" />
+          </el-select>
+        </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>
+<script setup>
+// 表单
+const form = inject('form')
+// 字典表
+const isUseList = inject('isUseList')
+const statusList = inject('statusList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss"></style>

+ 46 - 0
src/views/user/parts/user/user.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="index">
+    <custom-form v-model="form" :fields="formFields" @save="onSubmit">
+      <template #role>
+        <div class="tags">
+          <el-tag v-for="(item, index) in form.role" :key="index" type="primary">{{ getRole(item) }}</el-tag>
+        </div>
+      </template>
+      <template #industry>
+        <el-checkbox v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title" />
+      </template>
+      <template #gender>
+        <el-option v-for="i in genderList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+      </template>
+    </custom-form>
+  </div>
+</template>
+<script setup>
+// 基础
+const { t } = useI18n()
+const form = inject('form')
+const getRole = inject('getRole')
+const formFields = ref([
+  { label: t('pages.user.openid'), model: 'openid', options: { readonly: true } },
+  { label: t('pages.user.account'), model: 'account', options: { readonly: true } },
+  { label: t('pages.user.nick_name'), model: 'nick_name', options: { readonly: true } },
+  { label: t('pages.user.industry'), model: 'industry', type: 'checkbox' },
+  { label: t('pages.user.gender'), model: 'gender', type: 'select' },
+  { label: t('pages.user.email'), model: 'email', options: { readonly: true } },
+  { label: t('pages.user.phone'), model: 'phone', options: { readonly: true } },
+  { label: t('pages.user.role'), model: 'role', custom: true }
+])
+// 字典表
+const genderList = inject('genderList')
+const sectorList = inject('sectorList')
+const onSubmit = inject('onSubmit')
+</script>
+<style scoped lang="scss">
+.tags {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  grid-gap: 0.5rem;
+  gap: 0.5rem;
+}
+</style>