detail.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <template>
  2. <div id="index">
  3. <el-row>
  4. <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
  5. <div class="w_1200">
  6. <el-col :span="24" class="one">
  7. <el-row :span="24" class="list">
  8. <div class="join" :class="[info.match_status == '0' ? 'join0' : 'join1']">
  9. {{ getDict(info.match_status, 'status') }}
  10. </div>
  11. <el-col :span="4" class="left">
  12. <el-image
  13. v-if="info.file && info.file.length > 0"
  14. class="image"
  15. :src="info.file[0].url"
  16. fit="fill"
  17. />
  18. <el-image v-else class="image" :src="news" fit="fill" />
  19. </el-col>
  20. <el-col :span="20" class="right">
  21. <el-col :span="24" class="right_1">
  22. <span class="type">{{ getDict(info.form, 'form') }}</span>
  23. <span class="title">{{ info.name || '暂无比赛名称' }}</span>
  24. </el-col>
  25. <el-col :span="24" class="right_2">
  26. 组织单位:{{ info.organization || '暂无组织单位' }}
  27. </el-col>
  28. <el-col :span="24" class="right_3">
  29. <el-col :span="20" class="right_3Left">
  30. 比赛日期:{{ getTime(info.time) }}
  31. </el-col>
  32. <el-col :span="4" class="right_3Right">
  33. {{ info.money || '免费' }}
  34. </el-col>
  35. </el-col>
  36. <el-col :span="24" class="right_4">
  37. <el-tag type="primary">{{ getDict(info.type, 'type') }}</el-tag>
  38. </el-col>
  39. </el-col>
  40. </el-row>
  41. </el-col>
  42. <el-col :span="24" class="two">
  43. <el-row class="two_1">
  44. <el-col :span="22" class="twoLeft">
  45. <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
  46. <el-tab-pane label="赛制规则" name="first"></el-tab-pane>
  47. <el-tab-pane label="数据与评测" name="second"></el-tab-pane>
  48. <el-tab-pane label="常见问题" name="third"></el-tab-pane>
  49. </el-tabs>
  50. </el-col>
  51. <el-col :span="2" class="twoRight">
  52. <a-button type="primary" @click="toSign"> 报名参赛 </a-button>
  53. </el-col>
  54. </el-row>
  55. <el-row class="two_2" v-if="activeName === 'first'">
  56. <el-container style="height: 500px">
  57. <el-aside width="200px">
  58. <el-menu default-active="rules1" class="el-menu" @select="selectOpen">
  59. <el-menu-item
  60. :index="item.label"
  61. v-for="(item, index) in menuList"
  62. :key="index"
  63. >
  64. <el-icon><Opportunity /></el-icon>
  65. <span>{{ item.title }}</span>
  66. </el-menu-item>
  67. </el-menu>
  68. </el-aside>
  69. <el-main>
  70. <el-col :span="24">
  71. <h2>{{ rulesInfo.title }}</h2>
  72. <div class="rich-text-container" v-html="rulesInfo.content"></div>
  73. </el-col>
  74. </el-main>
  75. </el-container>
  76. </el-row>
  77. <el-row class="two_2" v-if="activeName === 'second'"> 数据与评测 </el-row>
  78. <el-row class="two_2" v-if="activeName === 'third'">
  79. <div v-html="info.brief"></div>
  80. </el-row>
  81. </el-col>
  82. <el-col :span="24" class="thr">
  83. <el-col :span="24" class="thr_1"> 相关推荐 </el-col>
  84. <el-col :span="24" class="thr_2"> 1111 </el-col>
  85. </el-col>
  86. </div>
  87. </el-col>
  88. </el-row>
  89. <el-dialog v-model="dialog" title="报名参赛" :destroy-on-close="true" @close="toClose">
  90. <data-form></data-form>
  91. </el-dialog>
  92. </div>
  93. </template>
  94. <script setup>
  95. import { get, cloneDeep } from 'lodash-es'
  96. const $checkRes = inject('$checkRes')
  97. // 组件
  98. import dataForm from './parts/index.vue'
  99. // 接口
  100. import { DictDataStore } from '@/store/api/system/dictData'
  101. import { MatchStore } from '@/store/api/platform/match'
  102. const store = MatchStore()
  103. const dictDataStore = DictDataStore()
  104. // 图片引入
  105. import news from '@/assets/news.png'
  106. // 路由
  107. const route = useRoute()
  108. // 加载中
  109. const loading = ref(false)
  110. const info = ref({})
  111. const rulesInfo = ref({})
  112. const activeName = ref('first')
  113. // 字典表
  114. const statusList = ref([])
  115. const typeList = ref([])
  116. const formList = ref([])
  117. const menuList = ref([
  118. { title: '大赛背景', label: 'rules1' },
  119. { title: '大赛主题和目标', label: 'rules2' },
  120. { title: '大赛基本情况介绍', label: 'rules3' },
  121. { title: '赛题任务', label: 'rules4' },
  122. { title: '赛程安排', label: 'rules5' },
  123. { title: '赛制阶段', label: 'rules6' },
  124. { title: '参赛资格', label: 'rules7' },
  125. { title: '参赛报名', label: 'rules8' },
  126. { title: '奖项设置与奖励办法', label: 'rules9' },
  127. { title: '组织单位', label: 'rules10' },
  128. { title: '赛事联络', label: 'rules11' },
  129. { title: '赛事交流', label: 'rules12' }
  130. ])
  131. // 弹框
  132. const form = ref({})
  133. const rules = reactive({
  134. name: [{ required: true, message: '请输入需求名称', trigger: 'blur' }],
  135. field: [{ required: true, message: '请选择行业领域', trigger: 'change' }],
  136. urgent: [{ required: true, message: '请选择需求紧急度', trigger: 'change' }],
  137. method: [{ required: true, message: '请选择合作方式', trigger: 'change' }],
  138. money: [{ required: true, message: '请输入价格', trigger: 'blur' }],
  139. area: [{ required: true, message: '请选择需求地区', trigger: 'change' }],
  140. time: [{ required: true, message: '请选择起始时间', trigger: 'change' }],
  141. is_use: [{ required: true, message: '请选择是否使用', trigger: 'change' }],
  142. brief: [{ required: true, message: '请输入简介', trigger: 'blur' }]
  143. })
  144. const dialog = ref(false)
  145. // 请求
  146. onMounted(async () => {
  147. loading.value = true
  148. await searchOther()
  149. await search()
  150. loading.value = false
  151. })
  152. const searchOther = async () => {
  153. let result
  154. // 类型
  155. result = await dictDataStore.query({ code: 'matchType', is_use: '0' })
  156. if ($checkRes(result)) typeList.value = result.data
  157. // 类别
  158. result = await dictDataStore.query({ code: 'matchForm', is_use: '0' })
  159. if ($checkRes(result)) formList.value = result.data
  160. // 赛事状态
  161. result = await dictDataStore.query({ code: 'matchStatus', is_use: '0' })
  162. if ($checkRes(result)) statusList.value = result.data
  163. }
  164. const search = async () => {
  165. let id = route.query.id
  166. if (id) {
  167. let res = await store.fetch(id)
  168. if (res.errcode == '0') {
  169. info.value = res.data
  170. rulesInfo.value = { title: '大赛背景', key: 'rules1', content: res.data.rules.rules1 }
  171. }
  172. }
  173. }
  174. // 字典数据转换
  175. const getDict = (data, model) => {
  176. let res
  177. if (model == 'status') res = statusList.value.find((f) => f.value == data)
  178. else if (model == 'type') res = typeList.value.find((f) => f.value == data)
  179. else if (model == 'form') res = formList.value.find((f) => f.value == data)
  180. return get(res, 'label')
  181. }
  182. // 时间
  183. const getTime = (data) => {
  184. if (data) return `${data[0]} - ${data[1]}`
  185. }
  186. const selectOpen = (key, keyPath) => {
  187. const res = menuList.value.find((f) => f.label == key)
  188. if (res) {
  189. rulesInfo.value = { title: get(res, 'title'), key, content: get(info.value.rules, key) }
  190. }
  191. }
  192. // 报名参赛
  193. const toSign = () => {
  194. dialog.value = true
  195. }
  196. const toClose = () => {
  197. dialog.value = false
  198. form.value = {}
  199. }
  200. // 报名
  201. const submitForm = async (formEl) => {
  202. if (!formEl) return
  203. await formEl.validate(async (valid, fields) => {
  204. if (valid) {
  205. const data = cloneDeep(form.value)
  206. console.log(data)
  207. } else {
  208. console.log('error submit!', fields)
  209. }
  210. })
  211. }
  212. // provide
  213. provide('form', form)
  214. provide('rules', rules)
  215. provide('submitForm', submitForm)
  216. </script>
  217. <style scoped lang="scss">
  218. .main {
  219. background: rgb(248, 248, 248);
  220. .one {
  221. margin-top: 20px;
  222. background: #ffffff;
  223. border-radius: 10px;
  224. padding: 15px;
  225. .list {
  226. display: flex;
  227. align-items: center;
  228. min-height: 150px;
  229. width: 100%;
  230. margin-bottom: 10px;
  231. position: relative;
  232. overflow: hidden;
  233. border: 1px solid #f5f5f5;
  234. .left {
  235. display: flex;
  236. align-items: center;
  237. justify-content: center;
  238. .image {
  239. width: 180px;
  240. height: 120px;
  241. }
  242. }
  243. .right {
  244. .right_1 {
  245. padding: 10px 0 0 0;
  246. .type {
  247. padding-left: 4px;
  248. padding-right: 4px;
  249. height: 22px;
  250. line-height: 20px;
  251. background: #f8f9fc;
  252. border-radius: 1px;
  253. border: 1px solid #dde2e7;
  254. font-size: 12px;
  255. font-family:
  256. PingFangSC-Regular,
  257. PingFang SC;
  258. font-weight: 400;
  259. color: #a9b2c6;
  260. text-align: center;
  261. vertical-align: top;
  262. display: inline-block;
  263. margin-right: 8px;
  264. }
  265. .title {
  266. max-width: 88%;
  267. display: inline-block;
  268. overflow: hidden;
  269. text-overflow: ellipsis;
  270. white-space: nowrap;
  271. font-size: 16px;
  272. font-family:
  273. PingFangSC-Medium,
  274. PingFang SC;
  275. font-weight: 500;
  276. color: #222;
  277. line-height: 22px;
  278. }
  279. }
  280. .right_2 {
  281. padding: 5px 0;
  282. font-size: 12px;
  283. font-family:
  284. PingFangSC-Regular,
  285. PingFang SC;
  286. font-weight: 400;
  287. color: #666;
  288. }
  289. .right_3 {
  290. padding: 5px 0;
  291. display: flex;
  292. justify-content: space-between;
  293. .right_3Left {
  294. font-size: 12px;
  295. font-family:
  296. PingFangSC-Regular,
  297. PingFang SC;
  298. font-weight: 400;
  299. color: #949fb8;
  300. }
  301. .right_3Right {
  302. text-align: right;
  303. font-size: 16px;
  304. font-family:
  305. PingFangSC-Semibold,
  306. PingFang SC;
  307. font-weight: 600;
  308. color: #ff5602;
  309. }
  310. }
  311. .right_4 {
  312. padding-top: 10px;
  313. border-top: 1px solid #f3f3f3;
  314. }
  315. }
  316. .join {
  317. position: absolute;
  318. right: -23px;
  319. top: 10px;
  320. font-size: 12px;
  321. font-family:
  322. PingFangSC-Normal,
  323. PingFang SC;
  324. color: #fff;
  325. line-height: 21px;
  326. height: 21px;
  327. width: 100px;
  328. text-align: center;
  329. -ms-transform: rotate(45deg);
  330. transform: rotate(45deg);
  331. -webkit-transform: rotate(45deg);
  332. -moz-transform: rotate(45deg);
  333. padding-left: 9px;
  334. }
  335. .join0 {
  336. background: #e6f4fe;
  337. color: #638aa5;
  338. }
  339. .join1 {
  340. background: #ededed;
  341. color: #666c7d;
  342. }
  343. }
  344. }
  345. .two {
  346. margin: 20px 0;
  347. background: #ffffff;
  348. border-radius: 10px;
  349. padding: 15px;
  350. .two_1 {
  351. border-bottom: 1px solid #e4e7ed;
  352. :deep(.el-tabs__nav-wrap:after) {
  353. background-color: transparent !important;
  354. }
  355. }
  356. .two_2 {
  357. padding: 15px;
  358. min-height: 500px;
  359. :deep(.el-aside)::-webkit-scrollbar {
  360. display: none;
  361. }
  362. h2 {
  363. padding-bottom: 0.3em;
  364. border-bottom: 1px solid #eaecef;
  365. }
  366. .rich-text-container {
  367. :deep(table) {
  368. border-collapse: collapse;
  369. }
  370. :deep(table) {
  371. border: 1px solid black;
  372. }
  373. :deep(th) {
  374. border: 1px solid black;
  375. }
  376. :deep(td) {
  377. border: 1px solid black;
  378. }
  379. }
  380. }
  381. }
  382. .thr {
  383. margin: 20px 0;
  384. background: #ffffff;
  385. border-radius: 10px;
  386. padding: 15px;
  387. .thr_1 {
  388. padding: 10px;
  389. font-family: PingFangSC-Semibold;
  390. font-size: 18px;
  391. color: #383b40;
  392. letter-spacing: 0;
  393. line-height: 18px;
  394. font-weight: 600;
  395. }
  396. }
  397. }
  398. </style>