Explorar o código

修改专家智库 优质企业

zs hai 1 ano
pai
achega
037b599735

+ 5 - 0
src/App.vue

@@ -19,6 +19,11 @@ body {
   margin: 0 auto;
 }
 .textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.textMore {
   display: -webkit-box;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;

+ 2 - 2
src/layout/index.vue

@@ -2,14 +2,14 @@
   <div class="common-layout">
     <div class="top">
       <el-row :gutter="20" align="middle">
-        <el-col :span="8" class="top_1">
+        <el-col :span="7" class="top_1">
           <el-image class="image" :src="siteInfo.logoUrl" fit="fill" />
           <div class="content">
             <text class="title">{{ siteInfo.zhTitle }}</text>
             <text class="english">{{ siteInfo.zhBrief }}</text>
           </div>
         </el-col>
-        <el-col :span="10">
+        <el-col :span="13">
           <a-tabs v-model:activeKey="current" size="large" @tabClick="selectMenu">
             <a-tab-pane v-for="item in menuList" :key="item.href" :tab="item.title"> </a-tab-pane>
           </a-tabs>

+ 3 - 0
src/layout/site.js

@@ -27,6 +27,9 @@ export const menuList = [
   { title: '政策新闻', href: 'news', English: 'Policy News' },
   { title: '新闻资讯', href: 'brain', English: 'News Information' },
   { title: '供需商城', href: 'demand', English: 'Demand Mall' },
+  { title: '专家智库', href: 'expert', English: 'Expert Tank' },
+  { title: '优质企业', href: 'company', English: 'Authorized Operator' },
+  { title: '项目精选', href: 'project', English: 'Project Selection' },
   { title: '创新大赛', href: 'innovation', English: 'Innovation Competition' },
   { title: '成果展示', href: 'achievement', English: 'Achievement Display' }
 ]

+ 22 - 4
src/router/index.js

@@ -56,24 +56,42 @@ const router = createRouter({
           meta: { title: '吉林省信息技术孵化平台-成果展示' },
           component: () => import('@/views/achievement/index.vue')
         },
+        {
+          path: '/company',
+          name: 'company',
+          meta: { title: '吉林省信息技术孵化平台-企业' },
+          component: () => import('@/views/company/index.vue')
+        },
+        {
+          path: '/expert',
+          name: 'expert',
+          meta: { title: '吉林省信息技术孵化平台-专家' },
+          component: () => import('@/views/expert/index.vue')
+        },
+        {
+          path: '/project',
+          name: 'project',
+          meta: { title: '吉林省信息技术孵化平台-项目' },
+          component: () => import('@/views/project/index.vue')
+        },
         {
           path: '/achievement/detail',
-          meta: { title: '吉林省重点领域技术转移平台' },
+          meta: { title: '吉林省重点领域技术转移平台-详情' },
           component: () => import('@/views/achievement/detail.vue')
         },
         {
           path: '/demand/detail',
-          meta: { title: '吉林省重点领域技术转移平台' },
+          meta: { title: '吉林省重点领域技术转移平台-详情' },
           component: () => import('@/views/demand/detail.vue')
         },
         {
           path: '/news/detail',
-          meta: { title: '吉林省重点领域技术转移平台' },
+          meta: { title: '吉林省重点领域技术转移平台-详情' },
           component: () => import('@/views/news/detail.vue')
         },
         {
           path: '/innovation/detail',
-          meta: { title: '吉林省重点领域技术转移平台' },
+          meta: { title: '吉林省重点领域技术转移平台-详情' },
           component: () => import('@/views/innovation/detail.vue')
         },
         {

+ 325 - 0
src/views/company/detail.vue

@@ -0,0 +1,325 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row :span="24" class="one_1">
+              <el-col :span="20" class="title">{{ info.name || '暂无标题' }}</el-col>
+              <el-col :span="4" class="file" @click="toCollection">
+                <el-icon><Star /></el-icon>
+                收藏
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <a-descriptions bordered>
+              <a-descriptions-item label="行业领域">
+                {{ getDict(info.field, 'field') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求紧急度">
+                {{ getDict(info.urgent, 'urgent') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="合作方式">
+                {{ getDict(info.method, 'method') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="有效期">
+                {{ getTime(info.time) }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求地区">
+                {{ getArea(info.area) }}
+              </a-descriptions-item>
+            </a-descriptions>
+          </el-col>
+          <el-col :span="24" class="pointer">
+            <div class="money">
+              价格:<span> {{ info.money || '面议' }} </span>
+            </div>
+            <a-button type="primary" @click="toDocking"> 我要对接 </a-button>
+          </el-col>
+          <el-col :span="24" class="pointer" v-if="!user && !user._id">
+            提醒:您还没有登录,登录成功后再对接
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-col :span="24" class="thr_1">
+              <p>单位信息</p>
+            </el-col>
+            <el-row :span="24" class="thr_2">
+              <el-col :span="17" class="left">
+                <el-col :span="24" class="name">
+                  {{ unit.name || '暂无' }}
+                </el-col>
+                <el-col :span="24" class="other">
+                  <span>联系人</span>
+                  {{ unit.contacts || '暂无' }}
+                </el-col>
+              </el-col>
+              <el-col :span="4" class="right">
+                <a-button type="primary" @click="toChat">
+                  <template #icon>
+                    <MessageOutlined />
+                  </template>
+                  点击在线洽谈
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <a-divider />
+          <el-col :span="24" class="four">
+            <el-col :span="24" class="four_1">
+              <p>需求详情</p>
+            </el-col>
+            <el-col :span="24" class="four_2">{{ info.brief || '暂无' }}</el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 基础
+import { MessageOutlined } from '@ant-design/icons-vue'
+import { get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 接口
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 路由
+const route = useRoute()
+// 加载中
+const loading = ref(false)
+const info = ref({})
+const unit = ref({ name: '吉林大学', contacts: '陈老师' })
+// 字典表
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const demandList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async () => {
+  let id = route.query.id
+  if (id) {
+    let res = await store.fetch(id)
+    if (res.errcode == '0') info.value = res.data
+  }
+}
+const searchOther = async () => {
+  let result
+  // 合作方式
+  result = await dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'method') res = methodList.value.find((f) => f.value == data)
+  else if (model == 'urgent') res = urgentList.value.find((f) => f.value == data)
+  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'demand') res = demandList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 时间
+const getTime = (data) => {
+  if (data) return `${data[0]} 至 ${data[1]}`
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+// 在线洽谈
+const toChat = () => {
+  console.log('在线洽谈')
+}
+// 我要对接
+const toDocking = () => {
+  console.log('我要对接')
+}
+// 收藏
+const toCollection = () => {
+  console.log('收藏')
+}
+</script>
+<style scoped lang="scss">
+.main {
+  .one {
+    margin: 10px 0 0 0;
+    background: #f7f7f7;
+    padding: 24px;
+    border-top: 6px solid #2374ff;
+    overflow: hidden;
+    border-radius: 0 0 5px 5px;
+
+    .one_1 {
+      margin: 0 0 10px 0;
+
+      .title {
+        font-size: 18px;
+        font-weight: 700;
+        color: #383b40;
+      }
+
+      .file {
+        display: flex;
+        align-items: center;
+        justify-content: end;
+        font-family: PingFangSC-Regular;
+        font-size: 14px;
+        color: #2374ff;
+        text-align: right;
+      }
+    }
+
+    .one_2 {
+      display: inline-block;
+      font-size: 12px;
+      background: rgba(18, 172, 117, 0.05);
+      color: #12ac75;
+      padding: 0 15px;
+      height: 20px;
+      line-height: 20px;
+      border-radius: 10px;
+      margin-bottom: 2px;
+    }
+  }
+
+  .two {
+    background: #f9fafb;
+    border-radius: 2px;
+    padding: 30px;
+    margin: 34px 0 20px;
+    font-family: PingFangSC-Medium;
+    font-size: 14px;
+    color: #383b40;
+    line-height: 14px;
+  }
+
+  .thr {
+    margin: 0 0 10px 0;
+
+    .thr_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+
+    .thr_2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      background: #fff;
+      border: 1px solid #edeff2;
+      box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.03);
+      border-radius: 2px;
+      padding: 30px 20px;
+
+      .left {
+        .name {
+          height: 20px;
+          font-family: PingFangSC-Semibold;
+          font-size: 18px;
+          color: #383b40;
+          line-height: 20px;
+          font-weight: 700;
+          margin-bottom: 8px;
+        }
+
+        .other {
+          height: 14px;
+          font-family: PingFangSC-Medium;
+          font-size: 14px;
+          color: #383b40;
+          line-height: 14px;
+          font-weight: 700;
+
+          span {
+            height: 14px;
+            font-family: PingFangSC-Regular;
+            font-size: 14px;
+            color: #7e8288;
+            line-height: 14px;
+            font-weight: 400;
+            margin-right: 16px;
+          }
+        }
+      }
+
+      .right {
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+  }
+
+  .four {
+    margin: 0 0 10px 0;
+
+    .four_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+  }
+  .pointer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-family: PingFangSC-Regular;
+    font-size: 14px;
+    color: #7e8288;
+    margin: 10px 0 0 0;
+    .money {
+      margin: 0 10px 0 0;
+      span {
+        font-family: PingFangSC-Semibold;
+        font-size: 20px;
+        color: #e94643;
+        line-height: 20px;
+      }
+    }
+  }
+}
+</style>

+ 451 - 0
src/views/company/index.vue

@@ -0,0 +1,451 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <el-col :span="24" class="idemand">
+          <el-image class="image" :src="demand" fit="fill" />
+        </el-col>
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row class="one_1" v-for="(val, indexs) in searchList" :key="indexs">
+              <el-col :span="3" class="left">{{ val.title }}</el-col>
+              <el-col :span="21" class="right">
+                <a-button
+                  class="title"
+                  v-for="(item, index) in val.list"
+                  :key="index"
+                  type="link"
+                  size="samll"
+                >
+                  {{ item.label }}
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <el-col
+              :span="24"
+              class="list"
+              v-for="(item, index) in list"
+              :key="index"
+              @click="toView(item)"
+            >
+              <el-col :span="4" class="left">
+                <el-image class="image" :src="item.url" fit="fill" />
+              </el-col>
+              <el-col :span="20" class="right">
+                <el-col :span="24" class="right_1">
+                  <div class="name">{{ item.name }}</div>
+                  <el-tag :type="[item.status == '0' ? 'success' : 'info']">{{
+                    getDict(item.status, 'status')
+                  }}</el-tag>
+                </el-col>
+                <el-col :span="24" class="right_2">
+                  <el-tag v-for="(val, indexs) in item.tags" :key="indexs" type="primary">{{
+                    val
+                  }}</el-tag>
+                </el-col>
+                <el-col :span="24" class="right_3">
+                  <el-col :span="4" class="info">
+                    法定代表人:<span>{{ item.representative || '暂无' }}</span>
+                  </el-col>
+                  <el-col :span="4" class="info">
+                    成立日期:{{ item.create_time || '暂无' }}
+                  </el-col>
+                  <el-col :span="8" class="info">
+                    统一社会信用代码:{{ item.code || '暂无' }}
+                  </el-col>
+                </el-col>
+                <el-col :span="24" class="right_3">
+                  <el-col :span="8" class="info">电话:{{ item.phone || '暂无' }}</el-col>
+                  <el-col :span="8" class="info">邮箱:{{ item.email || '暂无' }}</el-col>
+                </el-col>
+                <el-col :span="24" class="right_3"> 地址:{{ item.address || '暂无' }} </el-col>
+              </el-col>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-pagination
+              background
+              layout="total, prev, pager, next"
+              :page-sizes="[10, 20, 50, 100, 200]"
+              :total="total"
+              :page-size="limit"
+              v-model:current-page="currentPage"
+              @current-change="changePage"
+              @size-change="sizeChange"
+            >
+            </el-pagination>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+const $checkRes = inject('$checkRes')
+import { get } from 'lodash-es'
+// 接口
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+// 图片引入
+import demand from '@/assets/innovation.png'
+// 图片引入
+import science from '@/assets/bg.png'
+// 路由
+const router = useRouter()
+// 加载中
+const loading = ref(false)
+// 列表
+const list = ref([
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  },
+  {
+    name: '努比亚技术有限公司 ',
+    url: science,
+    tags: ['战略投资', '国家高新技术企业', '创新型中小企业', '国家级专利奖'],
+    code: ' 914403007320587423',
+    phone: '075************',
+    type: '0',
+    representative: '倪飞',
+    email: 'xie.giang@nubia.com',
+    person: '1万人以上',
+    address: '深圳市南山区桃源街道福光社区留仙大道3370号南山智园崇文园区2号楼1801',
+    create_time: '2001-09-12',
+    status: '0'
+  }
+])
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+// 字典表
+const fieldList = ref([])
+const statusList = ref([])
+const searchList = ref([
+  {
+    title: '注册资本',
+    list: [
+      { value: '0', label: '0~100万' },
+      { value: '1', label: '100~200万' },
+      { value: '2', label: '200~500万' },
+      { value: '3', label: '500~1000万' },
+      { value: '4', label: '1000万以上' }
+    ]
+  },
+  {
+    title: '成立时间',
+    list: [
+      { value: '0', label: '成立1年内' },
+      { value: '1', label: '1~2年成立' },
+      { value: '2', label: '2~3年成立' },
+      { value: '3', label: '3~5年成立' },
+      { value: '4', label: '5年以上' }
+    ]
+  },
+  {
+    title: '省份地区',
+    list: [
+      { value: '0', label: '北京市' },
+      { value: '1', label: '天津市' },
+      { value: '2', label: '河北省' },
+      { value: '3', label: '山西省' },
+      { value: '4', label: '内蒙古自治区' },
+      { value: '5', label: '辽宁省' },
+      { value: '6', label: '吉林省' },
+      { value: '7', label: '上海市' },
+      { value: '8', label: '江苏省' },
+      { value: '9', label: '浙江省' },
+      { value: '10', label: '安徽省' },
+      { value: '11', label: '福建省' },
+      { value: '0', label: '江西省' },
+      { value: '1', label: '山东省' },
+      { value: '2', label: '河南省' },
+      { value: '3', label: '湖北省' },
+      { value: '4', label: '湖南省' },
+      { value: '5', label: '广东省' },
+      { value: '6', label: '广西壮族自治区' },
+      { value: '7', label: '海南省' },
+      { value: '8', label: '重庆市' },
+      { value: '9', label: '四川省' },
+      { value: '10', label: '贵州省' },
+      { value: '11', label: '云南省' }
+    ]
+  },
+  {
+    title: '行业分类',
+    list: [
+      { value: '0', label: '农、林、牧、渔业' },
+      { value: '1', label: '采矿业' },
+      { value: '2', label: '制造业' },
+      { value: '3', label: '电力、热力、燃气及水生产和供应业' },
+      { value: '4', label: '建筑业' },
+      { value: '5', label: '批发和零售业' }
+    ]
+  }
+])
+// 查看
+const toView = (item) => {
+  router.push({ path: '/demand/detail', query: { id: item.id || item._id } })
+}
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const searchOther = async () => {
+  let result
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 企业状态
+  result = await dictDataStore.query({ code: 'companyStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+}
+const search = async (query = { skip: 0, limit }) => {
+  // const info = {
+  //   skip: query.skip,
+  //   limit: query.limit,
+  //   ...searchForm.value,
+  //   is_use: '0',
+  //   status: '1'
+  // }
+  // const res = await store.query(info)
+  // if (res.errcode == '0') {
+  //   list.value = res.data
+  //   total.value = res.total
+  // }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'status') res = statusList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 地区显示
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+const currentPage = ref(1)
+// 分页
+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 lang="scss" scoped>
+.main {
+  background: rgb(248, 248, 248);
+
+  .idemand {
+    .image {
+      width: 100%;
+      height: 200px;
+    }
+  }
+
+  .one {
+    background: #ffffff;
+    border-radius: 10px;
+    padding: 15px;
+    margin: 10px 0 0 0;
+
+    .one_1 {
+      display: flex;
+      align-items: center;
+      padding: 10px 0;
+      border-bottom: #9d9898 1px dashed;
+
+      .left {
+        background: #778cb3;
+        border-radius: 15px;
+        line-height: 26px;
+        color: #fff;
+        text-align: center;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        font-family: PingFangSC-Medium;
+      }
+
+      .right {
+        .title {
+          color: #666;
+          font-size: 14px;
+          line-height: 15px;
+          margin: 0 10px;
+          display: inline-block;
+          overflow: hidden;
+          text-decoration: none;
+        }
+
+        .title:hover {
+          color: #2374ff;
+        }
+      }
+    }
+  }
+
+  .two {
+    margin-top: 20px;
+    background: #ffffff;
+    border-radius: 10px;
+    padding: 15px;
+
+    .list {
+      display: flex;
+      margin: 5px 0 0 0;
+      padding: 5px;
+      border-radius: 8px;
+      border: 1px solid #f4f4f4;
+      .left {
+        text-align: center;
+        .image {
+          width: 150px;
+          height: 150px;
+          border-radius: 10px;
+        }
+      }
+      .right {
+        padding: 5px;
+        .right_1 {
+          display: flex;
+          align-content: center;
+          .name {
+            word-break: break-all;
+            font-size: 18px;
+            font-family:
+              PingFangSC-Regular,
+              PingFang SC;
+            font-weight: 500;
+            line-height: 24px;
+            width: 175px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+          }
+        }
+        .right_2 {
+          grid-gap: 0.5rem;
+          gap: 0.5rem;
+          display: flex;
+          margin: 10px 0;
+        }
+        .right_3 {
+          display: flex;
+          margin: 10px 0;
+          font-size: 14px;
+          font-family:
+            PingFangSC-Regular,
+            PingFang SC;
+          color: #666;
+          .info {
+            span {
+              color: #2171f6;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .thr {
+    display: flex;
+    flex-direction: row-reverse;
+    padding: 20px;
+  }
+}
+</style>

+ 325 - 0
src/views/expert/detail.vue

@@ -0,0 +1,325 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row :span="24" class="one_1">
+              <el-col :span="20" class="title">{{ info.name || '暂无标题' }}</el-col>
+              <el-col :span="4" class="file" @click="toCollection">
+                <el-icon><Star /></el-icon>
+                收藏
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <a-descriptions bordered>
+              <a-descriptions-item label="行业领域">
+                {{ getDict(info.field, 'field') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求紧急度">
+                {{ getDict(info.urgent, 'urgent') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="合作方式">
+                {{ getDict(info.method, 'method') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="有效期">
+                {{ getTime(info.time) }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求地区">
+                {{ getArea(info.area) }}
+              </a-descriptions-item>
+            </a-descriptions>
+          </el-col>
+          <el-col :span="24" class="pointer">
+            <div class="money">
+              价格:<span> {{ info.money || '面议' }} </span>
+            </div>
+            <a-button type="primary" @click="toDocking"> 我要对接 </a-button>
+          </el-col>
+          <el-col :span="24" class="pointer" v-if="!user && !user._id">
+            提醒:您还没有登录,登录成功后再对接
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-col :span="24" class="thr_1">
+              <p>单位信息</p>
+            </el-col>
+            <el-row :span="24" class="thr_2">
+              <el-col :span="17" class="left">
+                <el-col :span="24" class="name">
+                  {{ unit.name || '暂无' }}
+                </el-col>
+                <el-col :span="24" class="other">
+                  <span>联系人</span>
+                  {{ unit.contacts || '暂无' }}
+                </el-col>
+              </el-col>
+              <el-col :span="4" class="right">
+                <a-button type="primary" @click="toChat">
+                  <template #icon>
+                    <MessageOutlined />
+                  </template>
+                  点击在线洽谈
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <a-divider />
+          <el-col :span="24" class="four">
+            <el-col :span="24" class="four_1">
+              <p>需求详情</p>
+            </el-col>
+            <el-col :span="24" class="four_2">{{ info.brief || '暂无' }}</el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 基础
+import { MessageOutlined } from '@ant-design/icons-vue'
+import { get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 接口
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 路由
+const route = useRoute()
+// 加载中
+const loading = ref(false)
+const info = ref({})
+const unit = ref({ name: '吉林大学', contacts: '陈老师' })
+// 字典表
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const demandList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async () => {
+  let id = route.query.id
+  if (id) {
+    let res = await store.fetch(id)
+    if (res.errcode == '0') info.value = res.data
+  }
+}
+const searchOther = async () => {
+  let result
+  // 合作方式
+  result = await dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'method') res = methodList.value.find((f) => f.value == data)
+  else if (model == 'urgent') res = urgentList.value.find((f) => f.value == data)
+  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'demand') res = demandList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 时间
+const getTime = (data) => {
+  if (data) return `${data[0]} 至 ${data[1]}`
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+// 在线洽谈
+const toChat = () => {
+  console.log('在线洽谈')
+}
+// 我要对接
+const toDocking = () => {
+  console.log('我要对接')
+}
+// 收藏
+const toCollection = () => {
+  console.log('收藏')
+}
+</script>
+<style scoped lang="scss">
+.main {
+  .one {
+    margin: 10px 0 0 0;
+    background: #f7f7f7;
+    padding: 24px;
+    border-top: 6px solid #2374ff;
+    overflow: hidden;
+    border-radius: 0 0 5px 5px;
+
+    .one_1 {
+      margin: 0 0 10px 0;
+
+      .title {
+        font-size: 18px;
+        font-weight: 700;
+        color: #383b40;
+      }
+
+      .file {
+        display: flex;
+        align-items: center;
+        justify-content: end;
+        font-family: PingFangSC-Regular;
+        font-size: 14px;
+        color: #2374ff;
+        text-align: right;
+      }
+    }
+
+    .one_2 {
+      display: inline-block;
+      font-size: 12px;
+      background: rgba(18, 172, 117, 0.05);
+      color: #12ac75;
+      padding: 0 15px;
+      height: 20px;
+      line-height: 20px;
+      border-radius: 10px;
+      margin-bottom: 2px;
+    }
+  }
+
+  .two {
+    background: #f9fafb;
+    border-radius: 2px;
+    padding: 30px;
+    margin: 34px 0 20px;
+    font-family: PingFangSC-Medium;
+    font-size: 14px;
+    color: #383b40;
+    line-height: 14px;
+  }
+
+  .thr {
+    margin: 0 0 10px 0;
+
+    .thr_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+
+    .thr_2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      background: #fff;
+      border: 1px solid #edeff2;
+      box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.03);
+      border-radius: 2px;
+      padding: 30px 20px;
+
+      .left {
+        .name {
+          height: 20px;
+          font-family: PingFangSC-Semibold;
+          font-size: 18px;
+          color: #383b40;
+          line-height: 20px;
+          font-weight: 700;
+          margin-bottom: 8px;
+        }
+
+        .other {
+          height: 14px;
+          font-family: PingFangSC-Medium;
+          font-size: 14px;
+          color: #383b40;
+          line-height: 14px;
+          font-weight: 700;
+
+          span {
+            height: 14px;
+            font-family: PingFangSC-Regular;
+            font-size: 14px;
+            color: #7e8288;
+            line-height: 14px;
+            font-weight: 400;
+            margin-right: 16px;
+          }
+        }
+      }
+
+      .right {
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+  }
+
+  .four {
+    margin: 0 0 10px 0;
+
+    .four_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+  }
+  .pointer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-family: PingFangSC-Regular;
+    font-size: 14px;
+    color: #7e8288;
+    margin: 10px 0 0 0;
+    .money {
+      margin: 0 10px 0 0;
+      span {
+        font-family: PingFangSC-Semibold;
+        font-size: 20px;
+        color: #e94643;
+        line-height: 20px;
+      }
+    }
+  }
+}
+</style>

+ 325 - 0
src/views/expert/index.vue

@@ -0,0 +1,325 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <el-col :span="24" class="idemand">
+          <el-image class="image" :src="demand" fit="fill" />
+        </el-col>
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row class="one_1" v-for="(val, indexs) in searchList" :key="indexs">
+              <el-col :span="3" class="left">{{ val.title }}</el-col>
+              <el-col :span="21" class="right">
+                <a-button
+                  class="title"
+                  v-for="(item, index) in val.list"
+                  :key="index"
+                  type="link"
+                  size="samll"
+                >
+                  {{ item.label }}
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <el-col
+              :span="24"
+              class="list"
+              v-for="(item, index) in list"
+              :key="index"
+              @click="toView(item)"
+            >
+              <el-col :span="24" class="name">
+                <el-tooltip effect="dark" :content="item.name" placement="top">
+                  {{ item.name || '暂无需求名称' }}
+                </el-tooltip>
+              </el-col>
+              <el-row class="other">
+                <el-col :span="8" class="other_1">
+                  <span>技术领域:</span>
+                  {{ getDict(item.field || '暂无技术领域', 'field') }}
+                </el-col>
+                <el-col :span="8" class="other_1">
+                  <span>需求地区:</span>
+                  {{ getArea(item.area || '暂无需求地区') }}
+                </el-col>
+                <el-col :span="8" class="other_1">
+                  <span>单位:</span>
+                  {{ item.user || '暂无单位' }}
+                </el-col>
+              </el-row>
+              <el-col :span="24" class="brief textOver">
+                {{ item.brief || '没有更多需求简介' }}
+              </el-col>
+            </el-col>
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-pagination
+              background
+              layout="total, prev, pager, next"
+              :page-sizes="[10, 20, 50, 100, 200]"
+              :total="total"
+              :page-size="limit"
+              v-model:current-page="currentPage"
+              @current-change="changePage"
+              @size-change="sizeChange"
+            >
+            </el-pagination>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+const $checkRes = inject('$checkRes')
+import { get } from 'lodash-es'
+// 接口
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+// 图片引入
+import demand from '@/assets/innovation.png'
+// 路由
+const router = useRouter()
+// 加载中
+const loading = ref(false)
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+// 字典表
+const fieldList = ref([])
+const searchList = ref([
+  {
+    title: '技术领域',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '先进制造' },
+      { value: '2', label: '地球、空间与海洋' },
+      { value: '3', label: '新材料' },
+      { value: '4', label: '新能源与节能' },
+      { value: '5', label: '核应用技术' },
+      { value: '6', label: '环境保护' },
+      { value: '7', label: '现代交通' },
+      { value: '8', label: '现代农业' },
+      { value: '9', label: '生物医药与医疗器械' },
+      { value: '10', label: '电子信息' },
+      { value: '11', label: '航空航天' }
+    ]
+  },
+  {
+    title: '合作方式',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '技术开发' },
+      { value: '2', label: '技术咨询' },
+      { value: '3', label: '技术服务' },
+      { value: '4', label: '许可转让' },
+      { value: '4', label: '完全转让' },
+      { value: '4', label: '技术入股' },
+      { value: '4', label: '合作开发' },
+      { value: '4', label: '其他' }
+    ]
+  },
+  {
+    title: '所在区域',
+    list: [
+      { value: '0', label: '北京市' },
+      { value: '1', label: '天津市' },
+      { value: '2', label: '河北省' },
+      { value: '3', label: '山西省' },
+      { value: '4', label: '内蒙古自治区' },
+      { value: '5', label: '辽宁省' },
+      { value: '6', label: '吉林省' },
+      { value: '7', label: '上海市' },
+      { value: '8', label: '江苏省' },
+      { value: '9', label: '浙江省' },
+      { value: '10', label: '安徽省' },
+      { value: '11', label: '福建省' },
+      { value: '0', label: '江西省' },
+      { value: '1', label: '山东省' },
+      { value: '2', label: '河南省' },
+      { value: '3', label: '湖北省' },
+      { value: '4', label: '湖南省' },
+      { value: '5', label: '广东省' },
+      { value: '6', label: '广西壮族自治区' },
+      { value: '7', label: '海南省' },
+      { value: '8', label: '重庆市' },
+      { value: '9', label: '四川省' },
+      { value: '10', label: '贵州省' },
+      { value: '11', label: '云南省' }
+    ]
+  },
+  {
+    title: '需求状态',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '洽谈中' },
+      { value: '2', label: '已结束' }
+    ]
+  }
+])
+// 查看
+const toView = (item) => {
+  router.push({ path: '/demand/detail', query: { id: item.id || item._id } })
+}
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const searchOther = async () => {
+  let result
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+}
+const search = async (query = { skip: 0, limit }) => {
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    ...searchForm.value,
+    is_use: '0',
+    status: '1'
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 地区显示
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+const currentPage = ref(1)
+// 分页
+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 lang="scss" scoped>
+.main {
+  background: rgb(248, 248, 248);
+
+  .idemand {
+    .image {
+      width: 100%;
+      height: 200px;
+    }
+  }
+
+  .one {
+    background: #ffffff;
+    border-radius: 10px;
+    padding: 15px;
+    margin: 10px 0 0 0;
+
+    .one_1 {
+      display: flex;
+      align-items: center;
+      padding: 10px 0;
+      border-bottom: #9d9898 1px dashed;
+
+      .left {
+        background: #778cb3;
+        border-radius: 15px;
+        line-height: 26px;
+        color: #fff;
+        text-align: center;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        font-family: PingFangSC-Medium;
+      }
+
+      .right {
+        .title {
+          color: #666;
+          font-size: 14px;
+          line-height: 15px;
+          margin: 0 10px;
+          display: inline-block;
+          overflow: hidden;
+          text-decoration: none;
+        }
+
+        .title:hover {
+          color: #2374ff;
+        }
+      }
+    }
+  }
+
+  .two {
+    margin-top: 20px;
+    background: #ffffff;
+    border-radius: 10px;
+    padding: 15px;
+
+    .list {
+      margin-bottom: 30px;
+      border-bottom: 1px solid #ebebeb;
+      padding-bottom: 10px;
+
+      .name {
+        color: #337ab7;
+        font-size: 18px;
+        font-weight: bold;
+        display: inline-block;
+        margin: 10px 0;
+      }
+
+      .name:hover {
+        color: #2374ff;
+      }
+
+      .other {
+        padding: 5px 0;
+
+        .other_1 {
+          font-family: 'PingFangSC-Light', 'Microsoft YaHei', 'WenQuanYi Micro Hei', arial,
+            sans-serif;
+          font-size: 12px;
+          font-weight: normal;
+        }
+
+        .other_1:hover {
+          color: #2374ff;
+        }
+      }
+
+      .brief {
+        line-height: 30px;
+        color: #666;
+        font-size: 14px;
+      }
+    }
+  }
+
+  .thr {
+    display: flex;
+    flex-direction: row-reverse;
+    padding: 20px;
+  }
+}
+</style>

+ 13 - 9
src/views/home/index.vue

@@ -9,7 +9,7 @@
           <el-col :span="24" class="one">
             <el-col :span="24" class="one_1">
               <el-col :span="12" class="left">汇聚科技创新的创新赛道</el-col>
-              <el-col :span="12" class="right" @click="toMore">查看全部项目</el-col>
+              <el-col :span="12" class="right" @click="toMore('/project')">查看全部项目</el-col>
             </el-col>
             <el-col :span="24" class="one_2">
               <el-col :span="4" class="left">
@@ -24,7 +24,7 @@
                   <el-col :span="6" class="image">
                     <el-image class="image" :src="item.url" fit="fill" />
                   </el-col>
-                  <el-col :span="18" class="title">{{ item.title }}</el-col>
+                  <el-col :span="18" class="title textOver">{{ item.title }}</el-col>
                 </el-col>
               </el-col>
               <el-col :span="20" class="right">
@@ -35,8 +35,10 @@
                   :key="index"
                   @click="toView(item)"
                 >
-                  <el-col :span="24" class="name">{{ item.name || '暂无项目名称' }}</el-col>
-                  <el-col :span="24" class="brief textOver">
+                  <el-col :span="24" class="name textOver">{{
+                    item.name || '暂无项目名称'
+                  }}</el-col>
+                  <el-col :span="24" class="brief textMore">
                     项目介绍:{{ item.brief || '暂无项目介绍' }}
                   </el-col>
                   <el-col :span="24" class="info">
@@ -51,7 +53,7 @@
           <el-col :span="24" class="one">
             <el-col :span="24" class="one_1">
               <el-col :span="12" class="left">汇聚各种行业的专家</el-col>
-              <el-col :span="12" class="right" @click="toMore">查看更多专家</el-col>
+              <el-col :span="12" class="right" @click="toMore('/expert')">查看更多专家</el-col>
             </el-col>
             <el-col :span="24" class="one_3">
               <el-col :span="24" class="table_1">
@@ -111,7 +113,7 @@
           <el-col :span="24" class="one">
             <el-col :span="24" class="one_1">
               <el-col :span="12" class="left">汇聚各类优质的企业</el-col>
-              <el-col :span="12" class="right" @click="toMore">查看更多企业</el-col>
+              <el-col :span="12" class="right" @click="toMore('/company')">查看更多企业</el-col>
             </el-col>
             <el-col :span="24" class="one_4">
               <el-col
@@ -171,7 +173,7 @@ import { ProjectStore } from '@/store/api/platform/project'
 const store = ProjectStore()
 const dictDataStore = DictDataStore()
 // 路由
-const route = useRoute()
+const router = useRouter()
 // 图片引入
 import science from '@/assets/bg.png'
 import science_1 from '@/assets/science_1.png'
@@ -415,7 +417,9 @@ const toView = (item) => {
   console.log(item)
 }
 // 查看更多
-const toMore = () => {}
+const toMore = (path) => {
+  router.push({ path })
+}
 // 类型
 const mouseOver = (item) => {
   active.value = item.type
@@ -522,7 +526,7 @@ const toChat = (item) => {
             color: #111;
             line-height: 16px;
             display: block;
-            margin: 12px 0;
+            margin: 5px 0 12px 0;
           }
           .brief {
             font-size: 14px;

+ 325 - 0
src/views/project/detail.vue

@@ -0,0 +1,325 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row :span="24" class="one_1">
+              <el-col :span="20" class="title">{{ info.name || '暂无标题' }}</el-col>
+              <el-col :span="4" class="file" @click="toCollection">
+                <el-icon><Star /></el-icon>
+                收藏
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <a-descriptions bordered>
+              <a-descriptions-item label="行业领域">
+                {{ getDict(info.field, 'field') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求紧急度">
+                {{ getDict(info.urgent, 'urgent') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="合作方式">
+                {{ getDict(info.method, 'method') }}
+              </a-descriptions-item>
+              <a-descriptions-item label="有效期">
+                {{ getTime(info.time) }}
+              </a-descriptions-item>
+              <a-descriptions-item label="需求地区">
+                {{ getArea(info.area) }}
+              </a-descriptions-item>
+            </a-descriptions>
+          </el-col>
+          <el-col :span="24" class="pointer">
+            <div class="money">
+              价格:<span> {{ info.money || '面议' }} </span>
+            </div>
+            <a-button type="primary" @click="toDocking"> 我要对接 </a-button>
+          </el-col>
+          <el-col :span="24" class="pointer" v-if="!user && !user._id">
+            提醒:您还没有登录,登录成功后再对接
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-col :span="24" class="thr_1">
+              <p>单位信息</p>
+            </el-col>
+            <el-row :span="24" class="thr_2">
+              <el-col :span="17" class="left">
+                <el-col :span="24" class="name">
+                  {{ unit.name || '暂无' }}
+                </el-col>
+                <el-col :span="24" class="other">
+                  <span>联系人</span>
+                  {{ unit.contacts || '暂无' }}
+                </el-col>
+              </el-col>
+              <el-col :span="4" class="right">
+                <a-button type="primary" @click="toChat">
+                  <template #icon>
+                    <MessageOutlined />
+                  </template>
+                  点击在线洽谈
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <a-divider />
+          <el-col :span="24" class="four">
+            <el-col :span="24" class="four_1">
+              <p>需求详情</p>
+            </el-col>
+            <el-col :span="24" class="four_2">{{ info.brief || '暂无' }}</el-col>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+// 基础
+import { MessageOutlined } from '@ant-design/icons-vue'
+import { get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+// 接口
+import { DemandStore } from '@/store/api/platform/demand'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = DemandStore()
+const dictDataStore = DictDataStore()
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 路由
+const route = useRoute()
+// 加载中
+const loading = ref(false)
+const info = ref({})
+const unit = ref({ name: '吉林大学', contacts: '陈老师' })
+// 字典表
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const demandList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async () => {
+  let id = route.query.id
+  if (id) {
+    let res = await store.fetch(id)
+    if (res.errcode == '0') info.value = res.data
+  }
+}
+const searchOther = async () => {
+  let result
+  // 合作方式
+  result = await dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'method') res = methodList.value.find((f) => f.value == data)
+  else if (model == 'urgent') res = urgentList.value.find((f) => f.value == data)
+  else if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  else if (model == 'demand') res = demandList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 时间
+const getTime = (data) => {
+  if (data) return `${data[0]} 至 ${data[1]}`
+}
+// 地区
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+// 在线洽谈
+const toChat = () => {
+  console.log('在线洽谈')
+}
+// 我要对接
+const toDocking = () => {
+  console.log('我要对接')
+}
+// 收藏
+const toCollection = () => {
+  console.log('收藏')
+}
+</script>
+<style scoped lang="scss">
+.main {
+  .one {
+    margin: 10px 0 0 0;
+    background: #f7f7f7;
+    padding: 24px;
+    border-top: 6px solid #2374ff;
+    overflow: hidden;
+    border-radius: 0 0 5px 5px;
+
+    .one_1 {
+      margin: 0 0 10px 0;
+
+      .title {
+        font-size: 18px;
+        font-weight: 700;
+        color: #383b40;
+      }
+
+      .file {
+        display: flex;
+        align-items: center;
+        justify-content: end;
+        font-family: PingFangSC-Regular;
+        font-size: 14px;
+        color: #2374ff;
+        text-align: right;
+      }
+    }
+
+    .one_2 {
+      display: inline-block;
+      font-size: 12px;
+      background: rgba(18, 172, 117, 0.05);
+      color: #12ac75;
+      padding: 0 15px;
+      height: 20px;
+      line-height: 20px;
+      border-radius: 10px;
+      margin-bottom: 2px;
+    }
+  }
+
+  .two {
+    background: #f9fafb;
+    border-radius: 2px;
+    padding: 30px;
+    margin: 34px 0 20px;
+    font-family: PingFangSC-Medium;
+    font-size: 14px;
+    color: #383b40;
+    line-height: 14px;
+  }
+
+  .thr {
+    margin: 0 0 10px 0;
+
+    .thr_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+
+    .thr_2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      background: #fff;
+      border: 1px solid #edeff2;
+      box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.03);
+      border-radius: 2px;
+      padding: 30px 20px;
+
+      .left {
+        .name {
+          height: 20px;
+          font-family: PingFangSC-Semibold;
+          font-size: 18px;
+          color: #383b40;
+          line-height: 20px;
+          font-weight: 700;
+          margin-bottom: 8px;
+        }
+
+        .other {
+          height: 14px;
+          font-family: PingFangSC-Medium;
+          font-size: 14px;
+          color: #383b40;
+          line-height: 14px;
+          font-weight: 700;
+
+          span {
+            height: 14px;
+            font-family: PingFangSC-Regular;
+            font-size: 14px;
+            color: #7e8288;
+            line-height: 14px;
+            font-weight: 400;
+            margin-right: 16px;
+          }
+        }
+      }
+
+      .right {
+        display: flex;
+        justify-content: space-between;
+      }
+    }
+  }
+
+  .four {
+    margin: 0 0 10px 0;
+
+    .four_1 {
+      width: 100%;
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 2px solid #2374ff;
+      margin: 20px 0;
+      background: #f7f7f7;
+
+      p {
+        float: left;
+        padding: 0 20px;
+        height: 40px;
+        border-bottom: 2px solid #2374ff;
+        color: #fff;
+        font-size: 18px;
+        background: #2374ff;
+      }
+    }
+  }
+  .pointer {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-family: PingFangSC-Regular;
+    font-size: 14px;
+    color: #7e8288;
+    margin: 10px 0 0 0;
+    .money {
+      margin: 0 10px 0 0;
+      span {
+        font-family: PingFangSC-Semibold;
+        font-size: 20px;
+        color: #e94643;
+        line-height: 20px;
+      }
+    }
+  }
+}
+</style>

+ 349 - 0
src/views/project/index.vue

@@ -0,0 +1,349 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight">
+        <el-col :span="24" class="idemand">
+          <el-image class="image" :src="demand" fit="fill" />
+        </el-col>
+        <div class="w_1200">
+          <el-col :span="24" class="one">
+            <el-row class="one_1" v-for="(val, indexs) in searchList" :key="indexs">
+              <el-col :span="3" class="left">{{ val.title }}</el-col>
+              <el-col :span="21" class="right">
+                <a-button
+                  class="title"
+                  v-for="(item, index) in val.list"
+                  :key="index"
+                  type="link"
+                  size="samll"
+                >
+                  {{ item.label }}
+                </a-button>
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="24" class="two">
+            <a-list :loading="loading" :grid="{ gutter: 14, column: 4 }" :data-source="list">
+              <template #renderItem="{ item }">
+                <el-col :span="24" class="list">
+                  <el-col :span="24" class="name textOver">
+                    <el-tooltip effect="dark" :content="item.name" placement="top">
+                      {{ item.name || '暂无名称' }}
+                    </el-tooltip>
+                  </el-col>
+                  <el-col :span="24" class="two_1">
+                    <span>服务机构: </span>{{ item.unit || '暂无服务机构' }}
+                  </el-col>
+                  <el-col :span="24" class="two_1">
+                    <span>项目编号: </span>{{ item.no || '暂无项目编号' }}
+                  </el-col>
+                  <el-col :span="24" class="two_1">
+                    <span>发布日期: </span>{{ item.notice_time || '暂无发布日期' }}
+                  </el-col>
+                  <el-col :span="24" class="bottom">
+                    <div class="status">挂牌公告</div>
+                    <div class="button" @click="toView">查看详情</div>
+                  </el-col>
+                </el-col>
+              </template>
+            </a-list>
+          </el-col>
+          <el-col :span="24" class="thr">
+            <el-pagination
+              background
+              layout="total, prev, pager, next"
+              :page-sizes="[10, 20, 50, 100, 200]"
+              :total="total"
+              :page-size="limit"
+              v-model:current-page="currentPage"
+              @current-change="changePage"
+              @size-change="sizeChange"
+            >
+            </el-pagination>
+          </el-col>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+const $checkRes = inject('$checkRes')
+import { get } from 'lodash-es'
+// 接口
+import { ProjectStore } from '@/store/api/platform/project'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = ProjectStore()
+const dictDataStore = DictDataStore()
+// 图片引入
+import demand from '@/assets/innovation.png'
+// 路由
+const router = useRouter()
+// 加载中
+const loading = ref(false)
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
+// 字典表
+const fieldList = ref([])
+const searchList = ref([
+  {
+    title: '技术领域',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '先进制造' },
+      { value: '2', label: '地球、空间与海洋' },
+      { value: '3', label: '新材料' },
+      { value: '4', label: '新能源与节能' },
+      { value: '5', label: '核应用技术' },
+      { value: '6', label: '环境保护' },
+      { value: '7', label: '现代交通' },
+      { value: '8', label: '现代农业' },
+      { value: '9', label: '生物医药与医疗器械' },
+      { value: '10', label: '电子信息' },
+      { value: '11', label: '航空航天' }
+    ]
+  },
+  {
+    title: '合作方式',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '技术开发' },
+      { value: '2', label: '技术咨询' },
+      { value: '3', label: '技术服务' },
+      { value: '4', label: '许可转让' },
+      { value: '4', label: '完全转让' },
+      { value: '4', label: '技术入股' },
+      { value: '4', label: '合作开发' },
+      { value: '4', label: '其他' }
+    ]
+  },
+  {
+    title: '需求地区',
+    list: [
+      { value: '0', label: '北京市' },
+      { value: '1', label: '天津市' },
+      { value: '2', label: '河北省' },
+      { value: '3', label: '山西省' },
+      { value: '4', label: '内蒙古自治区' },
+      { value: '5', label: '辽宁省' },
+      { value: '6', label: '吉林省' },
+      { value: '7', label: '上海市' },
+      { value: '8', label: '江苏省' },
+      { value: '9', label: '浙江省' },
+      { value: '10', label: '安徽省' },
+      { value: '11', label: '福建省' },
+      { value: '0', label: '江西省' },
+      { value: '1', label: '山东省' },
+      { value: '2', label: '河南省' },
+      { value: '3', label: '湖北省' },
+      { value: '4', label: '湖南省' },
+      { value: '5', label: '广东省' },
+      { value: '6', label: '广西壮族自治区' },
+      { value: '7', label: '海南省' },
+      { value: '8', label: '重庆市' },
+      { value: '9', label: '四川省' },
+      { value: '10', label: '贵州省' },
+      { value: '11', label: '云南省' }
+    ]
+  },
+  {
+    title: '需求状态',
+    list: [
+      { value: '0', label: '全部' },
+      { value: '1', label: '洽谈中' },
+      { value: '2', label: '已结束' }
+    ]
+  }
+])
+// 查看
+const toView = (item) => {
+  router.push({ path: '/demand/detail', query: { id: item.id || item._id } })
+}
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search({ skip, limit })
+  loading.value = false
+})
+const searchOther = async () => {
+  let result
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+}
+const search = async (query = { skip: 0, limit }) => {
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    ...searchForm.value,
+    is_use: '0',
+    status: '1'
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  let res
+  if (model == 'field') res = fieldList.value.find((f) => f.value == data)
+  return get(res, 'label')
+}
+// 地区显示
+const getArea = (data) => {
+  if (data) return data.join(',')
+}
+const currentPage = ref(1)
+// 分页
+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 lang="scss" scoped>
+.main {
+  background: rgb(248, 248, 248);
+
+  .idemand {
+    .image {
+      width: 100%;
+      height: 200px;
+    }
+  }
+
+  .one {
+    background: #ffffff;
+    border-radius: 10px;
+    padding: 15px;
+    margin: 10px 0 0 0;
+
+    .one_1 {
+      display: flex;
+      align-items: center;
+      padding: 10px 0;
+      border-bottom: #9d9898 1px dashed;
+
+      .left {
+        background: #778cb3;
+        border-radius: 15px;
+        line-height: 26px;
+        color: #fff;
+        text-align: center;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        font-family: PingFangSC-Medium;
+      }
+
+      .right {
+        .title {
+          color: #666;
+          font-size: 14px;
+          line-height: 15px;
+          margin: 0 10px;
+          display: inline-block;
+          overflow: hidden;
+          text-decoration: none;
+        }
+
+        .title:hover {
+          color: #2374ff;
+        }
+      }
+    }
+  }
+
+  .two {
+    margin: 15px;
+    .list {
+      background: #fff;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
+      border-radius: 2px;
+      width: 285px;
+      height: 195px;
+      margin-bottom: 15px;
+      cursor: pointer;
+      transition: all 0.3s;
+      padding: 20px;
+
+      .name {
+        font-size: 16px;
+        color: #121834;
+        height: 16px;
+        line-height: 13px;
+        font-weight: 500;
+        margin: 10px 0;
+      }
+
+      .name:hover {
+        color: #2374ff;
+      }
+
+      .two_1 {
+        font-size: 12px;
+        text-align: justify;
+        line-height: 12px;
+        font-weight: 400;
+        letter-spacing: 0;
+        color: #8f97a3;
+        margin-top: 15px;
+
+        span:last-child {
+          color: #525a68;
+        }
+      }
+
+      .bottom {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-top: 20px;
+
+        .status {
+          padding: 5px 12px;
+          font-size: 12px;
+          color: #2374ff;
+          line-height: 12px;
+          font-weight: 400;
+          background: rgba(35, 116, 255, 0.05);
+          border-radius: 16px;
+        }
+
+        .button {
+          font-size: 12px;
+          color: #fff;
+          text-align: center;
+          line-height: 12px;
+          font-weight: 500;
+          padding: 5px 12px;
+          background: #2374ff;
+          box-shadow: 0 3px 6px 0 rgba(35, 116, 255, 0.1);
+          border-radius: 2px;
+        }
+      }
+    }
+
+    .list:hover {
+      background: #f0f7ff;
+      box-shadow: 0 0 16px rgba(205, 205, 205, 0.6);
+    }
+  }
+
+  .thr {
+    display: flex;
+    flex-direction: row-reverse;
+    padding: 20px;
+  }
+}
+</style>