提交 321f7316 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 1d3fcaaa
......@@ -10,13 +10,14 @@
"dependencies": {
"@element-plus/icons-vue": "^2.0.9",
"@tinymce/tinymce-vue": "^5.0.0",
"@vant/area-data": "^1.3.2",
"@vueuse/core": "^9.1.1",
"ali-oss": "^6.17.1",
"axios": "^0.27.2",
"blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2",
"dayjs": "^1.11.5",
"element-plus": "^2.2.15",
"element-plus": "^2.2.16",
"file-saver": "^2.0.5",
"lodash-es": "^4.17.21",
"pinia": "^2.0.21",
......@@ -41,11 +42,11 @@
"chalk": "^5.0.1",
"eslint": "^8.5.0",
"eslint-plugin-vue": "^9.4.0",
"sass": "^1.54.5",
"sass": "^1.54.8",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.11.2",
"vite": "^3.0.9",
"vite-plugin-checker": "^0.5.0",
"vite-plugin-checker": "^0.5.1",
"vue-tsc": "^0.40.5"
}
},
......@@ -236,16 +237,16 @@
}
},
"node_modules/@floating-ui/core": {
"version": "0.7.3",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-0.7.3.tgz",
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.1.tgz",
"integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA=="
},
"node_modules/@floating-ui/dom": {
"version": "0.5.4",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.5.4.tgz",
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.1.tgz",
"integrity": "sha512-wBDiLUKWU8QNPNOTAFHiIAkBv1KlHauG2AhqjSeh2H+wR8PX+AArXfz8NkRexH5PgMJMmSOS70YS89AbWYh5dA==",
"dependencies": {
"@floating-ui/core": "^0.7.3"
"@floating-ui/core": "^1.0.1"
}
},
"node_modules/@humanwhocodes/config-array": {
......@@ -571,6 +572,11 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@vant/area-data": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@vant/area-data/-/area-data-1.3.2.tgz",
"integrity": "sha512-KHGvvIxApxCXDTzsh5hPsIVrF4ll5J3pNgYNL3lmNJxye7aWySK97EgXiprVee+FshVa1jVW4esJ7VyW0l94WQ=="
},
"node_modules/@videojs/http-streaming": {
"version": "2.14.2",
"resolved": "https://registry.npmmirror.com/@videojs/http-streaming/-/http-streaming-2.14.2.tgz",
......@@ -1527,13 +1533,13 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/element-plus": {
"version": "2.2.15",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.15.tgz",
"integrity": "sha512-SMIx8xKB1YawT9JocyFhbs3Av2rXFfxrCVTLMYS0DK0xnW+fKvwjZngLfwF6MyRzXIuzNW17XFtu0iP3tlJHbA==",
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.16.tgz",
"integrity": "sha512-rvaTMFIujec9YDC5lyaiQv2XVUCHuhVDq2k+9vQxP78N8Wd07iEOGa9pvEVOO2uYc75l4rSl2RE/IWPH/6Mdzw==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
"@floating-ui/dom": "^0.5.4",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
"@types/lodash-es": "^4.17.6",
......@@ -3807,9 +3813,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
"version": "1.54.5",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.54.5.tgz",
"integrity": "sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==",
"version": "1.54.8",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz",
"integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
......@@ -4483,9 +4489,9 @@
}
},
"node_modules/vite-plugin-checker": {
"version": "0.5.0",
"resolved": "https://registry.npmmirror.com/vite-plugin-checker/-/vite-plugin-checker-0.5.0.tgz",
"integrity": "sha512-IuCHIpnJwRxDupd+jVNwza07+ajt4jFItwWMFEF2cDDp5E92ePx1eVn91JMsa02XkquR1s6L4VZX8/RTFamD9w==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.5.1.tgz",
"integrity": "sha512-NFiO1PyK9yGuaeSnJ7Whw9fnxLc1AlELnZoyFURnauBYhbIkx9n+PmIXxSFUuC9iFyACtbJQUAEuQi6yHs2Adg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.12.13",
......@@ -4965,16 +4971,16 @@
}
},
"@floating-ui/core": {
"version": "0.7.3",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-0.7.3.tgz",
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.1.tgz",
"integrity": "sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA=="
},
"@floating-ui/dom": {
"version": "0.5.4",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-0.5.4.tgz",
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.1.tgz",
"integrity": "sha512-wBDiLUKWU8QNPNOTAFHiIAkBv1KlHauG2AhqjSeh2H+wR8PX+AArXfz8NkRexH5PgMJMmSOS70YS89AbWYh5dA==",
"requires": {
"@floating-ui/core": "^0.7.3"
"@floating-ui/core": "^1.0.1"
}
},
"@humanwhocodes/config-array": {
......@@ -5221,6 +5227,11 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"@vant/area-data": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@vant/area-data/-/area-data-1.3.2.tgz",
"integrity": "sha512-KHGvvIxApxCXDTzsh5hPsIVrF4ll5J3pNgYNL3lmNJxye7aWySK97EgXiprVee+FshVa1jVW4esJ7VyW0l94WQ=="
},
"@videojs/http-streaming": {
"version": "2.14.2",
"resolved": "https://registry.npmmirror.com/@videojs/http-streaming/-/http-streaming-2.14.2.tgz",
......@@ -5988,13 +5999,13 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"element-plus": {
"version": "2.2.15",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.2.15.tgz",
"integrity": "sha512-SMIx8xKB1YawT9JocyFhbs3Av2rXFfxrCVTLMYS0DK0xnW+fKvwjZngLfwF6MyRzXIuzNW17XFtu0iP3tlJHbA==",
"version": "2.2.16",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.2.16.tgz",
"integrity": "sha512-rvaTMFIujec9YDC5lyaiQv2XVUCHuhVDq2k+9vQxP78N8Wd07iEOGa9pvEVOO2uYc75l4rSl2RE/IWPH/6Mdzw==",
"requires": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
"@floating-ui/dom": "^0.5.4",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
"@types/lodash-es": "^4.17.6",
......@@ -7695,9 +7706,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.54.5",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.54.5.tgz",
"integrity": "sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw==",
"version": "1.54.8",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz",
"integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
......@@ -8199,9 +8210,9 @@
}
},
"vite-plugin-checker": {
"version": "0.5.0",
"resolved": "https://registry.npmmirror.com/vite-plugin-checker/-/vite-plugin-checker-0.5.0.tgz",
"integrity": "sha512-IuCHIpnJwRxDupd+jVNwza07+ajt4jFItwWMFEF2cDDp5E92ePx1eVn91JMsa02XkquR1s6L4VZX8/RTFamD9w==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.5.1.tgz",
"integrity": "sha512-NFiO1PyK9yGuaeSnJ7Whw9fnxLc1AlELnZoyFURnauBYhbIkx9n+PmIXxSFUuC9iFyACtbJQUAEuQi6yHs2Adg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
......
......@@ -15,13 +15,14 @@
"dependencies": {
"@element-plus/icons-vue": "^2.0.9",
"@tinymce/tinymce-vue": "^5.0.0",
"@vant/area-data": "^1.3.2",
"@vueuse/core": "^9.1.1",
"ali-oss": "^6.17.1",
"axios": "^0.27.2",
"blueimp-md5": "^2.19.0",
"countup.js": "^2.3.2",
"dayjs": "^1.11.5",
"element-plus": "^2.2.15",
"element-plus": "^2.2.16",
"file-saver": "^2.0.5",
"lodash-es": "^4.17.21",
"pinia": "^2.0.21",
......@@ -46,11 +47,11 @@
"chalk": "^5.0.1",
"eslint": "^8.5.0",
"eslint-plugin-vue": "^9.4.0",
"sass": "^1.54.5",
"sass": "^1.54.8",
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.11.2",
"vite": "^3.0.9",
"vite-plugin-checker": "^0.5.0",
"vite-plugin-checker": "^0.5.1",
"vue-tsc": "^0.40.5"
}
}
import { areaList } from '@vant/area-data'
// json to array
const json2Array = function (data: Record<string, string>) {
return Object.keys(data).map(code => ({ code, label: data[code], value: data[code] }))
}
export function useArea() {
const provinceValue = ref('')
const cityValue = ref('')
const countyValue = ref('')
watch(provinceValue, () => {
cityValue.value = ''
})
watch(cityValue, () => {
countyValue.value = ''
})
const provinceList = ref(
json2Array(areaList.province_list).sort((a, b) => {
return a.label.localeCompare(b.label)
})
)
const cityList = computed(() => {
return json2Array(areaList.city_list)
.filter(item => {
const provinceValueCode =
provinceList.value.find(province => province.label === provinceValue.value)?.code || ''
return item.code.slice(0, 2) === provinceValueCode.slice(0, 2)
})
.sort((a, b) => {
return a.label.localeCompare(b.label)
})
})
const countyList = computed(() => {
return json2Array(areaList.county_list)
.filter(item => {
const cityValueCode = cityList.value.find(city => city.label === cityValue.value)?.code || ''
return item.code.slice(0, 4) === cityValueCode.slice(0, 4)
})
.sort((a, b) => {
return a.label.localeCompare(b.label)
})
})
const treeList = provinceList.value.map(item => {
const children = json2Array(areaList.city_list)
.filter(item2 => {
return item2.code.slice(0, 2) === item.code.slice(0, 2)
})
.sort((a, b) => {
return a.label.localeCompare(b.label)
})
return { ...item, children }
})
return { provinceValue, cityValue, countyValue, provinceList, cityList, countyList, treeList }
}
import httpRequest from '@/utils/axios'
import type { ContestantCreateParams, ContestantUpdateParams } from './types'
// 获取参赛选手列表
export function getContestantList(params?: { name?: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/list', { params })
}
// 创建参赛选手
export function createContestant(data: ContestantCreateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
// 修改参赛选手
export function updateContestant(data: ContestantUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
// 获取参赛选手列表
export function uploadContestant(data: { file: File }) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { Contestant, ContestantCreateParams } from '../types'
import { ElMessage } from 'element-plus'
import { createContestant, updateContestant } from '../api'
import { useMapStore } from '@/stores/map'
import { useArea } from '@/composables/useArea'
interface Props {
data?: Contestant | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
// 性别
const genderList = useMapStore().getMapValuesByKey('system_gender')
const formRef = $ref<FormInstance>()
const form = reactive<ContestantCreateParams>({
name: '',
phone: '',
company: '',
gender: '1',
province: '',
city: '',
county: ''
})
watchEffect(() => {
if (!props.data) return
Object.assign(form, props.data)
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请选择赛项所属部门/学校' }],
phone: [{ required: true, message: '请选择赛项课程' }],
company: [{ required: true, message: '请输入赛项名称' }],
position: [{ required: true, message: '请输入赛项学时' }],
province: [{ required: true, message: '请选择赛项类型' }],
city: [{ required: true, message: '请选择指导教师' }],
county: [{ required: true, message: '请输入赛项总成绩' }]
})
const isUpdate = $computed(() => {
return !!props.data?.id
})
const title = $computed(() => {
return isUpdate ? '编辑参赛选手信息' : '新增参赛选手'
})
const { provinceList, cityList, countyList, provinceValue, cityValue, countyValue } = useArea()
watchEffect(() => {
form.province = provinceValue.value
form.city = cityValue.value
form.county = countyValue.value
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
isUpdate ? handleUpdate() : handleCreate()
})
}
// 新增
function handleCreate() {
createContestant(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate() {
if (!props.data?.id) return
updateContestant({ id: props.data.id, ...form }).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参赛形式" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="证件类型" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="证件号码" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="学校名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="省" prop="province">
<el-select v-model="provinceValue" style="width: 100%">
<el-option v-for="item in provinceList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="市" prop="city">
<el-select v-model="cityValue" style="width: 100%">
<el-option v-for="item in cityList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="区/县" prop="county">
<el-select v-model="countyValue" style="width: 100%">
<el-option v-for="item in countyList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="专业名称" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="年级" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="班级" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-form-item label="指导教师" prop="phone">
<el-input v-model="form.phone" />
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
</el-row>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { Upload } from '@element-plus/icons-vue'
import { useFileDialog } from '@vueuse/core'
import { ElMessage } from 'element-plus'
import { uploadContestant } from '../api'
const emit = defineEmits<{
(e: 'update'): void
}>()
// 批量导入
const { files, open } = useFileDialog()
function handleImport() {
open({
accept: '.csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel',
multiple: false
})
}
watchEffect(() => {
if (!files.value?.length) return
const [file] = files.value
uploadContestant({ file }).then(() => {
ElMessage({ message: '导入成功', type: 'success' })
emit('update')
})
})
</script>
<template>
<el-dialog title="批量导入" :close-on-click-modal="false" width="400px">
<div class="box">
<el-button type="primary" round :icon="Upload" @click="handleImport">本地上传</el-button>
<p>
<a
href="https://webapp-pub.ezijing.com/project/saas-lab/%E5%AE%9E%E9%AA%8C%E6%88%90%E7%BB%A9%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx"
download
>下载模板</a
>
</p>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.box {
padding: 20px 0;
display: flex;
align-items: center;
justify-content: center;
.el-button {
width: 220px;
}
p {
color: #999;
margin-left: 20px;
}
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/contest/contestants',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export interface Contestant {
id: string
name: string
company: string
phone: string
gender: string
province: string
city: string
county: string
}
export type ContestantCreateParams = Omit<Contestant, 'id'>
export type ContestantUpdateParams = Contestant
<script setup lang="ts">
import type { Contestant } from '../types'
import { Upload } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue'
import { getContestantList } from '../api'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const ImportDialog = defineAsyncComponent(() => import('../components/ImportDialog.vue'))
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
remote: {
httpRequest: getContestantList,
params: { contest_id: '', name: '' }
},
filters: [
{
type: 'select',
prop: 'course_id',
label: '赛项',
placeholder: '请选择赛项'
},
{ type: 'input', prop: 'student_name', label: '选手姓名', placeholder: '请输入选手姓名' }
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' },
{ label: '选手姓名', prop: 'length' },
{ label: '参赛ID', prop: 'teacher_names' },
{ label: '性别', prop: 'type_name' },
{ label: '学校', prop: 'score' },
{ label: '所在专业', prop: 'score' },
{ label: '所在年级', prop: 'score' },
{ label: '所在班级', prop: 'score' },
{ label: '训练次数', prop: 'score' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 }
]
}
const importVisible = $ref(false)
let dialogVisible = $ref(false)
const rowData = ref<Contestant | undefined | null>(null)
// 编辑
function handleUpdate(row: Contestant) {
rowData.value = row
dialogVisible = true
}
function onUpdateSuccess() {
appList?.refetch()
}
</script>
<template>
<AppCard title="参赛选手管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button>
</template>
<template #table-x="{ row }: { row: Contestant }">
<el-button type="primary" round v-permission="'v1-backend-experiment-view'">查看</el-button>
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'"
>编辑</el-button
>
</template>
</AppList>
</AppCard>
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
<!-- 批量导入 -->
<ImportDialog v-model="importVisible" @update="onUpdateSuccess" v-if="importVisible"></ImportDialog>
</template>
import httpRequest from '@/utils/axios'
import type { ExperimentCreateItem } from './types'
// 获取赛项列表
export function getContestItemList(params?: { name?: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/list', { params })
}
// 获取实验详情
export function getContestItem(params: { experiment_id: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/view', { params })
}
// 创建实验
export function createExperiment(data: ExperimentCreateItem) {
return httpRequest.post('/api/resource/v1/backend/experiment/create', data)
}
// 更新实验
export function updateExperiment(data: ExperimentCreateItem) {
return httpRequest.post('/api/resource/v1/backend/experiment/update', data)
}
// 获取实验课程列表
export function getExperimentCourseList(params: { organ_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment/courses', { params })
}
// 获取实验指导老师列表
export function getExperimentTeacherList(params: { organ_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment/teachers', { params })
}
// 获取实验关联班级列表
export function getExperimentClassList(params: { experiment_id: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/class-add', { params })
}
// 实验关联班级
export function experimentAddClass(data: { experiment_id: string; classes_id: string; type: 'add' | 'delete' }) {
return httpRequest.post('/api/resource/v1/backend/experiment/class-add', data)
}
// 获取班级学生列表
export function getClassStudentList(params: { class_id: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/class-students', { params })
}
// 获取班级小组列表
export function getExperimentClassGroupsList(params: { experiment_id: string; class_id: string }) {
return httpRequest.get('/api/resource/v1/backend/experiment/class-teams', { params })
}
// 获取实验小组
export function getExperimentGroup(params: { team_id: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/team-view', { params })
}
// 新增实验小组
export function experimentAddClassGroup(data: { experiment_id: string; class_id: string; name: string }) {
return httpRequest.post('/api/resource/v1/backend/experiment/team-add', data)
}
// 实验小组添加学生
export function experimentGroupAddStudent(data: { team_id: string; students_id: string; type: 'add' | 'delete' }) {
return httpRequest.post('/api/resource/v1/backend/experiment/team-add-student', data)
}
// 获取实验关联班级列表
export function getExperimentGroupStudentList(params: { team_id: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/team-add-student', { params })
}
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { ContestItem, ExperimentCreateItem } from '../types'
import { ElMessage } from 'element-plus'
import { createExperiment, updateExperiment } from '../api'
import { useMapStore } from '@/stores/map'
interface Props {
data?: ContestItem | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
// 赛项类型
const types = useMapStore().getMapValuesByKey('experiment_type')
// 数据状态
const status = useMapStore().getMapValuesByKey('system_status')
const formRef = $ref<FormInstance>()
const form = reactive<ExperimentCreateItem>({
organ_id: '',
status: '1',
course_id: '',
name: '',
length: 10,
type: '',
score: 100,
teachers_id: '',
teachers_ids: []
})
watchEffect(() => {
if (!props.data) return
const score = parseFloat(props.data.score)
const length = parseFloat(props.data.length)
const teachers_ids = props.data.teacher.map(item => item.id)
Object.assign(form, props.data, { score, length, teachers_ids })
})
const rules = ref<FormRules>({
organ_id: [{ required: true, message: '请选择赛项所属部门/学校' }],
course_id: [{ required: true, message: '请选择赛项课程' }],
name: [{ required: true, message: '请输入赛项名称' }],
length: [{ required: true, message: '请输入赛项学时' }],
type: [{ required: true, message: '请选择赛项类型' }],
teachers_ids: [{ type: 'array', required: true, message: '请选择指导教师', trigger: 'change' }],
score: [{ required: true, message: '请输入赛项总成绩' }]
})
const isUpdate = $computed(() => {
return !!form.id
})
const title = $computed(() => {
return isUpdate ? '编辑赛项' : '新增赛项'
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
const params = {
...form,
teachers_id: form.teachers_ids?.join(',') || ''
}
isUpdate ? handleUpdate(params) : handleCreate(params)
})
}
// 新增
function handleCreate(params: ExperimentCreateItem) {
createExperiment(params).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate(params: ExperimentCreateItem) {
updateExperiment(params).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="150px">
<el-form-item label="赛项名称" prop="name">
<el-input v-model="form.name" :disabled="isUpdate" />
</el-form-item>
<el-form-item label="主办单位" prop="course_id">
<el-select v-model="form.course_id" style="width: 100%" :disabled="isUpdate">
<el-option v-for="item in types" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="承办单位" prop="course_id">
<el-select v-model="form.course_id" style="width: 100%" :disabled="isUpdate">
<el-option v-for="item in types" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="技术支持单位" prop="course_id">
<el-select v-model="form.course_id" style="width: 100%" :disabled="isUpdate">
<el-option v-for="item in types" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="赛项类型" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="指导教师" prop="teachers_ids">
<el-select v-model="form.teachers_ids" multiple style="width: 100%">
<el-option v-for="item in types" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="赛项周期" prop="type">
<el-date-picker type="daterange" v-model="form.type" />
</el-form-item>
<el-form-item label="正式比赛日期" prop="type">
<el-date-picker v-model="form.type" />
</el-form-item>
<el-form-item label="正式比赛时间" prop="type">
<el-date-picker type="daterange" v-model="form.type" />
</el-form-item>
<el-form-item label="报名截止日期" prop="type">
<el-date-picker v-model="form.type" />
</el-form-item>
<el-form-item label="有效状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="item in status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="赛项LOGO" prop="logo">
<AppUpload v-model="form.type"></AppUpload>
</el-form-item>
<el-form-item label="赛项封面" prop="logo">
<AppUpload v-model="form.type"></AppUpload>
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
</el-row>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '训练指导书名称', prop: 'specialty_id_name' },
{ label: '文件类型', prop: 'name' },
{ label: '创建人', prop: 'student_nums' },
{ label: '创建时间', prop: 'updated_time' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 }
]
}
// 刷新
function handleRefetch() {
appList?.refetch()
}
let rowData = $ref<ClassItem | null>(null)
// 删除
function handleRemoveClass(row: ClassItem) {
ElMessageBox.confirm('确定要删除吗?', '提示').then(() => {
experimentAddClass({ experiment_id: props.id, classes_id: row.id, type: 'delete' }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
appList?.refetch()
})
})
}
</script>
<template>
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" :icon="CirclePlus">新增</el-button>
</template>
<template #table-x="{ row }">
<el-button type="primary" round>查阅</el-button>
<el-button type="primary" round @click="handleRemoveClass(row)" v-permission="'v1-backend-experiment-class-add'"
>删除</el-button
>
</template>
</AppList>
</template>
<script setup lang="ts">
import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '操作视频名称', prop: 'specialty_id_name' },
{ label: '视频时长(分钟)', prop: 'name' },
{ label: '创建人', prop: 'student_nums' },
{ label: '创建时间', prop: 'created_time' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 }
]
}
// 刷新
function handleRefetch() {
appList?.refetch()
}
let rowData = $ref<ClassItem | null>(null)
// 删除
function handleRemoveClass(row: ClassItem) {
ElMessageBox.confirm('确定要删除吗?', '提示').then(() => {
experimentAddClass({ experiment_id: props.id, classes_id: row.id, type: 'delete' }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
appList?.refetch()
})
})
}
</script>
<template>
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" :icon="CirclePlus">新增</el-button>
</template>
<template #table-x="{ row }">
<el-button type="primary" round>查阅</el-button>
<el-button type="primary" round @click="handleRemoveClass(row)" v-permission="'v1-backend-experiment-class-add'"
>删除</el-button
>
</template>
</AppList>
</template>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/contest',
redirect: '/admin/contest/items'
},
{
path: '/admin/contest/items',
component: AppLayout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: ':id', component: () => import('./views/View.vue'), props: true }
]
}
]
export interface ExperimentItemTeacher {
id: string
name: string
}
export interface ContestItem {
course_id: string
course_name: string
created_operator: string
created_operator_name: string
created_time: string
delete_time: string
id: string
length: string
name: string
organ_id: string
organ_id_name: string
project_id: string
project_id_name: string
score: string
status: string
status_name: string
teacher: ExperimentItemTeacher[]
type: string
type_name: string
updated_operator: string
updated_operator_name: string
updated_time: string
}
export interface ExperimentCreateItem {
id?: string
organ_id: string
status: string
course_id: string
name: string
length: number
type: string
score: number
teachers_id: string
teachers_ids?: string[]
}
export interface ClassItem {
code: string
created_operator: string
created_operator_name: string
created_time: string
delete_time: string
id: string
name: string
organ_id: string
organ_id_name: string
project_id: string
project_id_name: string
specialty_id: string
specialty_id_name: string
start_year: string
start_year_name: string
status: string
status_name: string
student_nums: number
teacher_id: string
teacher_id_name: string
updated_operator: string
updated_operator_name: string
updated_time: string
}
export interface StudentItem {
birthday: string
city: string
city_name: string
class_id: string
class_id_name: string
county: string
county_name: string
created_operator: string
created_operator_name: string
created_time: string
delete_time: string
gender: string
gender_name: string
id: string
id_number: string
id_type: string
id_type_name: string
mobile: string
name: string
organ_id: string
organ_id_name: string
project_id: string
project_id_name: string
province: string
province_name: string
sno_number: string
specialty_id: string
specialty_id_name: string
sso_id: string
status: string
status_name: string
updated_operator: string
updated_operator_name: string
updated_time: string
}
export interface GroupItem {
class_id: string
created_operator: string
created_time: string
experiment_id: string
id: string
name: string
status: string
student_num: number
updated_operator: string
updated_time: string
experiment_name?: string
course_name?: string
class_name?: string
}
<script setup lang="ts">
import type { ContestItem } from '../types'
import { CirclePlus } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue'
import { getContestItemList } from '../api'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
remote: {
httpRequest: getContestItemList
},
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' },
{ label: '主办单位', prop: 'length' },
{ label: '承办单位', prop: 'teacher_names' },
{ label: '技术支持单位', prop: 'type_name' },
{ label: '赛项类型', prop: 'score' },
{ label: '指导老师', prop: 'score' },
{ label: '报名人数', prop: 'score' },
{ label: '专家人数', prop: 'score' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 }
]
}
let dialogVisible = $ref(false)
const rowData = ref<ContestItem | undefined | null>(null)
// 新增
function handleAdd() {
rowData.value = null
dialogVisible = true
}
// 编辑
function handleUpdate(row: ContestItem) {
rowData.value = row
dialogVisible = true
}
function onUpdateSuccess() {
appList?.refetch()
}
</script>
<template>
<AppCard title="赛项管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" :icon="CirclePlus" v-permission="'v1-backend-experiment-create'" @click="handleAdd"
>新增赛项</el-button
>
</template>
<template #table-x="{ row }: { row: ContestItem }">
<el-button type="primary" round v-permission="'v1-backend-experiment-view'">
<router-link :to="`/admin/contest/items/${row.id}`" target="_blank">查看</router-link>
</el-button>
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'"
>编辑</el-button
>
</template>
</AppList>
</AppCard>
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
</template>
<script setup lang="ts">
import type { ContestItem } from '../types'
import { getContestItem } from '../api'
const ViewBook = defineAsyncComponent(() => import('../components/ViewBook.vue'))
const ViewVideo = defineAsyncComponent(() => import('../components/ViewVideo.vue'))
interface Props {
id: string
}
const props = defineProps<Props>()
let detail = $ref<ContestItem | null>(null)
provide('detail', $$(detail))
function fetchInfo() {
getContestItem({ experiment_id: props.id }).then(res => {
detail = res.data
})
}
onMounted(() => fetchInfo)
</script>
<template>
<AppCard title="赛项管理">
<template #header-aside>
<el-button type="primary">评分规则</el-button>
<el-button type="primary">评分专家</el-button>
<el-button type="primary">参赛选手</el-button>
<el-button type="primary">评分细则</el-button>
</template>
<el-descriptions class="descriptions-box" v-if="detail">
<el-descriptions-item label="赛项名称:">{{ detail.name }}</el-descriptions-item>
<el-descriptions-item label="赛项课程:">{{ detail.course_name }}</el-descriptions-item>
<el-descriptions-item label="所属机构/学校:">{{ detail.organ_id_name }}</el-descriptions-item>
</el-descriptions>
<el-tabs>
<el-tab-pane label="训练指导书" lazy>
<ViewBook></ViewBook>
</el-tab-pane>
<el-tab-pane label="操作视频" lazy>
<ViewVideo></ViewVideo>
</el-tab-pane>
</el-tabs>
</AppCard>
</template>
import httpRequest from '@/utils/axios'
import type { JudgeCreateParams, JudgeUpdateParams } from './types'
// 获取评分专家列表
export function getJudgeList(params?: { name?: string; page?: number; 'per-page'?: number }) {
return httpRequest.get('/api/resource/v1/backend/experiment/list', { params })
}
// 创建评分专家
export function createJudge(data: JudgeCreateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
// 修改评分专家
export function updateJudge(data: JudgeUpdateParams) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
// 获取评分专家列表
export function uploadJudge(data: { file: File }) {
return httpRequest.post('/api/resource/v1/backend/experiment/list', data)
}
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import type { Judge, JudgeCreateParams } from '../types'
import { ElMessage } from 'element-plus'
import { createJudge, updateJudge } from '../api'
import { useMapStore } from '@/stores/map'
import { useArea } from '@/composables/useArea'
interface Props {
data?: Judge | null
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
// 性别
const genderList = useMapStore().getMapValuesByKey('system_gender')
const formRef = $ref<FormInstance>()
const form = reactive<JudgeCreateParams>({
name: '',
phone: '',
company: '',
gender: '1',
position: '',
province: '',
city: '',
county: ''
})
watchEffect(() => {
if (!props.data) return
Object.assign(form, props.data)
})
const rules = ref<FormRules>({
name: [{ required: true, message: '请选择赛项所属部门/学校' }],
phone: [{ required: true, message: '请选择赛项课程' }],
company: [{ required: true, message: '请输入赛项名称' }],
position: [{ required: true, message: '请输入赛项学时' }],
province: [{ required: true, message: '请选择赛项类型' }],
city: [{ required: true, message: '请选择指导教师', trigger: 'change' }],
county: [{ required: true, message: '请输入赛项总成绩' }]
})
const isUpdate = $computed(() => {
return !!props.data?.id
})
const title = $computed(() => {
return isUpdate ? '编辑专家信息' : '新增评分专家'
})
const { provinceList, cityList, countyList, provinceValue, cityValue, countyValue } = useArea()
watchEffect(() => {
form.province = provinceValue.value
form.city = cityValue.value
form.county = countyValue.value
})
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
isUpdate ? handleUpdate() : handleCreate()
})
}
// 新增
function handleCreate() {
createJudge(form).then(() => {
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
// 修改
function handleUpdate() {
if (!props.data?.id) return
updateJudge({ id: props.data.id, ...form }).then(() => {
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" width="600px" @update:modelValue="$emit('update:modelValue')">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" :disabled="isUpdate" />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="form.phone" :disabled="isUpdate" />
</el-form-item>
<el-form-item label="所在单位" prop="position">
<el-input v-model="form.position" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio v-for="item in genderList" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="职务" prop="position">
<el-select v-model="form.position" style="width: 100%" :disabled="isUpdate">
<el-option v-for="item in genderList" :key="item.id" :label="item.label" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="省" prop="province">
<el-select v-model="provinceValue" style="width: 100%">
<el-option v-for="item in provinceList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="市" prop="city">
<el-select v-model="cityValue" style="width: 100%">
<el-option v-for="item in cityList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="区/县" prop="county">
<el-select v-model="countyValue" style="width: 100%">
<el-option v-for="item in countyList" :value="item.label" :key="item.code"></el-option>
</el-select>
</el-form-item>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">取消</el-button>
</el-row>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
import { Upload } from '@element-plus/icons-vue'
import { useFileDialog } from '@vueuse/core'
import { ElMessage } from 'element-plus'
import { uploadJudge } from '../api'
const emit = defineEmits<{
(e: 'update'): void
}>()
// 批量导入
const { files, open } = useFileDialog()
function handleImport() {
open({
accept: '.csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel',
multiple: false
})
}
watchEffect(() => {
if (!files.value?.length) return
const [file] = files.value
uploadJudge({ file }).then(() => {
ElMessage({ message: '导入成功', type: 'success' })
emit('update')
})
})
</script>
<template>
<el-dialog title="批量导入" :close-on-click-modal="false" width="400px">
<div class="box">
<el-button type="primary" round :icon="Upload" @click="handleImport">本地上传</el-button>
<p>
<a
href="https://webapp-pub.ezijing.com/project/saas-lab/%E5%AE%9E%E9%AA%8C%E6%88%90%E7%BB%A9%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx"
download
>下载模板</a
>
</p>
</div>
</el-dialog>
</template>
<style lang="scss" scoped>
.box {
padding: 20px 0;
display: flex;
align-items: center;
justify-content: center;
.el-button {
width: 220px;
}
p {
color: #999;
margin-left: 20px;
}
}
</style>
<script setup lang="ts">
import type { Judge } from '../types'
// import { getExperimentClassGroupsList } from '../api'
interface Props {
data: Judge
}
defineProps<Props>()
// 列表配置
const listOptions = {
// remote: {
// httpRequest: getExperimentClassGroupsList,
// params: { experiment_id: data?.id, class_id: props.data?.id }
// },
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' },
{ label: '角色', prop: 'student_num' }
]
}
</script>
<template>
<el-dialog title="查看专家信息">
<el-descriptions :column="2">
<el-descriptions-item label="姓名:">{{ data.name }}</el-descriptions-item>
<el-descriptions-item label="联系电话:">{{ data.phone }}</el-descriptions-item>
<el-descriptions-item label="所在单位:">{{ data.company }}</el-descriptions-item>
<el-descriptions-item label="职务:">{{ data.position }}</el-descriptions-item>
</el-descriptions>
<AppList v-bind="listOptions"></AppList>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</el-dialog>
</template>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/contest/judges',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export interface Judge {
id: string
name: string
phone: string
company: string
gender: string
position: string
province: string
city: string
county: string
}
export type JudgeCreateParams = Omit<Judge, 'id'>
export type JudgeUpdateParams = Judge
<script setup lang="ts">
import type { Judge } from '../types'
import { Upload } from '@element-plus/icons-vue'
import AppList from '@/components/base/AppList.vue'
import { getJudgeList } from '../api'
const ViewDialog = defineAsyncComponent(() => import('../components/ViewDialog.vue'))
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const ImportDialog = defineAsyncComponent(() => import('../components/ImportDialog.vue'))
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
remote: {
httpRequest: getJudgeList,
params: { contest_id: '', name: '' }
},
filters: [
{
type: 'select',
prop: 'course_id',
label: '赛项',
placeholder: '请选择赛项'
},
{ type: 'input', prop: 'student_name', label: '专家姓名', placeholder: '请输入专家姓名' }
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '赛项名称', prop: 'name' },
{ label: '专家姓名', prop: 'length' },
{ label: '参赛ID', prop: 'teacher_names' },
{ label: '性别', prop: 'type_name' },
{ label: '学校', prop: 'score' },
{ label: '所在专业', prop: 'score' },
{ label: '所在年级', prop: 'score' },
{ label: '所在班级', prop: 'score' },
{ label: '训练次数', prop: 'score' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 200 }
]
}
const importVisible = $ref(false)
const rowData = ref<Judge | undefined | null>(null)
// 查看
let viewDialogVisible = $ref(false)
function handleView(row: Judge) {
rowData.value = row
viewDialogVisible = true
}
// 编辑
let dialogVisible = $ref(false)
function handleUpdate(row: Judge) {
rowData.value = row
dialogVisible = true
}
function onUpdateSuccess() {
appList?.refetch()
}
</script>
<template>
<AppCard title="评分专家管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" round :icon="Upload" @click="importVisible = true">批量导入</el-button>
</template>
<template #table-x="{ row }: { row: Judge }">
<el-button type="primary" round @click="handleView(row)" v-permission="'v1-backend-experiment-view'"
>查看</el-button
>
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'"
>编辑</el-button
>
</template>
</AppList>
</AppCard>
<!-- 查看 -->
<ViewDialog v-model="viewDialogVisible" :data="rowData" v-if="viewDialogVisible && rowData"></ViewDialog>
<!-- 编辑 -->
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
<!-- 批量导入 -->
<ImportDialog v-model="importVisible" @update="onUpdateSuccess" v-if="importVisible"></ImportDialog>
</template>
......@@ -63,10 +63,24 @@ const adminMenus: IMenuItem[] = [
// name: '成绩分析',
// path: '/admin/contest/score'
// },
// {
// name: '技能大赛',
// path: '/admin/contest'
// },
{
name: '技能大赛',
path: '/admin/contest',
children: [
{
name: '赛项管理',
path: '/admin/contest/items'
},
{
name: '参赛选手管理',
path: '/admin/contest/contestants'
},
{
name: '评分专家管理',
path: '/admin/contest/judges'
}
]
},
{
name: '系统管理',
path: '/admin/system',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论