瀏覽代碼

Merge branch 'main' of http://git.cc-lotus.info/Information/cxyy-admin

lrf 7 月之前
父節點
當前提交
da73fe7cae

+ 11 - 0
package-lock.json

@@ -14,6 +14,7 @@
         "@vueuse/integrations": "^10.9.0",
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "5.1.10",
+        "animate.css": "^4.1.1",
         "axios": "^1.6.7",
         "crypto-js": "^4.2.0",
         "echarts": "^5.5.0",
@@ -1592,6 +1593,11 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/animate.css": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/animate.css/-/animate.css-4.1.1.tgz",
+      "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="
+    },
     "node_modules/ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -7667,6 +7673,11 @@
         "uri-js": "^4.2.2"
       }
     },
+    "animate.css": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/animate.css/-/animate.css-4.1.1.tgz",
+      "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ=="
+    },
     "ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "@vueuse/integrations": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "5.1.10",
+    "animate.css": "^4.1.1",
     "axios": "^1.6.7",
     "crypto-js": "^4.2.0",
     "echarts": "^5.5.0",

二進制
public/images/bg.png


二進制
public/images/line1.png


二進制
public/images/line2.png


+ 1 - 0
src/main.js

@@ -18,6 +18,7 @@ import globalComponents from '@/components'
 import { InitDirective } from './utils/directives'
 // 自动滚动
 import vue3SeamlessScroll from 'vue3-seamless-scroll'
+import 'animate.css/animate.min.css'
 const app = createApp(App)
 globalComponents(app)
 setupStore(app)

+ 25 - 1
src/store/api/util.js

@@ -10,5 +10,29 @@ export const UtilStore = defineStore('util', () => {
     const res = await axios.$post(`/util/toExport`, payload)
     return res
   }
-  return { toImport, toExport }
+  const one = async (payload) => {
+    const res = await axios.$get(`/util/oneStatistics`, payload)
+    return res
+  }
+  const two = async (payload) => {
+    const res = await axios.$get(`/util/twoStatistics`, payload)
+    return res
+  }
+  const thr = async (payload) => {
+    const res = await axios.$get(`/util/thrStatistics`, payload)
+    return res
+  }
+  const four = async (payload) => {
+    const res = await axios.$get(`/util/fourStatistics`, payload)
+    return res
+  }
+  const five = async (payload) => {
+    const res = await axios.$get(`/util/fiveStatistics`, payload)
+    return res
+  }
+  const six = async (payload) => {
+    const res = await axios.$get(`/util/sixStatistics`, payload)
+    return res
+  }
+  return { toImport, toExport, one, two, thr, four, five, six }
 })

+ 61 - 4
src/views/board/index.vue

@@ -1,8 +1,65 @@
 <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({
+  achievement: defineAsyncComponent(() => import('./parts/achievement.vue')),
+  company: defineAsyncComponent(() => import('./parts/company.vue')),
+  supply: defineAsyncComponent(() => import('./parts/supply.vue')),
+  demand: defineAsyncComponent(() => import('./parts/demand.vue')),
+  expert: defineAsyncComponent(() => import('./parts/expert.vue')),
+  incubate: defineAsyncComponent(() => import('./parts/incubate.vue')),
+  project: defineAsyncComponent(() => import('./parts/project.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>

+ 314 - 0
src/views/board/parts/achievement.vue

@@ -0,0 +1,314 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>成果来源</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>行业领域</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>成果列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>成果名称</th>
+                    <th>地区</th>
+                    <th>行业领域</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td class="name">{{ item.name || '暂无成果名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ getArea(item.area) || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.field || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>成果来源排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'achievement')
+import { get } from 'lodash-es'
+// 成果
+import { AchievementStore } from '@/store/api/platform/achievement'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = AchievementStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join('-')
+  else return '暂无地区'
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          .name {
+            max-width: 200px;
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 314 - 0
src/views/board/parts/company.vue

@@ -0,0 +1,314 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>企业类型</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>行业领域</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>企业列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>企业名称</th>
+                    <th>地区</th>
+                    <th>人数</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无企业名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ getArea(item.area) || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.person || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>企业员工数量排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'company')
+import { get } from 'lodash-es'
+// 企业
+import { CompanyStore } from '@/store/api/user/company'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = CompanyStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join('-')
+  else return '暂无地区'
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 314 - 0
src/views/board/parts/demand.vue

@@ -0,0 +1,314 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>需求来源</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>行业领域</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>需求列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>需求名称</th>
+                    <th>地区</th>
+                    <th>行业领域</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无需求名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ getArea(item.area) || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.field || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>需求来源</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'demand')
+import { get } from 'lodash-es'
+// 需求
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = DemandStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join('-')
+  else return '暂无地区'
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 328 - 0
src/views/board/parts/echarts/echarts1.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="echarts">
+    <div ref="echarts1" class="echarts1"></div>
+    <div ref="echarts2" class="echarts1"></div>
+    <div ref="echarts3" class="echarts1"></div>
+    <div ref="echarts4" class="echarts1"></div>
+  </div>
+</template>
+<style scoped lang="scss">
+.echarts {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  flex-wrap: wrap;
+  .echarts1 {
+    height: 15vh;
+    width: 50%;
+  }
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts1 = ref()
+const echarts2 = ref()
+const echarts3 = ref()
+const echarts4 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const list = ref([])
+onMounted(async () => {
+  await search()
+  await echarts1View()
+  await echarts2View()
+  await echarts3View()
+  await echarts4View()
+})
+const search = async () => {
+  let res = await utilStore.one({ type })
+  if (res.errcode == '0') list.value = res.data
+}
+function echarts1View() {
+  const myChart1 = echarts.init(echarts1.value)
+  const option1 = {
+    grid: {
+      left: '0%',
+      top: '20%',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    title: {
+      subtext: list.value[0].name,
+      left: 'center',
+      bottom: 15,
+      subtextStyle: {
+        color: 'rgba(255,255,255,.6)',
+        fontSize: 12
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'gauge',
+        radius: '90%',
+        startAngle: 200,
+        endAngle: -20,
+        detail: { formatter: '{value}', offsetCenter: [0, 1], color: '#fff', fontSize: 16 },
+        data: list.value[0].data,
+        axisLine: {
+          lineStyle: {
+            width: 10,
+            color: [
+              [
+                list.value[0].percentage,
+                new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  {
+                    offset: 0,
+                    color: '#ae3df6'
+                  },
+                  {
+                    offset: 1,
+                    color: '#53bef9'
+                  }
+                ])
+              ],
+              [1, '#1c4e85']
+            ]
+          }
+        },
+        pointer: {
+          show: false, //不显示指针
+          length: '70%',
+          width: 3
+        },
+        axisLabel: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ]
+  }
+  myChart1.setOption(option1)
+  window.addEventListener('resize', function () {
+    myChart1.resize()
+  })
+}
+function echarts2View() {
+  const myChart2 = echarts.init(echarts2.value)
+  const option2 = {
+    grid: {
+      left: '0%',
+      top: '20%',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    title: {
+      subtext: list.value[1].name,
+      left: 'center',
+      bottom: 15,
+      subtextStyle: {
+        color: 'rgba(255,255,255,.6)',
+        fontSize: 12
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'gauge',
+        radius: '90%',
+        startAngle: 200,
+        endAngle: -20,
+        detail: { formatter: '{value}', offsetCenter: [0, 1], color: '#fff', fontSize: 16 },
+        data: list.value[1].data,
+        axisLine: {
+          lineStyle: {
+            width: 10,
+            color: [
+              [
+                list.value[1].percentage,
+                new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  {
+                    offset: 0,
+                    color: '#1db0d2'
+                  },
+                  {
+                    offset: 1,
+                    color: '#44ceef'
+                  }
+                ])
+              ],
+              [1, '#1c4e85']
+            ]
+          }
+        },
+        pointer: {
+          show: false, //不显示指针
+          length: '70%',
+          width: 3
+        },
+        axisLabel: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ]
+  }
+  myChart2.setOption(option2)
+  window.addEventListener('resize', function () {
+    myChart2.resize()
+  })
+}
+function echarts3View() {
+  const myChart3 = echarts.init(echarts3.value)
+  const option3 = {
+    grid: {
+      left: '0%',
+      top: '20%',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    title: {
+      subtext: list.value[2].name,
+      left: 'center',
+      bottom: 15,
+      subtextStyle: {
+        color: 'rgba(255,255,255,.6)',
+        fontSize: 12
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'gauge',
+        radius: '90%',
+        startAngle: 200,
+        endAngle: -20,
+        detail: { formatter: '{value}', offsetCenter: [0, 1], color: '#fff', fontSize: 16 },
+        data: list.value[2].data,
+        axisLine: {
+          lineStyle: {
+            width: 10,
+            color: [
+              [
+                list.value[2].percentage,
+                new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  {
+                    offset: 0,
+                    color: '#1ea899'
+                  },
+                  {
+                    offset: 1,
+                    color: '#29c582'
+                  }
+                ])
+              ],
+              [1, '#1c4e85']
+            ]
+          }
+        },
+        pointer: {
+          show: false, //不显示指针
+          length: '70%',
+          width: 3
+        },
+        axisLabel: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ]
+  }
+  myChart3.setOption(option3)
+  window.addEventListener('resize', function () {
+    myChart3.resize()
+  })
+}
+function echarts4View() {
+  const myChart4 = echarts.init(echarts4.value)
+  const option4 = {
+    grid: {
+      left: '0%',
+      top: '20%',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    title: {
+      subtext: list.value[3].name,
+      left: 'center',
+      bottom: 15,
+      subtextStyle: {
+        color: 'rgba(255,255,255,.6)',
+        fontSize: 12
+      }
+    },
+    series: [
+      {
+        name: '',
+        type: 'gauge',
+        radius: '90%',
+        startAngle: 200,
+        endAngle: -20,
+        detail: { formatter: '{value}', offsetCenter: [0, 1], color: '#fff', fontSize: 16 },
+        data: list.value[3].data,
+        axisLine: {
+          lineStyle: {
+            width: 10,
+            color: [
+              [
+                list.value[3].percentage,
+                new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  //0.8值为颜色显示百分比80%
+                  {
+                    offset: 0,
+                    color: '#e6658f'
+                  },
+                  {
+                    offset: 1,
+                    color: '#f8a58b'
+                  }
+                ])
+              ],
+              [1, '#1c4e85']
+            ]
+          }
+        },
+        pointer: {
+          show: false, //不显示指针
+          length: '70%',
+          width: 3
+        },
+        axisLabel: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ]
+  }
+  myChart4.setOption(option4)
+  window.addEventListener('resize', function () {
+    myChart4.resize()
+  })
+}
+</script>

+ 119 - 0
src/views/board/parts/echarts/echarts2.vue

@@ -0,0 +1,119 @@
+<template>
+  <div ref="echarts2" class="echarts2"></div>
+</template>
+<style scoped lang="scss">
+.echarts2 {
+  height: 30vh;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts2 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const info = ref({})
+onMounted(async () => {
+  await search()
+  await echarts2View()
+})
+const search = async () => {
+  let res = await utilStore.two({ type })
+  if (res.errcode == '0') info.value = res.data
+}
+function echarts2View() {
+  const myChart2 = echarts.init(echarts2.value)
+  const option2 = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    grid: {
+      left: '0%',
+      top: '15px',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    xAxis: {
+      data: info.value.nameList,
+      axisLine: { show: false },
+      axisLabel: {
+        color: '#fff',
+        fontSize: 12
+      }
+    },
+    yAxis: {
+      name: '(人)',
+      nameTextStyle: {
+        color: '#fff',
+        fontSize: 14
+      },
+      axisLine: { show: false },
+      axisLabel: {
+        color: '#fff',
+        fontSize: 12
+      },
+      splitLine: { show: false },
+      interval: 100,
+      max: 500
+    },
+    dataZoom: [
+      {
+        show: true,
+        height: 12,
+        xAxisIndex: [0, 6],
+        bottom: 5,
+        start: 0,
+        end: 80,
+        handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
+        handleSize: '110%',
+        handleStyle: {
+          color: '#d3dee5'
+        },
+        textStyle: {
+          color: '#fff'
+        },
+        borderColor: 'rgba(255,255,255,.3)'
+      }
+    ],
+    series: [
+      {
+        type: 'bar',
+        barWidth: '30%',
+        itemStyle: {
+          normal: {
+            barBorderRadius: 50,
+            color: new echarts.graphic.LinearGradient(
+              0,
+              0,
+              0,
+              1,
+              [
+                {
+                  offset: 0,
+                  color: '#01fdcc'
+                },
+                {
+                  offset: 0.8,
+                  color: '#11a1d8'
+                }
+              ],
+              false
+            )
+          }
+        },
+        data: info.value.list
+      }
+    ]
+  }
+  myChart2.setOption(option2)
+  window.addEventListener('resize', function () {
+    myChart2.resize()
+  })
+}
+</script>

+ 75 - 0
src/views/board/parts/echarts/echarts3.vue

@@ -0,0 +1,75 @@
+<template>
+  <div ref="echarts3" class="echarts3"></div>
+</template>
+<style scoped lang="scss">
+.echarts3 {
+  height: 30vh;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts3 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const info = ref({})
+onMounted(async () => {
+  await search()
+  await echarts3View()
+})
+const search = async () => {
+  let res = await utilStore.thr({ type })
+  if (res.errcode == '0') info.value = res.data
+}
+function echarts3View() {
+  const myChart3 = echarts.init(echarts3.value)
+  const option3 = {
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b}: {c} ({d}%)',
+      position: function (p) {
+        //其中p为当前鼠标的位置
+        return [p[0] + 10, p[1] - 10]
+      }
+    },
+    grid: {
+      left: '20%',
+      top: '0',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    series: [
+      {
+        name: '数据',
+        type: 'pie',
+        radius: ['45%', '70%'],
+        color: ['#1E90FF', '#00FFFF', '#B0E0E6', '#00BFFF'],
+        avoidLabelOverlap: false,
+        label: {
+          alignTo: 'labelLine',
+          lineHeight: 15,
+          edgeDistance: 10,
+          color: '#FFFFFF',
+          fontSize: '14'
+        },
+        emphasis: {
+          label: {
+            show: false
+          }
+        },
+        labelLine: {
+          show: true
+        },
+        data: info.value.list
+      }
+    ]
+  }
+  myChart3.setOption(option3)
+  window.addEventListener('resize', function () {
+    myChart3.resize()
+  })
+}
+</script>

+ 106 - 0
src/views/board/parts/echarts/echarts4.vue

@@ -0,0 +1,106 @@
+<template>
+  <div ref="echarts4" class="echarts4"></div>
+</template>
+<style scoped lang="scss">
+.echarts4 {
+  height: 30vh;
+  width: 100%;
+}
+</style>
+<script setup>
+// 引入jilin.js
+import jilinJson from './json/jilin.json'
+import * as echarts from 'echarts'
+const echarts4 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const data = ref([])
+onMounted(async () => {
+  await search()
+  await echarts4View()
+})
+const search = async () => {
+  let res = await utilStore.four({ type })
+  if (res.errcode == '0') data.value = res.data
+}
+const convertData = (data) => {
+  var res = []
+  let features = jilinJson['features']
+  for (var i = 0; i < data.length; i++) {
+    var geoCoord = features.find((a) => a.properties.name == data[i].name)
+    if (geoCoord && geoCoord.properties && geoCoord.properties.center) {
+      res.push({
+        name: data[i].name,
+        value: geoCoord.properties.center.concat(data[i].value)
+      })
+    }
+  }
+  return res
+}
+function echarts4View() {
+  const myChart4 = echarts.init(echarts4.value)
+  echarts.registerMap('jilin', jilinJson)
+  const option4 = {
+    tooltip: {
+      trigger: 'item'
+    },
+    geo: {
+      map: 'jilin',
+      label: {
+        emphasis: {
+          show: false
+        }
+      },
+      roam: false,
+      zoom: 1.2,
+      itemStyle: {
+        normal: {
+          areaColor: 'rgba(2,37,101,.5)',
+          borderColor: 'rgba(112,187,252,.5)'
+        },
+        emphasis: {
+          areaColor: 'rgba(2,37,101,.8)'
+        }
+      }
+    },
+    series: [
+      {
+        name: '企业分布',
+        type: 'scatter',
+        coordinateSystem: 'geo',
+        data: convertData(data.value),
+        symbolSize: function (val) {
+          if (val[2] > 10) return val[2] / 10
+          else return val[2]
+        },
+        label: {
+          normal: {
+            formatter: '{b}',
+            position: 'right',
+            show: false
+          },
+          emphasis: {
+            show: true
+          }
+        },
+        itemStyle: {
+          normal: {
+            color: '#ffeb7b'
+          }
+        },
+        tooltip: {
+          formatter: function (params) {
+            return params.name + ' : ' + params.value[2] + '个'
+          }
+        }
+      }
+    ]
+  }
+  myChart4.setOption(option4)
+  window.addEventListener('resize', function () {
+    myChart4.resize()
+  })
+}
+</script>

+ 101 - 0
src/views/board/parts/echarts/echarts5.vue

@@ -0,0 +1,101 @@
+<template>
+  <div ref="echarts5" class="echarts5"></div>
+</template>
+<style scoped lang="scss">
+.echarts5 {
+  height: 30vh;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts5 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const info = ref({})
+onMounted(async () => {
+  await search()
+  await echarts5View()
+})
+const search = async () => {
+  let res = await utilStore.five({ type })
+  if (res.errcode == '0') info.value = res.data
+}
+function echarts5View() {
+  const myChart5 = echarts.init(echarts5.value)
+  const option5 = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    grid: {
+      left: '0%',
+      top: '15px',
+      right: '0%',
+      bottom: '0%',
+      containLabel: true
+    },
+    xAxis: {
+      data: info.value.nameList,
+      axisLine: { show: false },
+      axisLabel: {
+        color: '#fff',
+        fontSize: 12
+      }
+    },
+    yAxis: {
+      name: '(人)',
+      nameTextStyle: {
+        color: '#fff',
+        fontSize: 14
+      },
+      axisLine: { show: false },
+      axisLabel: {
+        color: '#fff',
+        fontSize: 12
+      },
+      splitLine: { show: false },
+      interval: 100,
+      max: 500
+    },
+    series: [
+      {
+        type: 'bar',
+        barWidth: '30%',
+
+        itemStyle: {
+          normal: {
+            barBorderRadius: 50,
+            color: new echarts.graphic.LinearGradient(
+              0,
+              0,
+              0,
+              1,
+              [
+                {
+                  offset: 0,
+                  color: '#01fdcc'
+                },
+                {
+                  offset: 0.8,
+                  color: '#11a1d8'
+                }
+              ],
+              false
+            )
+          }
+        },
+        data: info.value.list
+      }
+    ]
+  }
+  myChart5.setOption(option5)
+  window.addEventListener('resize', function () {
+    myChart5.resize()
+  })
+}
+</script>

+ 135 - 0
src/views/board/parts/echarts/echarts6.vue

@@ -0,0 +1,135 @@
+<template>
+  <div ref="echarts6" class="echarts6"></div>
+</template>
+<style scoped lang="scss">
+.echarts6 {
+  height: 30vh;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts6 = ref()
+const type = inject('type')
+// 接口
+import { UtilStore } from '@/store/api/util'
+const utilStore = UtilStore()
+const info = ref({})
+onMounted(async () => {
+  await search()
+  await echarts6View()
+})
+const search = async () => {
+  let res = await utilStore.six({ type })
+  if (res.errcode == '0') info.value = res.data
+}
+function echarts6View() {
+  const myChart6 = echarts.init(echarts6.value)
+  const option6 = {
+    grid: {
+      left: '10',
+      top: '20',
+      right: '30',
+      bottom: '-25',
+      containLabel: true
+    },
+    xAxis: {
+      show: false
+    },
+    yAxis: [
+      {
+        show: true,
+        data: info.value.nameList,
+        inverse: true,
+        axisLine: {
+          show: false
+        },
+
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          textStyle: {
+            color: 'rgba(255,255,255,.6)'
+          },
+          formatter: function (value) {
+            return ['{title|' + value + '} '].join('\n')
+          },
+          rich: {}
+        }
+      },
+      {
+        show: true,
+        inverse: true,
+        data: info.value.list,
+        axisLabel: {
+          textStyle: {
+            color: 'rgba(255,255,255,.5)'
+          }
+        },
+        axisLine: {
+          show: false
+        },
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        }
+      }
+    ],
+    series: [
+      {
+        name: '条',
+        type: 'bar',
+        yAxisIndex: 0,
+        data: info.value.list,
+        barWidth: '40%',
+        itemStyle: {
+          normal: {
+            barBorderRadius: 30,
+            color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+              {
+                offset: 0,
+                color: '#248ff7'
+              },
+              {
+                offset: 1,
+                color: '#3893e5'
+              }
+            ])
+          }
+        },
+        label: {
+          normal: {
+            show: false
+          }
+        }
+      },
+      {
+        name: '框',
+        type: 'bar',
+        yAxisIndex: 1,
+        barGap: '-100%',
+        data: info.value.max,
+        barWidth: '40%',
+        itemStyle: {
+          normal: {
+            color: 'none',
+            borderColor: 'rgba(255,255,255,.1)',
+            borderWidth: 1,
+            barBorderRadius: 15
+          }
+        }
+      }
+    ]
+  }
+  myChart6.setOption(option6)
+  window.addEventListener('resize', function () {
+    myChart6.resize()
+  })
+}
+</script>

文件差異過大導致無法顯示
+ 103310 - 0
src/views/board/parts/echarts/json/china.json


文件差異過大導致無法顯示
+ 25715 - 0
src/views/board/parts/echarts/json/jilin.json


+ 309 - 0
src/views/board/parts/expert.vue

@@ -0,0 +1,309 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>职称类型</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>产业类型</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>专家列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>专家名称</th>
+                    <th>产业类型</th>
+                    <th>职称</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无专家名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ item.industry_type || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.title || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>职称类型排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'expert')
+import { get } from 'lodash-es'
+// 专家
+import { ExpertStore } from '@/store/api/user/expert'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = ExpertStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 309 - 0
src/views/board/parts/incubate.vue

@@ -0,0 +1,309 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>孵化基地入驻企业数</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>市级以上活动数</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>孵化基地列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>孵化基地名称</th>
+                    <th>负责人</th>
+                    <th>负责人电话</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无孵化基地名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ item.person || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.person_phone || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>省级以上导师数排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'incubate')
+import { get } from 'lodash-es'
+// 孵化基地
+import { IncubatorStore } from '@/store/api/user/incubator'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = IncubatorStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 314 - 0
src/views/board/parts/project.vue

@@ -0,0 +1,314 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>项目来源</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>项目进展</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>项目列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>项目名称</th>
+                    <th>地区</th>
+                    <th>人数</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无项目名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ getArea(item.area) || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.person || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>项目来源排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'project')
+import { get } from 'lodash-es'
+// 项目
+import { ProjectStore } from '@/store/api/platform/project'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = ProjectStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join('-')
+  else return '暂无地区'
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 314 - 0
src/views/board/parts/supply.vue

@@ -0,0 +1,314 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>行业领域</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>所属产业</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>地区分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts4></echarts4>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>供给列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>供给名称</th>
+                    <th>地区</th>
+                    <th>人数</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.name || '暂无供给名称' }}</td>
+                    <td>
+                      <span class="text-w">{{ getArea(item.area) || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.person || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>行业领域排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts4 from './echarts/echarts4.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'supply')
+import { get } from 'lodash-es'
+// 供给
+import { SupplyStore } from '@/store/api/platform/supply'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = SupplyStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join('-')
+  else return '暂无地区'
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 309 - 0
src/views/board/parts/user.vue

@@ -0,0 +1,309 @@
+<template>
+  <div id="path">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <div class="top">
+          <div class="content top_1">
+            <div class="title">
+              <span>统计数量</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts1></echarts1>
+            </div>
+          </div>
+          <div class="content top_2">
+            <div class="title">
+              <span>所属产业分布情况</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts2></echarts2>
+            </div>
+          </div>
+          <div class="content top_3">
+            <div class="title">
+              <span>用户类型</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts3></echarts3>
+            </div>
+          </div>
+        </div>
+        <div class="bottom">
+          <div class="content bottom_1">
+            <div class="title">
+              <span>所属产业</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts5></echarts5>
+            </div>
+          </div>
+          <div class="content bottom_2">
+            <div class="title">
+              <span>用户列表</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <table class="table" width="100%" border="0" cellspacing="0" cellpadding="0">
+                <tbody>
+                  <tr>
+                    <th>用户昵称</th>
+                    <th>账号</th>
+                    <th>手机号</th>
+                    <th>状态</th>
+                  </tr>
+                  <tr v-for="(item, index) in list" :key="index">
+                    <td>{{ item.nick_name || '暂无用户昵称' }}</td>
+                    <td>
+                      <span class="text-w">{{ item.account || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <span class="text-b">{{ item.phone || '暂无' }}</span>
+                    </td>
+                    <td>
+                      <div class="text-d">{{ getDict(item.status, 'status') || '暂无' }}</div>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+              <div class="total">
+                <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+              </div>
+            </div>
+          </div>
+          <div class="content bottom_3">
+            <div class="title">
+              <span>用户性别排名</span>
+              <p></p>
+            </div>
+            <div class="boxnav">
+              <echarts6></echarts6>
+            </div>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+import echarts5 from './echarts/echarts5.vue'
+import echarts6 from './echarts/echarts6.vue'
+provide('type', 'user')
+import { get } from 'lodash-es'
+// 用户
+import { UserStore } from '@/store/api/user/user'
+import { DictDataStore } from '@/store/api/system/dictData'
+const dictDataStore = DictDataStore()
+const store = UserStore()
+const $checkRes = inject('$checkRes')
+// 列表
+const list = ref([])
+let skip = 0
+let limit = 5
+const total = ref(0)
+const currentPage = ref(1)
+onMounted(async () => {
+  await searchOther()
+  await search({ skip, limit })
+})
+// #region 字典表
+const examStatusList = ref([])
+const searchOther = async () => {
+  let result
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) examStatusList.value = result.data
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+}
+const search = async (query = { skip, limit }) => {
+  const nq = { skip: get(query, 'skip'), limit: get(query, 'limit') }
+  const res = await store.query(nq)
+  if ($checkRes(res)) {
+    list.value = get(res, 'data', [])
+    total.value = get(res, 'total', 0)
+  }
+}
+// 字典表
+const getDict = (data, type) => {
+  let list
+  let result
+  switch (type) {
+    case 'status':
+      list = examStatusList.value
+      break
+    default:
+      break
+  }
+  if (!list) return result
+  const i = list.find((f) => f.value === data)
+  if (!i) return result
+  return get(i, 'label')
+}
+// 分页
+const changePage = (page = currentPage.value) => {
+  search({ skip: (page - 1) * limit, limit: limit })
+}
+const sizeChange = (limits) => {
+  limit = limits
+  currentPage.value = 1
+  search({ skip: 0, limit: limit })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  width: 100%;
+  min-height: 74vh;
+  color: #fff;
+  font-size: 16px;
+  background: #033c76 url('/images/bg.png') no-repeat center bottom;
+  .top {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0 0 0;
+    .top_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .top_2 {
+      width: 50%;
+    }
+    .top_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+    }
+  }
+  .bottom {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    min-height: 45%;
+    padding: 20px 0;
+    .bottom_1 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .bottom_2 {
+      width: 50%;
+    }
+    .bottom_3 {
+      width: 25%;
+      margin: 0 20px;
+    }
+    .content {
+      .title {
+        display: flex;
+        align-items: flex-end;
+        span {
+          background: url('/images/line1.png') no-repeat bottom right;
+          font-size: 20px;
+          white-space: nowrap;
+          padding-bottom: 10px;
+          padding-right: 20px;
+        }
+        p {
+          background: url('/images/line2.png') no-repeat bottom right;
+          width: 100%;
+          height: 13px;
+          margin-bottom: 5px;
+          opacity: 0.5;
+        }
+      }
+      .boxnav {
+        .table {
+          height: 26vh;
+          .name {
+            max-width: 200px;
+          }
+          th {
+            border-bottom: 1px solid #407abd;
+            font-size: 14px;
+            text-align: center;
+            padding: 6px 0;
+            color: rgba(255, 255, 255, 0.8);
+          }
+          td {
+            border-bottom: 1px dotted#407abd;
+            font-size: 12px;
+            padding: 6px 0;
+            text-align: center;
+            color: rgba(255, 255, 255, 0.6);
+          }
+          tr:last-child td {
+            border: none;
+          }
+          .text-w {
+            color: #ffe400;
+          }
+          .text-d {
+            color: #ff6316;
+          }
+          .text-s {
+            color: #14e144;
+          }
+          .text-b {
+            color: #07e5ff;
+          }
+        }
+        .total {
+          margin: 5px 0 0 0;
+          display: flex;
+          justify-content: center;
+        }
+      }
+    }
+  }
+}
+:deep(.el-pagination.is-background .btn-prev:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next:disabled) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-prev) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .btn-next) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li.is-active) {
+  background-color: #4d5b87 !important;
+  color: #ffffff !important;
+}
+:deep(.el-pagination.is-background .el-pager li) {
+  background-color: rgba($color: #ffffff, $alpha: 0.1) !important;
+  color: #ffffff !important;
+}
+</style>

+ 1 - 1
src/views/user/index.vue

@@ -30,7 +30,7 @@ const viewComponent = ref()
  * @param value 子页面id
  * @param component 子页面文件名,需要与代码中的页面对应,上面componentList设置的key
  */
- const route = useRoute()
+const route = useRoute()
 const path = get(route, 'path')
 import { UserStore } from '@/store/user'
 const userStore = UserStore()

+ 1 - 3
src/views/user/parts/user/state.vue

@@ -21,9 +21,7 @@
         </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-input clearable v-model="form.type" placeholder="请输入部门类型" />
           </el-form-item>
         </el-col>
       </el-row>