Преглед на файлове

Merge branch 'master' of http://git.cc-lotus.info/Information/cxyy-web

lrf преди 8 месеца
родител
ревизия
a51c972e73

+ 1 - 1
src/components/WangEditor/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="editor-wrapper">
+  <div class="editor-wrapper" style="width: 100%; border: 1px solid #dcdfe6; margin: 0 0 10px 0">
     <!-- 工具栏 -->
     <Toolbar id="toolbar-container" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
     <!-- 编辑器 -->

+ 2 - 1
src/components/custom/custom-form.vue

@@ -54,7 +54,7 @@
       </el-col>
       <el-col :span="24" label="" class="btn" v-if="useSave">
         <slot name="submit">
-          <el-button type="warning" @click="draftSave(formRef)">{{ submitDraft || '保存草稿' }}</el-button>
+          <el-button type="warning" v-if="DraftSave" @click="draftSave(formRef)">{{ submitDraft || '保存草稿' }}</el-button>
           <el-button type="primary" @click="save(formRef)">{{ submitText || '保存并提交审核' }}</el-button>
         </slot>
       </el-col>
@@ -72,6 +72,7 @@ const props = defineProps({
   fields: { type: Array, default: () => [] },
   submitText: { type: String },
   submitDraft: { type: String },
+  DraftSave: { type: Boolean, default: true },
   useSave: { type: Boolean, default: true },
   span: { type: Number, default: 24 } // 限制两侧的距离,24就是整行全用
 })

+ 1 - 0
src/layout/index.vue

@@ -112,6 +112,7 @@ watch(
     height: 100%;
     border-top-right-radius: 40px;
     border-bottom-right-radius: 40px;
+    overflow: hidden;
     .left_1 {
       cursor: default;
       padding-top: 20px;

+ 8 - 1
src/layout/site.js

@@ -146,5 +146,12 @@ export const menuList3 = [
   { id: 4, name: '成果管理', route_name: 'achievement', path: '/achievement', icon: 'Medal' },
   { id: 5, name: '需求管理', route_name: 'demand', path: '/demand', icon: 'DataBoard' },
   { id: 6, name: '我的收藏', route_name: 'collect', path: '/collect', icon: 'Collection' },
-  { id: 7, name: '活动管理', route_name: 'match', path: '/match', icon: 'Suitcase' }
+  { id: 7, name: '活动管理', route_name: 'sign', path: '/sign', icon: 'Suitcase' },
+  { id: 8, name: '行业动态', route_name: 'news2', path: '/news2', icon: 'Message' },
+  { id: 9, name: '供给管理', route_name: 'supply', path: '/supply', icon: 'Notification' },
+  { id: 10, name: '中试管理', route_name: 'footplate', path: '/footplate', icon: 'TakeawayBox' },
+  { id: 11, name: '赛事管理', route_name: 'match', path: '/match', icon: 'CollectionTag' },
+  // { id: 12, name: '行研产研', route_name: 'journal', path: '/journal', icon: 'Reading' },
+  { id: 13, name: '项目管理', route_name: 'project', path: '/project', icon: 'Trophy' },
+  { id: 14, name: '修改密码', route_name: 'password', path: '/password', icon: 'Lock' }
 ]

+ 1 - 1
src/views/center/achievement.vue

@@ -157,7 +157,7 @@ const search = async (query = { skip, limit }) => {
     user: user.value.id,
     ...searchForm.value
   }
-  const res = await store.list(info)
+  const res = await store.query(info)
   if (res.errcode == '0') {
     list.value = res.data
     total.value = res.total

+ 1 - 1
src/views/center/attestation.vue

@@ -81,7 +81,6 @@ const searchOther = async () => {
   // 角色
   result = await roleStore.query({ is_use: '0' })
   if ($checkRes(result)) {
-    activeName.value = result.data[0].code
     const list = []
     for (const val of result.data) {
       const role = user.value.role.find((i) => i == val.code)
@@ -90,6 +89,7 @@ const searchOther = async () => {
       if (val.code != 'Admin' && val.code != 'User') list.push(val)
     }
     roleList.value = list
+    activeName.value = list[0].code
   }
   // 性别
   result = await dictDataStore.query({ code: 'gender', is_use: '0' })

+ 1 - 1
src/views/center/demand.vue

@@ -148,7 +148,7 @@ const search = async (query = { skip, limit }) => {
     user: user.value.id,
     ...searchForm.value
   }
-  const res = await store.list(info)
+  const res = await store.query(info)
   if (res.errcode == '0') {
     list.value = res.data
     total.value = res.total

+ 266 - 0
src/views/center/footplate.vue

@@ -0,0 +1,266 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_left" @click="toAdd">发布平台</div>
+          <div class="one_right">
+            <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="平台名称" />
+            <el-table-column prop="build" align="center" label="建设主体" width="180" />
+            <el-table-column prop="operate" align="center" label="运营主体" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
+                <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="3" listType="picture-card" url="/files/web/cxyy_footplate/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #industry>
+              <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" style="width: 100%" />
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { FootplateStore } from '@/store/api/platform/footplate'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+import { SectorStore } from '@/store/api/platform/sector'
+import { RegionStore } from '@/store/api/system/region'
+const regionStore = RegionStore()
+const store = FootplateStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const sectorStore = SectorStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const cityList = ref([])
+const tagsList = ref([])
+const sectorList = ref([])
+
+const form = ref({ file: [] })
+const dialog = ref({ type: '1', show: false, title: '发布平台' })
+const formFields = ref([
+  { label: '封面', model: 'file', custom: true },
+  { label: '名称', model: 'name' },
+  { label: '标签', model: 'tags', custom: true },
+  { label: '所属产业', model: 'industry', type: 'select' },
+  { label: '建设主体', model: 'build' },
+  { label: '运营主体', model: 'operate' },
+  { label: '服务产业领域', model: 'field' },
+  { label: '所在地区', model: 'area', custom: true },
+  { label: '地址', model: 'address', type: 'textarea' },
+  { label: '联系人', model: 'contacts' },
+  { label: '联系电话', model: 'phone' },
+  { label: '是否启用', model: 'is_use', type: 'radio' },
+  { label: '简介', model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入平台名称', trigger: 'blur' }] })
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+  // 城市
+  result = await regionStore.area({ level: 'province', code: 22 })
+  if ($checkRes(result)) cityList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: '发布平台' }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改平台' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+const toClose = () => {
+  form.value = { file: [] }
+  dialog.value = { show: false }
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 227 - 0
src/views/center/journal.vue

@@ -0,0 +1,227 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_left" @click="toAdd">发布行研产研</div>
+          <div class="one_right">
+            <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="行研产研名称" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
+                <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/cxyy_journal/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { JournalStore } from '@/store/api/platform/journal'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = JournalStore()
+const dictDataStore = DictDataStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+
+const form = ref({ file: [] })
+const dialog = ref({ type: '1', show: false, title: '发布行研产研' })
+const formFields = ref([
+  { label: '封面', model: 'file', custom: true },
+  { label: '名称', model: 'name' },
+  { label: '是否公开', model: 'is_use', type: 'radio' },
+  { label: '简介', model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入行研产研名称', trigger: 'blur' }] })
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: '发布行研产研' }
+}
+// 修改
+const toEdit = (data) => {
+  data.time = [data.start_time, data.end_time]
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改行研产研' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+const toClose = () => {
+  form.value = { file: [] }
+  dialog.value = { show: false }
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 313 - 25
src/views/center/match.vue

@@ -3,21 +3,35 @@
     <el-row>
       <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
         <el-col :span="24" class="one">
-          <el-radio-group size="large" v-model="searchForm.type">
-            <el-radio-button label="活动报名" value="1" />
-            <el-radio-button label="大赛报名" value="2" />
-          </el-radio-group>
+          <div class="one_1">
+            <el-radio-group size="large" v-model="searchForm.form" @change="search">
+              <el-radio-button v-for="(item, index) in formList" :key="index" :label="item.label" :value="item.value" />
+            </el-radio-group>
+          </div>
+          <div class="one_2">
+            <div class="one_left" @click="toAdd">发布赛事</div>
+            <div class="one_right">
+              <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+            </div>
+          </div>
         </el-col>
         <el-col :span="24" class="two">
           <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
             <template #empty>
               <el-empty description="暂无数据" />
             </template>
-            <el-table-column prop="name" align="center" label="标题" />
-            <el-table-column prop="time" align="center" label="报名时间" width="180" />
-            <el-table-column prop="status" align="center" label="状态" width="180" />
-            <el-table-column align="center" label="操作" width="180">
+            <el-table-column prop="name" align="center" label="赛事名称" />
+            <el-table-column prop="start_time" align="center" label="开始时间" width="180" />
+            <el-table-column prop="end_time" align="center" label="结束时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="250">
               <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="warning" size="mini" @click="toSign(row)" style="margin-right: 10px">报名</el-link>
                 <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
                 <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
               </template>
@@ -29,24 +43,165 @@
         </el-col>
       </el-col>
     </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" listType="picture-card" url="/files/web/cxyy_match/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #industry>
+              <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+            </template>
+            <template #match_status>
+              <el-option v-for="i in matchList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #match_type>
+              <el-radio v-for="i in matchTypeList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #type>
+              <el-option v-for="i in typeList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #form>
+              <el-option v-for="i in formList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #rules>
+              <div class="rules">
+                <custom-form v-model="form.rules" :fields="rulesFields" :useSave="false">
+                  <template #rules1>
+                    <WangEditor v-model="form.rules.rules1" />
+                  </template>
+                  <template #rules2>
+                    <WangEditor v-model="form.rules.rules2" />
+                  </template>
+                  <template #rules3>
+                    <WangEditor v-model="form.rules.rules3" />
+                  </template>
+                  <template #rules4>
+                    <WangEditor v-model="form.rules.rules4" />
+                  </template>
+                  <template #rules5>
+                    <WangEditor v-model="form.rules.rules5" />
+                  </template>
+                  <template #rules6>
+                    <WangEditor v-model="form.rules.rules6" />
+                  </template>
+                  <template #rules7>
+                    <WangEditor v-model="form.rules.rules7" />
+                  </template>
+                  <template #rules8>
+                    <WangEditor v-model="form.rules.rules8" />
+                  </template>
+                  <template #rules9>
+                    <WangEditor v-model="form.rules.rules9" />
+                  </template>
+                  <template #rules10>
+                    <WangEditor v-model="form.rules.rules10" />
+                  </template>
+                  <template #rules11>
+                    <WangEditor v-model="form.rules.rules11" />
+                  </template>
+                  <template #rules12>
+                    <WangEditor v-model="form.rules.rules12" />
+                  </template>
+                </custom-form>
+              </div>
+            </template>
+            <template #brief>
+              <WangEditor v-model="form.brief" />
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <sign :matchForm="form"></sign>
+        </el-col>
+      </el-row>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+// 组件
+import sign from './parts/sign.vue'
+
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
 import { UserStore } from '@/store/user'
 const userStore = UserStore()
 const user = computed(() => userStore.user)
+// 接口
+import { MatchStore } from '@/store/api/platform/match'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+import { SectorStore } from '@/store/api/platform/sector'
+const store = MatchStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const sectorStore = SectorStore()
 // 加载中
 const loading = ref(false)
-// 路由
-const router = useRouter()
+
+const searchForm = ref({ form: '0' })
 // 列表
 const list = ref([])
 let skip = 0
 let limit = inject('limit')
 const total = ref(0)
 const currentPage = ref(1)
-const searchForm = ref({ type: '1' })
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const typeList = ref([])
+const matchList = ref([])
+const formList = ref([])
+const tagsList = ref([])
+const sectorList = ref([])
+const matchTypeList = ref([])
+
+const form = ref({ time: [], rules: {} })
+const dialog = ref({ type: '1', show: false, title: '发布赛事' })
+const formFields = ref([
+  { label: '封面', model: 'file', custom: true },
+  { label: '赛事名称', model: 'name' },
+  { label: '标签', model: 'tags', custom: true },
+  { label: '类型', model: 'type', type: 'select' },
+  { label: '赛事类型', model: 'match_type', type: 'radio' },
+  { label: '路由', model: 'href' },
+  { label: '组织单位', model: 'work' },
+  { label: '所属产业', model: 'industry', type: 'select' },
+  { label: '类别', model: 'form', type: 'select' },
+  { label: '奖金(万元)', model: 'money' },
+  { label: '有效期', model: 'time', type: 'daterange' },
+  { label: '是否启用', model: 'is_use', type: 'radio' },
+  { model: 'rules', custom: true },
+  { label: '简介', model: 'brief', custom: true },
+  { label: '赛事状态', model: 'match_status', type: 'select' }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入赛事名称', trigger: 'blur' }] })
+// 赛事规则
+const rulesFields = ref([
+  { label: '大赛背景', model: 'rules1', custom: true },
+  { label: '大赛主题和目标', model: 'rules2', custom: true },
+  { label: '大赛基本情况介绍', model: 'rules3', custom: true },
+  { label: '赛题任务', model: 'rules4', custom: true },
+  { label: '赛程安排', model: 'rules5', custom: true },
+  { label: '赛制阶段', model: 'rules6', custom: true },
+  { label: '参赛资格', model: 'rules7', custom: true },
+  { label: '参赛报名', model: 'rules8', custom: true },
+  { label: '奖项设置与奖励办法', model: 'rules9', custom: true },
+  { label: '组织单位', model: 'rules10', custom: true },
+  { label: '赛事联络', model: 'rules11', custom: true },
+  { label: '赛事交流', model: 'rules12', custom: true }
+])
 // 请求
 onMounted(async () => {
   loading.value = true
@@ -55,21 +210,135 @@ onMounted(async () => {
   loading.value = false
 })
 const search = async (query = { skip, limit }) => {
-  // skip = query.skip
-  // limit = query.limit
-  // const info = {
-  //   skip: query.skip,
-  //   limit: query.limit,
-  //   user: user.value.id,
-  // ...searchForm.value
-  // }
-  // const res = await store.list(info)
-  // if (res.errcode == '0') {
-  //   list.value = res.data
-  //   total.value = res.total
-  // }
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 类型
+  result = await dictDataStore.query({ code: 'matchType', is_use: '0' })
+  if ($checkRes(result)) typeList.value = result.data
+  // 赛事类型
+  result = await dictDataStore.query({ code: 'activeType', is_use: '0' })
+  if ($checkRes(result)) matchTypeList.value = result.data
+  // 类别
+  result = await dictDataStore.query({ code: 'matchForm', is_use: '0' })
+  if ($checkRes(result)) formList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 赛事状态
+  result = await dictDataStore.query({ code: 'matchStatus', is_use: '0' })
+  if ($checkRes(result)) matchList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: '发布赛事' }
+}
+// 修改
+const toEdit = (data) => {
+  data.time = [data.start_time, data.end_time]
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改赛事' }
+}
+// 报名
+const toSign = (data) => {
+  form.value = data
+  dialog.value = { type: '2', show: true, title: '报名管理' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', user: user.value.id }
+  if (data.time && data.time.length > 1) {
+    data.start_time = data.time[0]
+    data.end_time = data.time[1]
+  }
+  delete data.time
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', user: user.value.id }
+  if (data.time && data.time.length > 1) {
+    data.start_time = data.time[0]
+    data.end_time = data.time[1]
+  }
+  delete data.time
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+const toClose = () => {
+  form.value = { time: [], rules: {} }
+  dialog.value = { show: false }
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
 }
-const searchOther = async () => {}
 // 分页
 const changePage = (page = currentPage.value) => {
   search({ skip: (page - 1) * limit, limit: limit })
@@ -84,6 +353,25 @@ const sizeChange = (limits) => {
 .main {
   .one {
     margin: 0 0 10px 0;
+    .one_1 {
+      height: 50px;
+    }
+    .one_2 {
+      height: 50px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .one_left {
+        background: #1875df;
+        padding: 0 10px;
+        height: 30px;
+        color: #fff;
+        line-height: 30px;
+        text-align: center;
+        font-size: 16px;
+        cursor: default;
+      }
+    }
   }
   .thr {
     display: flex;

+ 248 - 0
src/views/center/news2.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_left" @click="toAdd">发布行业动态</div>
+          <div class="one_right">
+            <el-input v-model="searchForm.title" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="title" align="center" label="标题" />
+            <el-table-column prop="time" align="center" label="发布时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
+                <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #logo>
+              <custom-upload model="logo" :list="form.logo" :limit="1" url="/files/web/cxyy_news/upload" @change="onUpload" listType="picture-card"></custom-upload>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #content>
+              <WangEditor v-model="form.content" />
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import moment from 'moment'
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { NewsStore } from '@/store/api/platform/news'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+const store = NewsStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const tagsList = ref([])
+
+const form = ref({ logo: [], type: '2' })
+const dialog = ref({ type: '1', show: false, title: '发布行业动态' })
+const formFields = ref([
+  { label: '封面', model: 'logo', custom: true },
+  { label: '标题', model: 'title' },
+  { label: '标签', model: 'tags', custom: true },
+  { label: '发布人', model: 'person', options: { disabled: true } },
+  { label: '发布时间', model: 'time', type: 'date', options: { disabled: true } },
+  { label: '浏览量', model: 'number', options: { disabled: true } },
+  { label: '是否公开', model: 'is_use', type: 'radio' },
+  { label: '内容', model: 'content', custom: true }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入标题', trigger: 'blur' }] })
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    type: '2',
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  form.value = { status: '0', type: '2', user: user.value.id, time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  dialog.value = { type: '1', show: true, title: '发布行业动态' }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改行业动态' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', type: '2', user: user.value.id, time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', type: '2', user: user.value.id, time: moment().format('YYYY-MM-DD'), person: user.value.nick_name }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+const toClose = () => {
+  form.value = { logo: [], type: '2' }
+  dialog.value = { show: false }
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 1 - 1
src/views/center/notice.vue

@@ -49,7 +49,7 @@ const search = async (query = { skip, limit }) => {
   //   limit: query.limit,
   //   user: user.value.id
   // }
-  // const res = await store.list(info)
+  // const res = await store.query(info)
   // if (res.errcode == '0') {
   //   list.value = res.data
   //   total.value = res.total

+ 205 - 0
src/views/center/parts/sign.vue

@@ -0,0 +1,205 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_right">
+            <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="姓名" />
+            <el-table-column prop="phone" align="center" label="手机号" width="180" />
+            <el-table-column prop="time" align="center" label="报名时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '0'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toView(row)" style="margin-right: 10px">查看</el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :useSave="false">
+            <template #cardType>
+              <el-option v-for="i in cardTypeList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+        <el-col :span="24" v-if="dialog.type == '2'">
+          <custom-form v-model="examForm" :fields="examFormFields" :rules="examRules" @save="toExamSave" :DraftSave="false" submitText="保存">
+            <template #status>
+              <el-option v-for="i in statusList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { SignStore } from '@/store/api/platform/sign'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = SignStore()
+const dictDataStore = DictDataStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+
+const props = defineProps({
+  matchForm: { type: Object, default: () => {} }
+})
+
+const { matchForm } = toRefs(props)
+
+// 字典表
+const cardTypeList = ref([])
+const statusList = ref([])
+
+const form = ref({})
+const dialog = ref({ type: '1', show: false, title: '发布需求' })
+const formFields = ref([
+  { label: '姓名', model: 'name' },
+  { label: '手机号', model: 'phone' },
+  { label: '证件类型', model: 'cardType', type: 'select' },
+  { label: '证件号码', model: 'card' },
+  { label: '报名时间', model: 'time' },
+  { label: '微信/QQ', model: 'communication' },
+  { label: '电子邮箱', model: 'email' },
+  { label: '备注', model: 'remark', type: 'textarea' }
+])
+// 审核
+const examFormFields = [{ label: '状态', model: 'status', type: 'select' }]
+const examRules = reactive({
+  status: [{ required: true, message: '请选项状态', trigger: 'blur' }]
+})
+const examForm = ref({})
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    match: matchForm.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 证件类型
+  result = await dictDataStore.query({ code: 'cardType', is_use: '0' })
+  if ($checkRes(result)) cardTypeList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 查看
+const toView = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '查看报名' }
+}
+// 审核
+const toExam = (data) => {
+  examForm.value = data
+  dialog.value = { type: '2', show: true, title: '报名审核' }
+}
+// 审核保存
+const toExamSave = async () => {
+  const data = cloneDeep(examForm.value)
+  let res = await store.update({ id: data.id, status: data.status })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 90 - 0
src/views/center/password.vue

@@ -0,0 +1,90 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <el-form label-position="top" ref="ruleFormRef" :model="form" :rules="rules" label-width="80px" class="form">
+            <el-form-item label="新密码" prop="password">
+              <el-input size="large" v-model="form.password" type="password" show-password placeholder="请输入新密码">
+                <template #prefix>
+                  <el-icon>
+                    <Unlock />
+                  </el-icon>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item label="确认密码" prop="refpassword">
+              <el-input size="large" v-model="form.refpassword" type="password" show-password placeholder="请再次确认输入密码">
+                <template #prefix>
+                  <el-icon>
+                    <Unlock />
+                  </el-icon>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-col :span="24" class="button">
+              <el-button type="primary" @click="submit(ruleFormRef)">修改密码</el-button>
+            </el-col>
+          </el-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { cloneDeep } from 'lodash-es'
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+
+import { LoginStore } from '@/store/api/login'
+const store = LoginStore()
+const router = useRouter()
+const $checkRes = inject('$checkRes')
+// 加载中
+const loading = ref(false)
+const ruleFormRef = ref()
+const form = ref({})
+const validatePassword = (rule, value, callback) => {
+  if (!value) {
+    return callback(new Error('请输入确认密码'))
+  }
+  if (form.value.password !== value) {
+    callback(new Error('两次输入的密码不一致'))
+  } else {
+    callback()
+  }
+}
+const rules = reactive({
+  password: [{ required: true, message: '请输入新密码', trigger: 'blur' }],
+  refpassword: [{ required: true, validator: validatePassword, trigger: 'blur' }]
+})
+// 修改密码
+const submit = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      const data = cloneDeep(form.value)
+      const res = await store.rp({
+        type: 'User',
+        id: user.value.id,
+        password: data.password
+      })
+      if ($checkRes(res, true)) {
+        userStore.logOut()
+        router.push({ path: '/login', query: { status: '1' } })
+      }
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+</script>
+<style scoped lang="scss">
+.main {
+  .button {
+    text-align: center;
+  }
+}
+</style>

+ 304 - 0
src/views/center/project.vue

@@ -0,0 +1,304 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_left" @click="toAdd">发布项目</div>
+          <div class="one_right">
+            <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="项目名称" />
+            <el-table-column prop="time" align="center" label="发布时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
+                <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+            <template #industry>
+              <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+            </template>
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #field>
+              <el-option v-for="i in fieldList" :key="i.id" :label="i.label" :value="i.label"></el-option>
+            </template>
+            <template #type>
+              <el-option v-for="i in typeList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #maturity>
+              <el-option v-for="i in maturityList" :key="i.id" :label="i.label" :value="i.label"></el-option>
+            </template>
+            <template #skill>
+              <el-option v-for="i in skillList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #file>
+              <custom-upload model="file" :list="form.file" :limit="1" url="/files/web/cxyy_project/upload" @change="onUpload"></custom-upload>
+            </template>
+            <template #cooperate>
+              <el-option v-for="i in cooperateList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" style="width: 100%" />
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { ProjectStore } from '@/store/api/platform/project'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+import { SectorStore } from '@/store/api/platform/sector'
+import { RegionStore } from '@/store/api/system/region'
+const regionStore = RegionStore()
+const store = ProjectStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const sectorStore = SectorStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const fieldList = ref([])
+const typeList = ref([])
+const maturityList = ref([])
+const skillList = ref([])
+const cityList = ref([])
+const cooperateList = ref([])
+const tagsList = ref([])
+const sectorList = ref([])
+
+const form = ref({ file: [] })
+const dialog = ref({ type: '1', show: false, title: '发布项目' })
+const formFields = ref([
+  { label: '项目名称', model: 'name' },
+  { label: '标签', model: 'tags', custom: true },
+  { label: '所属产业', model: 'industry', type: 'select' },
+  { label: '行业分类', model: 'type', type: 'select' },
+  { label: '成熟度', model: 'maturity', type: 'select' },
+  { label: '技术类型', model: 'skill', type: 'select' },
+  { label: '行业领域', model: 'field', type: 'select' },
+  { label: '合作类型', model: 'cooperate', type: 'select' },
+  { label: '项目地区', model: 'area', custom: true },
+  { label: '发布时间', model: 'time', type: 'date' },
+  { label: '项目主体', model: 'main' },
+  { label: '项目进展', model: 'progress' },
+  { label: '跟踪支持单位', model: 'track_unit' },
+  { label: '项目来源', model: 'source' },
+  { label: '是否启用', model: 'is_use', type: 'radio' },
+  { label: '简介', model: 'brief', type: 'textarea' },
+  { label: '附件', model: 'file', custom: true }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }] })
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 行业分类
+  result = await dictDataStore.query({ code: 'industry', is_use: '0' })
+  if ($checkRes(result)) typeList.value = result.data
+  // 成熟度
+  result = await dictDataStore.query({ code: 'projectMaturity', is_use: '0' })
+  if ($checkRes(result)) maturityList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 技术类型
+  result = await dictDataStore.query({ code: 'technology', is_use: '0' })
+  if ($checkRes(result)) skillList.value = result.data
+  // 合作类型
+  result = await dictDataStore.query({ code: 'projectType', is_use: '0' })
+  if ($checkRes(result)) cooperateList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+  // 城市
+  result = await regionStore.area({ level: 'province', code: 22 })
+  if ($checkRes(result)) cityList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 上传图片
+const onUpload = (e) => {
+  const { model, value } = e
+  form.value[model] = value
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: '发布项目' }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改项目' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', user: user.value.id }
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+const toClose = () => {
+  form.value = { file: [] }
+  dialog.value = { show: false }
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>

+ 242 - 0
src/views/center/sign.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <el-radio-group size="large" v-model="searchForm.type">
+            <el-radio-button label="活动报名" value="1" />
+            <el-radio-button label="大赛报名" value="2" />
+          </el-radio-group>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="标题" />
+            <el-table-column prop="time" align="center" label="报名时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改报名信息</el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <el-form label-position="top" ref="ruleFormRef" :model="form" :rules="rules" label-width="80px" class="form">
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="姓名" prop="name">
+                  <el-input clearable v-model="form.name" placeholder="请输入姓名"> </el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="电话号" prop="phone">
+                  <el-input clearable v-model="form.phone" placeholder="请输入电话号"> </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="证件类型" prop="cardType">
+                  <el-select clearable v-model="form.cardType" placeholder="请选择证件类型">
+                    <el-option v-for="(item, index) in cardTypeList" :key="index" :label="item.label" :value="item.label" />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="证件号码" prop="card">
+                  <el-input clearable v-model="form.card" placeholder="请输入证件号码"> </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-row :gutter="20">
+              <el-col :span="12">
+                <el-form-item label="微信/QQ" prop="communication">
+                  <el-input clearable v-model="form.communication" placeholder="请输入微信/QQ"> </el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="电子邮箱" prop="email">
+                  <el-input clearable v-model="form.email" placeholder="请输入电子邮箱"> </el-input>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="备注" prop="brief">
+              <el-input v-model="form.remark" :autosize="{ minRows: 2, maxRows: 8 }" type="textarea" placeholder="请输入备注" />
+            </el-form-item>
+            <el-form-item>
+              <el-col :span="24" class="button">
+                <el-button type="primary" @click="submitForm(ruleFormRef)">确定</el-button>
+              </el-col>
+            </el-form-item>
+          </el-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import moment from 'moment'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { SignStore } from '@/store/api/platform/sign'
+import { DictDataStore } from '@/store/api/system/dictData'
+const store = SignStore()
+const dictDataStore = DictDataStore()
+// 加载中
+const loading = ref(false)
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+const searchForm = ref({ type: '1' })
+const dialog = ref({ type: '1', show: false, title: '修改报名信息' })
+const ruleFormRef = ref()
+const form = ref({})
+const validatePhoneNumber = (rule, value, callback) => {
+  const reg = /^1[3-9]\d{9}$/
+  if (!value) {
+    return callback(new Error('手机号不能为空'))
+  }
+  if (!reg.test(value)) {
+    return callback(new Error('请输入正确的手机号'))
+  }
+  callback()
+}
+const validateCardNumber = (rule, value, callback) => {
+  var reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
+  if (!value) {
+    return callback(new Error('证件号码不能为空'))
+  }
+  if (!reg.test(value)) {
+    return callback(new Error('请输入正确的证件号码'))
+  }
+  callback()
+}
+const rules = reactive({
+  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
+  phone: [{ required: true, validator: validatePhoneNumber, trigger: 'blur' }],
+  cardType: [{ required: true, message: '请选择证件类型', trigger: 'change' }],
+  card: [{ required: true, validator: validateCardNumber, trigger: 'change' }],
+  remark: [{ required: true, message: '请输入备注', trigger: 'blur' }]
+})
+// 字典表
+const statusList = ref([])
+const cardTypeList = ref([])
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id
+    // ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 证件类型
+  result = await dictDataStore.query({ code: 'cardType', is_use: '0' })
+  if ($checkRes(result)) cardTypeList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改报名信息' }
+}
+// 报名
+const submitForm = async (formEl) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      ElMessageBox.confirm(`您确定修改该数据将会重新审核?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+        .then(async () => {
+          const data = cloneDeep(form.value)
+          const other = { time: moment().format('YYYY-MM-DD'), status: '0' }
+          let res = await store.update({ ...data, ...other })
+          if ($checkRes(res, true)) {
+            await toClose()
+            await search()
+          } else {
+            ElMessage({ message: `${res.errmsg}`, type: 'error' })
+          }
+        })
+        .catch(() => {})
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = { show: false }
+}
+// 分页
+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 {
+  .one {
+    margin: 0 0 10px 0;
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+  .button {
+    text-align: center;
+  }
+}
+</style>

+ 293 - 0
src/views/center/supply.vue

@@ -0,0 +1,293 @@
+<template>
+  <div class="index">
+    <el-row>
+      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
+        <el-col :span="24" class="one">
+          <div class="one_left" @click="toAdd">发布供给</div>
+          <div class="one_right">
+            <el-input v-model="searchForm.name" style="width: 250px" size="large" placeholder="搜索" @change="search" :suffix-icon="Search" />
+          </div>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-table :data="list" style="width: 100%" size="large" :header-cell-style="{ backgroundColor: '#edf3ff' }">
+            <template #empty>
+              <el-empty description="暂无数据" />
+            </template>
+            <el-table-column prop="name" align="center" label="供给名称" />
+            <el-table-column prop="start_time" align="center" label="开始时间" width="180" />
+            <el-table-column prop="end_time" align="center" label="结束时间" width="180" />
+            <el-table-column prop="status" align="center" label="状态" width="180">
+              <template #default="scope">
+                <div>{{ getDict(scope.row.status, 'status') }}</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作" width="180">
+              <template #default="{ row }">
+                <el-link v-if="row.status == '-2'" :underline="false" type="warning" size="mini" @click="toExam(row)" style="margin-right: 10px">提交审核</el-link>
+                <el-link :underline="false" type="primary" size="mini" @click="toEdit(row)" style="margin-right: 10px">修改</el-link>
+                <el-link :underline="false" type="danger" size="mini" @click="toDelete(row)"> 删除 </el-link>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+        <el-col :span="24" class="thr">
+          <el-pagination background layout="prev, pager, next" :total="total" :page-size="limit" v-model:current-page="currentPage" @current-change="changePage" @size-change="sizeChange" />
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog v-model="dialog.show" :title="dialog.title" :destroy-on-close="false" @close="toClose">
+      <el-row>
+        <el-col :span="24" v-if="dialog.type == '1'">
+          <custom-form v-model="form" :fields="formFields" :rules="rules" @save="toSave" @draftSave="toDraftSave">
+            <template #is_use>
+              <el-radio v-for="i in isUseList" :key="i.id" :label="i.value">{{ i.label }}</el-radio>
+            </template>
+            <template #field>
+              <el-option v-for="i in fieldList" :key="i.id" :label="i.label" :value="i.label"></el-option>
+            </template>
+            <template #urgent>
+              <el-option v-for="i in urgentList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #method>
+              <el-option v-for="i in methodList" :key="i.id" :label="i.label" :value="i.value"></el-option>
+            </template>
+            <template #industry>
+              <el-option v-for="i in sectorList" :key="i.id" :label="i.title" :value="i.title"></el-option>
+            </template>
+            <template #area>
+              <el-cascader v-model="form.area" :props="{ value: 'name', label: 'name' }" :options="cityList" style="width: 100%" />
+            </template>
+            <template #tags>
+              <el-select v-model="form.tags" multiple filterable allow-create default-first-option :reserve-keyword="false" placeholder="请选择标签" style="width: 100%">
+                <el-option v-for="item in tagsList" :key="item.id" :label="item.title" :value="item.title" />
+              </el-select>
+            </template>
+          </custom-form>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { Search } from '@element-plus/icons-vue'
+import { cloneDeep, get } from 'lodash-es'
+const $checkRes = inject('$checkRes')
+import { UserStore } from '@/store/user'
+const userStore = UserStore()
+const user = computed(() => userStore.user)
+// 接口
+import { SupplyStore } from '@/store/api/platform/supply'
+import { DictDataStore } from '@/store/api/system/dictData'
+import { TagsStore } from '@/store/api/system/tags'
+import { SectorStore } from '@/store/api/platform/sector'
+import { RegionStore } from '@/store/api/system/region'
+const regionStore = RegionStore()
+const store = SupplyStore()
+const dictDataStore = DictDataStore()
+const tagsStore = TagsStore()
+const sectorStore = SectorStore()
+// 加载中
+const loading = ref(false)
+
+const searchForm = ref({})
+// 列表
+const list = ref([])
+let skip = 0
+let limit = inject('limit')
+const total = ref(0)
+const currentPage = ref(1)
+// 字典表
+const isUseList = ref([])
+const statusList = ref([])
+const methodList = ref([])
+const urgentList = ref([])
+const fieldList = ref([])
+const cityList = ref([])
+const supplyList = ref([])
+const tagsList = ref([])
+const sectorList = ref([])
+
+const form = ref({ time: [] })
+const dialog = ref({ type: '1', show: false, title: '发布供给' })
+const formFields = ref([
+  { label: '供给名称', model: 'name' },
+  { label: '标签', model: 'tags', custom: true },
+  { label: '所属产业', model: 'industry', type: 'select' },
+  { label: '行业领域', model: 'field' },
+  { label: '供给紧急度', model: 'urgent', type: 'select' },
+  { label: '合作方式', model: 'method', type: 'select' },
+  { label: '价格(万元)', model: 'money' },
+  { label: '项目来源', model: 'source' },
+  { label: '供给地区', model: 'area', custom: true },
+  { label: '发布时间', model: 'time', type: 'daterange' },
+  { label: '是否公开', model: 'is_use', type: 'radio' },
+  { label: '简介', model: 'brief', type: 'textarea' }
+])
+const rules = reactive({ name: [{ required: true, message: '请输入供给名称', trigger: 'blur' }] })
+// 请求
+onMounted(async () => {
+  loading.value = true
+  await searchOther()
+  await search()
+  loading.value = false
+})
+const search = async (query = { skip, limit }) => {
+  skip = query.skip
+  limit = query.limit
+  const info = {
+    skip: query.skip,
+    limit: query.limit,
+    user: user.value.id,
+    ...searchForm.value
+  }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    list.value = res.data
+    total.value = res.total
+  }
+}
+const searchOther = async () => {
+  let result
+  // 是否使用
+  result = await dictDataStore.query({ code: 'isUse', is_use: '0' })
+  if ($checkRes(result)) isUseList.value = result.data
+  // 状态
+  result = await dictDataStore.query({ code: 'examStatus', is_use: '0' })
+  if ($checkRes(result)) statusList.value = result.data
+  // 合作方式
+  result = await dictDataStore.query({ code: 'method', is_use: '0' })
+  if ($checkRes(result)) methodList.value = result.data
+  // 需求紧急度
+  result = await dictDataStore.query({ code: 'urgent', is_use: '0' })
+  if ($checkRes(result)) urgentList.value = result.data
+  // 技术领域
+  result = await dictDataStore.query({ code: 'field', is_use: '0' })
+  if ($checkRes(result)) fieldList.value = result.data
+  // 需求状态
+  result = await dictDataStore.query({ code: 'supplyStatus', is_use: '0' })
+  if ($checkRes(result)) supplyList.value = result.data
+  // 标签
+  result = await tagsStore.query({ is_use: '0' })
+  if ($checkRes(result)) tagsList.value = result.data
+  // 行业
+  result = await sectorStore.query({ is_use: '0' })
+  if ($checkRes(result)) sectorList.value = result.data
+  // 城市
+  result = await regionStore.area({ level: 'province', code: 22 })
+  if ($checkRes(result)) cityList.value = result.data
+}
+// 字典数据转换
+const getDict = (data, model) => {
+  if (data) {
+    let res
+    if (model == 'status') res = statusList.value.find((f) => f.value == data)
+    return get(res, 'label')
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = { type: '1', show: true, title: '发布供给' }
+}
+// 修改
+const toEdit = (data) => {
+  data.time = [data.start_time, data.end_time]
+  form.value = data
+  dialog.value = { type: '1', show: true, title: '修改供给' }
+}
+// 删除
+const toDelete = (data) => {
+  ElMessageBox.confirm(`您确认删除${data.name}该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const res = await store.del(data.id)
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+      }
+    })
+    .catch(() => {})
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '0', user: user.value.id }
+  if (data.time && data.time.length > 1) {
+    data.start_time = data.time[0]
+    data.end_time = data.time[1]
+  }
+  delete data.time
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+const toDraftSave = async () => {
+  const data = cloneDeep(form.value)
+  const other = { status: '-2', user: user.value.id }
+  if (data.time && data.time.length > 1) {
+    data.start_time = data.time[0]
+    data.end_time = data.time[1]
+  }
+  delete data.time
+  let res
+  if (get(data, 'id')) res = await store.update({ ...data, ...other })
+  else res = await store.create({ ...data, ...other })
+  if ($checkRes(res, true)) {
+    search({ skip, limit })
+    toClose()
+  }
+}
+// 审核保存
+const toExam = async (row) => {
+  ElMessageBox.confirm(`您确认保存并提交审核该数据?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
+    .then(async () => {
+      const data = cloneDeep(row)
+      let res = await store.update({ id: data.id, status: '0', user: user.value.id })
+      if ($checkRes(res, true)) {
+        search({ skip, limit })
+        toClose()
+      }
+    })
+    .catch(() => {})
+}
+const toClose = () => {
+  form.value = { time: [] }
+  dialog.value = { show: false }
+}
+// 分页
+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 {
+  .one {
+    height: 50px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 0 0 10px 0;
+    .one_left {
+      background: #1875df;
+      padding: 0 10px;
+      height: 30px;
+      color: #fff;
+      line-height: 30px;
+      text-align: center;
+      font-size: 16px;
+      cursor: default;
+    }
+  }
+  .thr {
+    display: flex;
+    justify-content: center;
+    margin: 20px 0 0 0;
+  }
+}
+</style>