提交 1ffc22ab authored 作者: lhh's avatar lhh

实验管理需求开发

上级 39f896f5
......@@ -193,3 +193,20 @@ export function exportDrawLotStudentList(params: { draw_rule_id: string }) {
export function getStudentDrawLotInfo(params: { draw_rule_id: string; student_id: string }) {
return httpRequest.get('/api/resource/v1/backend/competition-draw/student-draw-info', { params })
}
// 获取学员的抽签详情
export function getExperimentsList(params: {
type: number; name?: string; 'per-page': number
}) {
return httpRequest.get('/api/resource/v1/backend/competition/experiments', { params })
}
// 赛项绑定实验
export function bindExperiment(data: { id: string; experiment_id: any }) {
return httpRequest.post('/api/resource/v1/backend/competition/bind-experiment', data)
}
// 删除绑定的实验
export function unbindExperiment(data: { id: string, experiment_id: string }) {
return httpRequest.post('/api/resource/v1/backend/competition/unbind-experiment', data)
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ import { pick } from 'lodash-es'
import dayjs from 'dayjs'
import isBetween from 'dayjs/plugin/isBetween'
import { createContest, updateContest } from '../api'
import { createContest, updateContest, getExamList } from '../api'
import { useMapStore } from '@/stores/map'
import { useGetTeacherList } from '../composables/useGetTeacherList'
import { useAppConfig } from '@/composables/useAppConfig'
......@@ -70,14 +70,21 @@ const form = reactive({
{ name: '数据营销实操', is_show: '0', type: '2', url: '', platform_key: 'data_marketing' }
],
competition_platform_configs: [
{ name: appConfig.xExamLabel || '1+X理论考试', is_show: '1', type: '1', url: '', platform_key: 'x_exam' },
{
name: appConfig.labExamLabel || '商业数据分析实验',
is_show: '0',
type: '2',
name: appConfig.xExamLabel || '1+X理论考试',
is_show: '1',
type: '1',
url: '',
platform_key: 'career_data_analysis'
platform_key: 'x_exam',
exam_id: ''
},
// {
// name: appConfig.labExamLabel || '商业数据分析实验',
// is_show: '0',
// type: '2',
// url: '',
// platform_key: 'career_data_analysis'
// },
{ name: '数据营销实操', is_show: '0', type: '2', url: '', platform_key: 'data_marketing' }
]
})
......@@ -194,6 +201,10 @@ const title = $computed(() => {
// })
// 提交
function handleSubmit() {
function containsNumber(A: number, B: number): boolean {
// 将数字A和B转换成字符串,并检查A的字符串是否包含B的字符串
return A.toString().includes(B.toString())
}
formRef?.validate().then(() => {
const [firstDate, secondDate] = form.dateRange || []
const [firstDatetime, secondDatetime] = form.datetimeRange || []
......@@ -210,6 +221,19 @@ function handleSubmit() {
end_at: dayjs(secondDatetime).year(year).month(month).date(date).unix(),
apply_expiration_date: dayjs(form.apply_expiration_date).endOf('date').unix()
}
// 判断正式比赛理论答题时间和选择的考试
const findExam = examList.find(item => item.exam_id === form.competition_platform_configs[0].exam_id)
const examStartTime = new Date(findExam?.start_time || '').getTime()
const examEndTime = new Date(findExam?.start_time || '').getTime()
if (
containsNumber(examStartTime, mergedForm.start_at) !== true ||
containsNumber(examEndTime, mergedForm.start_at) !== true
) {
ElMessage({ message: `正式比赛理论答题时间与${findExam?.name}的考试时间不符`, type: 'warning' })
return false
}
const params: ContestUpdateParams = pick(mergedForm, [
'id',
'name',
......@@ -267,6 +291,17 @@ const clientList = [
{ label: '全媒体运营师赛项', value: 'all_media_operator' },
{ label: '网络主播赛项', value: 'network_anchor_competition' }
]
let examList = $ref<Record<string, any>[]>([])
// 获取关联考试列表
function fetchExamList() {
getExamList({ project: 'x1', 'per-page': 1000 }).then(res => {
examList = res.data.list || []
})
}
onMounted(() => {
fetchExamList()
})
</script>
<template>
......@@ -275,8 +310,9 @@ const clientList = [
:close-on-click-modal="false"
align-center
width="600px"
@update:modelValue="value => $emit('update:modelValue', value)">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
@update:modelValue="value => $emit('update:modelValue', value)"
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="165px">
<el-form-item label="客户端标识" prop="client_id">
<el-select v-model="form.client_id" style="width: 100%" clearable>
<el-option v-for="item in clientList" :key="item.value" :label="item.label" :value="item.value"></el-option>
......@@ -301,7 +337,8 @@ const clientList = [
v-for="item in technicalSupportUnitList"
:key="item.id"
:label="item.label"
:value="item.id"></el-option>
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="赛项类型" prop="type">
......@@ -320,19 +357,21 @@ const clientList = [
range-separator="至"
v-model="form.dateRange"
style="width: 100%"
@change="handleDateRangeChange" />
@change="handleDateRangeChange"
/>
</el-form-item>
<el-form-item label="正式比赛日期" prop="date">
<el-date-picker type="date" v-model="form.date" style="width: 100%" />
</el-form-item>
<el-form-item label="正式比赛时间" prop="datetimeRange">
<el-form-item label="正式比赛理论答题时间" prop="datetimeRange">
<el-time-picker
is-range
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
v-model="form.datetimeRange"
style="width: 100%" />
style="width: 100%"
/>
</el-form-item>
<el-form-item label="报名截止日期" prop="apply_expiration_date">
<el-date-picker type="date" v-model="form.apply_expiration_date" style="width: 100%" />
......@@ -344,11 +383,12 @@ const clientList = [
style="margin-bottom: 10px"
v-model="item.is_show"
v-for="item in form.train_platform_configs"
:key="item.platform_key">
:key="item.platform_key"
>
<div style="display: flex; align-items: center">
<!-- <span style="margin-right: 10px; width: 180px">{{ item.name }}</span> -->
<el-input v-model="item.name" style="margin-right: 10px; width: 200px" />
<el-input v-model="item.url" />
<el-input v-model="item.name" style="margin-right: 10px; max-width: 130px" />
<el-input v-model="item.url" style="width: 200px" />
</div>
</el-checkbox>
</el-form-item>
......@@ -359,11 +399,20 @@ const clientList = [
style="margin-bottom: 10px"
v-model="item.is_show"
v-for="item in form.competition_platform_configs"
:key="item.platform_key">
:key="item.platform_key"
>
<div style="display: flex; align-items: center">
<!-- <span style="margin-right: 10px; width: 180px">{{ item.name }}</span> -->
<el-input v-model="item.name" style="margin-right: 10px; width: 200px" />
<el-input v-model="item.url" />
<el-input v-model="item.name" style="margin-right: 10px; max-width: 130px" />
<el-input v-model="item.url" v-if="item.type === '2'" style="width: 200px" />
<el-select v-model="item.exam_id" filterable style="width: 200px" v-if="item.type === '1'">
<el-option
v-for="item in examList"
:key="item.exam_id"
:label="item.name"
:value="item.exam_id"
></el-option>
</el-select>
</div>
</el-checkbox>
</el-form-item>
......
<script setup lang="ts">
import { CirclePlus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
import { unbindExperiment, getContest } from '../api'
import { useMapStore } from '@/stores/map'
const FormDialog = defineAsyncComponent(() => import('./ViewExperimentFormDialog.vue'))
interface Props {
id: string
pid: string
}
const props = defineProps<Props>()
const types = useMapStore().getMapValuesByKey('experiment_type')
const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置
const listOptions = {
hasPagination: false,
remote: {
httpRequest: getContest,
params: { id: props.pid },
callback(res: any) {
let list: any = []
if (Object.keys(res?.detail?.experiment).length) {
list = [res?.detail?.experiment]
}
return { list: list }
}
},
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '实验名称', prop: 'name' },
{
label: '实验类型',
prop: 'type',
computed({ row }: { row: any }) {
const type = types.find(item => item.value === row.type)?.label
return type || ''
}
},
{
label: '实验指导老师',
prop: 'teachers',
computed({ row }: { row: any }) {
let name = ''
if (row.teachers) {
name = row.teachers[0]?.name || ''
}
return name
}
},
{
label: '实验学生人数',
prop: 'classes',
computed({ row }: { row: any }) {
let num = ''
if (row.classes) {
num = row.classes[0]?.student_total
}
return num
}
},
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 180 }
]
}
let dialogVisible = $ref(false)
const rowData = ref<any>()
// 新增
function handleAdd() {
rowData.value = undefined
dialogVisible = true
}
// 查阅
function handleView(row: any) {
const qaURL = `/admin/lab/experiment/${row.id}`
window.open(qaURL)
}
// 删除
function handleRemoveClass(row: any) {
ElMessageBox.confirm('确定要删除吗?', '提示').then(() => {
unbindExperiment({ id: props.id, experiment_id: row.id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
onUpdateSuccess()
})
})
}
function onUpdateSuccess() {
appList?.refetch()
}
</script>
<template>
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" :icon="CirclePlus" @click="handleAdd" v-permission="'competition-book-create'"
>关联</el-button
>
</template>
<template #table-x="{ row }">
<el-button link round type="info" @click="handleView(row)" v-permission="'competition-book-detail'"
>查阅</el-button
>
<el-button link round type="danger" @click="handleRemoveClass(row)" v-permission="'competition-book-delete'"
>删除</el-button
>
</template>
</AppList>
<FormDialog v-model="dialogVisible" :id="id" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
</template>
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { getExperimentsList, bindExperiment } from '../api'
interface Props {
id?: any
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const formRef = $ref<FormInstance>()
const form = reactive<any>({
id: ''
})
const title = $computed(() => {
return '关联实验'
})
let list = $ref<Record<string, any>[]>([])
const loading = ref(false)
// 获取关联考试列表
function fetchExamList(query?: string) {
loading.value = true
getExperimentsList({ type: 4, name: query, 'per-page': 1000 }).then((res: any) => {
loading.value = false
list = res.data?.list || []
})
}
onMounted(() => {
fetchExamList()
})
// 提交
function handleSubmit() {
bindExperiment({ id: props.id, experiment_id: form.id }).then(() => {
ElMessage({ message: '绑定成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
}
</script>
<template>
<el-dialog
:title="title"
:close-on-click-modal="false"
width="500px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
<el-form ref="formRef" :model="form" label-width="90px">
<el-form-item label="选择实验">
<el-select
v-model="form.id"
filterable
remote
reserve-keyword
placeholder="输入实验名称"
remote-show-suffix
:remote-method="fetchExamList"
:loading="loading"
style="width: 340px"
>
<el-option v-for="item in list" :key="item.value" :label="item.name" :value="item.id" />
</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>
......@@ -10,6 +10,7 @@ const ViewVideo = defineAsyncComponent(() => import('../components/ViewVideo.vue
const ViewQuestion = defineAsyncComponent(() => import('../components/ViewQuestion.vue'))
const ViewExam = defineAsyncComponent(() => import('../components/ViewExam.vue'))
const ViewDrawLots = defineAsyncComponent(() => import('../components/ViewDrawLots.vue'))
const ViewExperiment = defineAsyncComponent(() => import('../components/ViewExperiment.vue'))
const ScoringRulesDialog = defineAsyncComponent(() => import('../components/ScoringRulesDialog.vue'))
const ScoringExpertsDialog = defineAsyncComponent(() => import('../components/ScoringExpertsDialog.vue'))
const ContestantDialog = defineAsyncComponent(() => import('../components/ContestantDialog.vue'))
......@@ -121,10 +122,16 @@ function handleExperts() {
<template>
<AppCard title="查看赛项信息">
<template #header-aside>
<el-button type="primary" @click="scoringRulesVisible = true" v-permission="'competition-rule'">评分规则</el-button>
<el-button type="primary" @click="scoringRulesVisible = true" v-permission="'competition-rule'"
>评分规则</el-button
>
<el-button type="primary" @click="handleExperts" v-permission="'competition-bind-experts'">评分专家</el-button>
<el-button type="primary" @click="contestantVisible = true" v-permission="'competition-competitor-list'">参赛选手</el-button>
<el-button type="primary" @click="scoringRulesBookVisible = true" v-permission="'competition-rubric-update'">评分细则</el-button>
<el-button type="primary" @click="contestantVisible = true" v-permission="'competition-competitor-list'"
>参赛选手</el-button
>
<el-button type="primary" @click="scoringRulesBookVisible = true" v-permission="'competition-rubric-update'"
>评分细则</el-button
>
</template>
<div class="top" v-if="detail">
<div class="top-cover">
......@@ -137,11 +144,15 @@ function handleExperts() {
<el-descriptions-item label="主办单位:">{{ detail.host_unit.label }}</el-descriptions-item>
<el-descriptions-item label="指导教师:">{{ teacherText }}</el-descriptions-item>
<el-descriptions-item label="承办单位:">{{ orgText }}</el-descriptions-item>
<el-descriptions-item label="赛项周期:">{{ formatDate(detail.start_range) }} ~ {{ formatDate(detail.end_range) }}</el-descriptions-item>
<el-descriptions-item label="赛项周期:"
>{{ formatDate(detail.start_range) }} ~ {{ formatDate(detail.end_range) }}</el-descriptions-item
>
<el-descriptions-item label="技术支持单位:">{{ detail.technical_support_unit.label }}</el-descriptions-item>
<el-descriptions-item label="正式比赛日期:">{{ formatDate(detail.start_at) }}</el-descriptions-item>
<el-descriptions-item label="生效状态:">{{ statusText }}</el-descriptions-item>
<el-descriptions-item label="正式比赛时间:">{{ formatTime(detail.start_at) }} ~ {{ formatTime(detail.end_at) }}</el-descriptions-item>
<el-descriptions-item label="正式比赛时间:"
>{{ formatTime(detail.start_at) }} ~ {{ formatTime(detail.end_at) }}</el-descriptions-item
>
<el-descriptions-item label="专家组长:">{{ expertLeadersText }}</el-descriptions-item>
<el-descriptions-item label="专家:">{{ expertMembersText }}</el-descriptions-item>
</el-descriptions>
......@@ -159,21 +170,38 @@ function handleExperts() {
<AppCard title="大赛试卷">
<ViewExam :id="id"></ViewExam>
</AppCard>
<AppCard title="关联实验">
<ViewExperiment :id="detail?.id || ''" :pid="id"></ViewExperiment>
</AppCard>
<AppCard title="抽签加密">
<ViewDrawLots :id="id"></ViewDrawLots>
</AppCard>
<!-- 评分规则 -->
<ScoringRulesDialog v-model="scoringRulesVisible" :disabled="isStarted" @update="fetchRule" v-if="scoringRulesVisible && detail"></ScoringRulesDialog>
<ScoringRulesDialog
v-model="scoringRulesVisible"
:disabled="isStarted"
@update="fetchRule"
v-if="scoringRulesVisible && detail"
></ScoringRulesDialog>
<!-- 评分专家 -->
<ScoringExpertsDialog
v-model="scoringExpertsVisible"
:disabled="isStarted"
@update="fetchExperts"
v-if="scoringExpertsVisible && detail"></ScoringExpertsDialog>
v-if="scoringExpertsVisible && detail"
></ScoringExpertsDialog>
<!-- 参赛选手 -->
<ContestantDialog v-model="contestantVisible" :disabled="isStarted" v-if="contestantVisible && detail"></ContestantDialog>
<ContestantDialog
v-model="contestantVisible"
:disabled="isStarted"
v-if="contestantVisible && detail"
></ContestantDialog>
<!-- 评分细则 -->
<ScoringRulesBookDialog v-model="scoringRulesBookVisible" :disabled="isStarted" v-if="scoringRulesBookVisible && detail"></ScoringRulesBookDialog>
<ScoringRulesBookDialog
v-model="scoringRulesBookVisible"
:disabled="isStarted"
v-if="scoringRulesBookVisible && detail"
></ScoringRulesBookDialog>
</template>
<style lang="scss">
......
......@@ -181,3 +181,8 @@ export function copyExperiment(data: { experiment_id: string }) {
export function deleteExperiment(data: { experiment_id: string }) {
return httpRequest.post('/api/resource/v1/backend/experiment/delete', data)
}
// 获取实验成绩规则
export function getLiveCommodity(params: { experiment_id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-commodity/all', { params })
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
import type { FormInstance } from 'element-plus'
import type { ExperimentItem } from '../types'
import { ElMessage } from 'element-plus'
import { getTripConfig, updateTripConfig } from '../api'
import { getTripConfig, updateTripConfig, getLiveCommodity } from '../api'
import { useConnection, useUserAttr, useMetaEvent, useTag, useGroup, useMaterial } from '../composables/useAllData'
import { useDocumentVisibility } from '@vueuse/core'
......@@ -29,6 +29,92 @@ const dmlURL = computed(() => {
return `${appConfig.dmlURL || import.meta.env.VITE_DML_URL}/trip/template?experiment_id=${props.data.id}`
})
const experimentConfig: any = [
{
id: 1,
name: '基础配置',
is_checked: false,
pid: 0,
children: [
{ id: 2, name: '连接管理', is_checked: false, pid: 1, children: [] },
{ id: 3, name: '用户属性管理', is_checked: false, pid: 1, children: [] },
{ id: 4, name: '事件属性管理', is_checked: false, pid: 1, children: [] }
]
},
{
id: 5,
name: '营销策划',
is_checked: false,
pid: 0,
children: []
},
{
id: 6,
name: '用户画像',
is_checked: false,
pid: 0,
children: []
},
{
id: 7,
name: '用户识别',
is_checked: false,
pid: 0,
children: [
{ id: 8, name: '标签管理', is_checked: false, pid: 7, children: [] },
{ id: 9, name: '群组管理', is_checked: false, pid: 7, children: [] }
]
},
{
id: 10,
name: '营销内容设计',
is_checked: false,
pid: 0,
children: [
{ id: 11, name: '文本资料管理', is_checked: false, pid: 10, children: [] },
{ id: 12, name: '图片资料管理', is_checked: false, pid: 10, children: [] },
{ id: 13, name: '卡券资料管理', is_checked: false, pid: 10, children: [] },
{ id: 14, name: '视频资料管理', is_checked: false, pid: 10, children: [] },
{ id: 15, name: 'H5资料管理', is_checked: false, pid: 10, children: [] },
{ id: 16, name: '二维码资料管理', is_checked: false, pid: 10, children: [] },
{ id: 17, name: '语言资料管理', is_checked: false, pid: 10, children: [] },
{ id: 18, name: '小程序资料管理', is_checked: false, pid: 10, children: [] }
]
},
{
id: 19,
name: '自动化营销',
is_checked: false,
pid: 0,
children: []
},
{
id: 20,
name: '直播带货',
is_checked: false,
pid: 0,
children: [
{ id: 21, name: '商品品类管理', is_checked: false, pid: 20, children: [] },
{ id: 22, name: '商品属性管理', is_checked: false, pid: 20, children: [] },
{ id: 23, name: '商品管理', is_checked: false, pid: 20, children: [] },
{ id: 24, name: '直播练习', is_checked: false, pid: 20, children: [] },
{ id: 25, name: '直播话术管理', is_checked: false, pid: 20, children: [] }
]
},
{
id: 26,
name: '数据分析',
is_checked: false,
pid: 0,
children: [
{ id: 27, name: '用户分析', is_checked: false, pid: 26, children: [] },
{ id: 28, name: '标签群组分析', is_checked: false, pid: 26, children: [] },
{ id: 29, name: '事件分析', is_checked: false, pid: 26, children: [] },
{ id: 30, name: '营销分析', is_checked: false, pid: 26, children: [] }
]
}
]
const formRef = $ref<FormInstance>()
const form = reactive({
experiment_id: props.data.id,
......@@ -43,7 +129,10 @@ const form = reactive({
ids: ['教师维护的用户和事件数据'],
tag_ids: [],
group_ids: [],
material_ids: []
material_ids: [],
auth_config: experimentConfig,
is_use_common_live_commodities: 1,
live_commodity_ids: []
})
// 模板列表
......@@ -81,7 +170,13 @@ function fetchInfo() {
is_use_common_materials: data.is_use_common_materials,
tag_ids,
group_ids,
material_ids
material_ids,
auth_config: data.auth_config ? data.auth_config : experimentConfig,
live_commodity_ids: data?.live_commodities.reduce((a: any, b: any) => {
a.push(b.id)
return a
}, []),
is_use_common_live_commodities: data.is_use_common_live_commodities
})
})
}
......@@ -92,7 +187,7 @@ watch(visibility, (current, previous) => {
if (current === 'visible' && previous === 'hidden') fetchInfo()
})
const step = ref(0)
const step = ref(-1)
// 上一步
function handlePrev() {
step.value--
......@@ -112,7 +207,9 @@ function handleSubmit() {
event_config: JSON.stringify(form.event_config),
tag_ids: JSON.stringify(form.tag_ids),
group_ids: JSON.stringify(form.group_ids),
material_ids: JSON.stringify(form.material_ids)
material_ids: JSON.stringify(form.material_ids),
auth_config: JSON.stringify(form.auth_config),
live_commodity_ids: JSON.stringify(form.live_commodity_ids)
}
updateTripConfig(params).then(() => {
ElMessage({ message: '保存成功', type: 'success' })
......@@ -121,13 +218,32 @@ function handleSubmit() {
})
})
}
// 多选
const handleH2Check = (item: any) => {
item.children.map((d: any) => {
d.is_checked = !item.is_checked
return d
})
}
const handleItemCheck = (item: any) => {
const isCheck = item.children.findIndex((d: any) => d.is_checked === false)
isCheck === -1 ? (item.is_checked = true) : (item.is_checked = false)
}
// 直播商品列表
const liveList: any = ref([])
getLiveCommodity({ experiment_id: props.data.id }).then((res: any) => {
liveList.value = res.data?.items || []
})
</script>
<template>
<el-dialog
title="配置数字营销实验"
:close-on-click-modal="false"
width="600px"
width="1000px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
<el-form ref="formRef" :model="form" label-suffix=":">
......@@ -136,7 +252,28 @@ function handleSubmit() {
<el-form-item label="实验类型">{{ data.type_name }} </el-form-item>
<el-form-item label="实验总成绩">{{ data.score }}</el-form-item>
</el-row>
<el-tabs v-model="step">
<el-tabs v-model="step" tab-position="left">
<el-tab-pane label="功能" :name="-1">
<div class="check-ul">
<div class="li" v-for="item in form?.auth_config" :key="item?.id">
<div class="check-h2">
<el-checkbox @click="handleH2Check(item)" v-model="item.is_checked" :label="item.name" size="large" />
</div>
<div class="check-item">
<template v-for="cItem in item.children" :key="cItem?.id">
<el-checkbox
@change="handleItemCheck(item)"
v-model="cItem.is_checked"
:label="cItem.name"
size="large"
/>
</template>
</div>
<el-divider />
</div>
</div>
<!-- <el-checkbox v-model="checked1" label="Option 1" size="large" /> -->
</el-tab-pane>
<el-tab-pane label="模板与连接" :name="0">
<el-form-item label="旅程模板" label-width="82" prop="itinerary_id">
<template v-if="templateList.length">
......@@ -244,6 +381,20 @@ function handleSubmit() {
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="直播商品" :name="6">
<el-form-item label="是否允许学生新建直播商品">
<el-radio-group v-model="form.is_use_common_live_commodities">
<el-radio :label="0"></el-radio>
<el-radio :label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="学生能够使用的商品">
<el-checkbox-group v-model="form.live_commodity_ids" :disabled="form.is_use_common_live_commodities === 1">
<el-checkbox :label="item?.id" v-for="item in liveList" :key="item?.id">{{ item?.title }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-tab-pane>
<!-- <el-tab-pane label="旅程资源" :name="10">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common">
......@@ -279,7 +430,9 @@ function handleSubmit() {
</el-form>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)" v-if="step === 0">关闭</el-button>
<el-button round auto-insert-space @click="$emit('update:modelValue', false)" v-if="step === -1"
>关闭</el-button
>
<el-button round auto-insert-space @click="handlePrev" v-else>上一步</el-button>
<el-button type="primary" round auto-insert-space @click="handleNext" v-if="step < 2">下一步</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit" v-else>保存</el-button>
......@@ -287,3 +440,23 @@ function handleSubmit() {
</template>
</el-dialog>
</template>
<style lang="scss">
.check-ul {
.li {
margin-bottom: 20px;
.check-h2 {
.el-checkbox__label {
color: #ba143e !important;
}
}
.check-item {
.el-checkbox__label {
color: #606266 !important;
}
}
}
.el-divider--horizontal {
margin: 0 !important;
}
}
</style>
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import type { ExperimentItem } from '../types'
import { ElMessage } from 'element-plus'
import { getTripConfig, updateTripConfig } from '../api'
import { useConnection, useUserAttr, useMetaEvent, useTag, useGroup, useMaterial } from '../composables/useAllData'
import { useDocumentVisibility } from '@vueuse/core'
import { useAppConfig } from '@/composables/useAppConfig'
const appConfig = useAppConfig()
const props = defineProps<{
data: ExperimentItem
}>()
const emit = defineEmits<{
(e: 'update'): void
(e: 'update:modelValue', visible: boolean): void
}>()
const { connectionList } = useConnection(props.data.id)
const { userAttrList } = useUserAttr(props.data.id)
const { metaEventList } = useMetaEvent(props.data.id)
const { tagList } = useTag(props.data.id)
const { groupList } = useGroup(props.data.id)
const { materialList } = useMaterial(props.data.id)
const dmlURL = computed(() => {
return `${appConfig.dmlURL || import.meta.env.VITE_DML_URL}/trip/template?experiment_id=${props.data.id}`
})
const formRef = $ref<FormInstance>()
const form = reactive({
experiment_id: props.data.id,
itinerary_id: '',
connect_ids: [],
user_attr_config: { is_all: true, items: [] },
event_config: { is_all: true, items: [] },
is_use_common: 0,
is_use_common_tags: 0,
is_use_common_groups: 0,
is_use_common_materials: 0,
ids: ['教师维护的用户和事件数据'],
tag_ids: [],
group_ids: [],
material_ids: []
})
// 模板列表
let templateList = $ref<{ id: string; name: string }[]>([])
function fetchInfo() {
getTripConfig({ experiment_id: props.data.id }).then(res => {
const data = res.data
if (!data.itinerary?.id) return
templateList = [data.itinerary]
const connect_ids = data.connections.map((item: any) => item.id)
if (!data.is_config_created) {
Object.assign(form, { itinerary_id: data.itinerary.id, connect_ids })
return
}
const user_attr_config = {
is_all: data.user_attr_config.is_all,
items: data.user_attr_config.items.map((item: any) => item.id)
}
const event_config = {
is_all: data.event_config.is_all,
items: data.event_config.items.map((item: any) => item.id)
}
const tag_ids = data.tags.map((item: any) => item.id)
const group_ids = data.groups.map((item: any) => item.id)
const material_ids = data.marketing_materials.map((item: any) => item.id)
Object.assign(form, {
itinerary_id: data.itinerary.id,
connect_ids,
user_attr_config,
event_config,
is_use_common: data.is_use_common,
is_use_common_tags: data.is_use_common_tags,
is_use_common_groups: data.is_use_common_groups,
is_use_common_materials: data.is_use_common_materials,
tag_ids,
group_ids,
material_ids
})
})
}
watchEffect(() => fetchInfo())
const visibility = useDocumentVisibility()
watch(visibility, (current, previous) => {
if (current === 'visible' && previous === 'hidden') fetchInfo()
})
const step = ref(0)
// 上一步
function handlePrev() {
step.value--
}
// 下一步
function handleNext() {
step.value++
}
// 提交
function handleSubmit() {
formRef?.validate().then(() => {
const params = {
...form,
connect_ids: JSON.stringify(form.connect_ids),
user_attr_config: JSON.stringify(form.user_attr_config),
event_config: JSON.stringify(form.event_config),
tag_ids: JSON.stringify(form.tag_ids),
group_ids: JSON.stringify(form.group_ids),
material_ids: JSON.stringify(form.material_ids)
}
updateTripConfig(params).then(() => {
ElMessage({ message: '保存成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
})
}
</script>
<template>
<el-dialog
title="配置数字营销实验"
:close-on-click-modal="false"
width="600px"
@update:modelValue="value => $emit('update:modelValue', value)"
>
<el-form ref="formRef" :model="form" label-suffix=":">
<el-row justify="space-between">
<el-form-item label="实验名称">{{ data.name }}</el-form-item>
<el-form-item label="实验类型">{{ data.type_name }} </el-form-item>
<el-form-item label="实验总成绩">{{ data.score }}</el-form-item>
</el-row>
<el-tabs v-model="step">
<el-tab-pane label="模板与连接" :name="0">
<el-form-item label="旅程模板" label-width="82" prop="itinerary_id">
<template v-if="templateList.length">
<el-select v-model="form.itinerary_id" style="width: 100%">
<el-option v-for="item in templateList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</template>
<template v-else>
<a :href="dmlURL" target="_blank">
<el-button type="primary">新建旅程模板</el-button>
</a>
</template>
</el-form-item>
<el-form-item label="连接" label-width="82" prop="connect_ids">
<el-select v-model="form.connect_ids" multiple style="width: 100%">
<el-option
v-for="item in connectionList"
:label="item.name"
:value="item.id"
:key="item.id"
disabled
></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="元数据" :name="1">
<el-form-item label="用户属性" label-width="82">
<el-radio-group v-model="form.user_attr_config.is_all">
<el-radio :label="true">全部</el-radio>
<el-radio :label="false">部分</el-radio>
</el-radio-group>
<el-select
v-model="form.user_attr_config.items"
multiple
style="margin-left: 40px"
v-if="!form.user_attr_config.is_all"
>
<el-option v-for="item in userAttrList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="事件" label-width="82">
<el-radio-group v-model="form.event_config.is_all">
<el-radio :label="true">全部</el-radio>
<el-radio :label="false">部分</el-radio>
</el-radio-group>
<el-select
v-model="form.event_config.items"
multiple
style="margin-left: 40px"
v-if="!form.event_config.is_all"
>
<el-option v-for="item in metaEventList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="用户/事件数据" :name="2">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户/事件数据" label-width="118">
<el-select v-model="form.ids" multiple style="width: 100%" disabled>
<el-option value="教师维护的用户和事件数据"></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="标签数据" :name="3">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common_tags">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="标签数据" label-width="118" prop="tag_ids">
<el-select v-model="form.tag_ids" multiple style="width: 100%">
<el-option v-for="item in tagList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="用户群组" :name="4">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common_groups">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="用户群组" label-width="118" prop="group_ids">
<el-select v-model="form.group_ids" multiple style="width: 100%">
<el-option v-for="item in groupList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="营销资料" :name="5">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common_materials">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="营销资料" label-width="118" prop="material_ids">
<el-select v-model="form.material_ids" multiple style="width: 100%">
<el-option v-for="item in materialList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<!-- <el-tab-pane label="旅程资源" :name="10">
<el-form-item label="是否允许学生新建如下资源">
<el-radio-group v-model="form.is_use_common">
<el-radio :label="1">否</el-radio>
<el-radio :label="0">是</el-radio>
</el-radio-group>
</el-form-item>
<template v-if="form.is_use_common === 1">
<el-divider />
<el-form-item label="用户/事件数据" label-width="118">
<el-select v-model="form.ids" multiple style="width: 100%" disabled>
<el-option value="教师维护的用户和事件数据"></el-option>
</el-select>
</el-form-item>
<el-form-item label="标签数据" label-width="118" prop="tag_ids">
<el-select v-model="form.tag_ids" multiple style="width: 100%">
<el-option v-for="item in tagList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="用户群组" label-width="118" prop="group_ids">
<el-select v-model="form.group_ids" multiple style="width: 100%">
<el-option v-for="item in groupList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="营销资料" label-width="118" prop="material_ids">
<el-select v-model="form.material_ids" multiple style="width: 100%">
<el-option v-for="item in materialList" :label="item.name" :value="item.id" :key="item.id"></el-option>
</el-select>
</el-form-item>
</template>
</el-tab-pane> -->
</el-tabs>
</el-form>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)" v-if="step === 0">关闭</el-button>
<el-button round auto-insert-space @click="handlePrev" v-else>上一步</el-button>
<el-button type="primary" round auto-insert-space @click="handleNext" v-if="step < 2">下一步</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit" v-else>保存</el-button>
</el-row>
</template>
</el-dialog>
</template>
......@@ -95,7 +95,7 @@ const listOptions = $computed(() => {
{ label: '生效状态', prop: 'status_name' },
{ label: '更新人', prop: 'updated_operator_name' },
{ label: '更新时间', prop: 'updated_time' },
{ label: '操作', slots: 'table-x', width: 220 }
{ label: '操作', slots: 'table-x', width: 250 }
]
}
})
......@@ -150,24 +150,28 @@ async function handleDelete(row: ExperimentItem) {
<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>
<el-button type="primary" :icon="CirclePlus" v-permission="'v1-backend-experiment-create'" @click="handleAdd"
>新增实验</el-button
>
</template>
<template #table-x="{ row }: { row: ExperimentItem }">
<el-button type="primary" round v-permission="'v1-backend-experiment-view'">
<router-link :to="`/admin/lab/experiment/${row.id}`" target="_blank">查看</router-link>
</el-button>
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'">编辑</el-button>
<el-button type="primary" round @click="handleUpdate(row)" v-permission="'v1-backend-experiment-update'"
>编辑</el-button
>
<el-button type="primary" round :icon="Delete" @click="handleDelete(row)">删除</el-button>
<!-- 功能按钮移入详情里 s v-if="false" -->
<el-dropdown style="margin-left: 12px">
<el-button type="primary" round :icon="MoreFilled"></el-button>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item>
<router-link :to="`/admin/lab/experiment/${row.id}`" target="_blank">查看</router-link>
</el-dropdown-item>
<el-dropdown-item @click="handleUpdate(row)">编辑</el-dropdown-item> -->
<el-dropdown-item :icon="Setting" @click="handleUpdateDML(row)" v-if="row.type === '4'">配置数字营销</el-dropdown-item>
<el-dropdown-item :icon="Setting" @click="handleUpdateDML(row)" v-if="row.type === '4'"
>配置数字营销</el-dropdown-item
>
<template v-if="!row.stu_commit_count">
<el-dropdown-item :icon="Edit" @click="handleUpdateGradeRules(row)">编辑成绩规则</el-dropdown-item>
<el-dropdown-item :icon="EditPen">
......@@ -179,6 +183,8 @@ async function handleDelete(row: ExperimentItem) {
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 功能按钮移入详情里 end -->
<!-- <template v-if="row.type === '4'">
<el-button
type="primary"
......@@ -216,8 +222,18 @@ async function handleDelete(row: ExperimentItem) {
</AppCard>
<FormDialog v-model="dialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="dialogVisible"></FormDialog>
<!-- 编辑实验成绩规则 -->
<GradeRulesDialog v-model="gradeRulesDialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="gradeRulesDialogVisible && rowData"></GradeRulesDialog>
<GradeRulesDialog
v-model="gradeRulesDialogVisible"
:data="rowData"
@update="onUpdateSuccess"
v-if="gradeRulesDialogVisible && rowData"
></GradeRulesDialog>
<!-- 配置数字营销实验 -->
<DMLFormDialog v-model="dmlDialogVisible" :data="rowData" v-if="dmlDialogVisible && rowData"></DMLFormDialog>
<CopyDialog v-model="copyDialogVisible" :data="rowData" @update="onUpdateSuccess" v-if="copyDialogVisible && rowData"></CopyDialog>
<CopyDialog
v-model="copyDialogVisible"
:data="rowData"
@update="onUpdateSuccess"
v-if="copyDialogVisible && rowData"
></CopyDialog>
</template>
<script setup lang="ts">
import type { ExperimentItem, ClassItem } from '../types'
import { CirclePlus } from '@element-plus/icons-vue'
import { CirclePlus, CopyDocument, Setting } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
......@@ -14,6 +14,8 @@ const StudentListDialog = defineAsyncComponent(() => import('../components/Stude
const ViewGradeRules = defineAsyncComponent(() => import('../components/ViewGradeRules.vue'))
const ViewReportRules = defineAsyncComponent(() => import('../components/ViewReportRules.vue'))
const ViewExam = defineAsyncComponent(() => import('../components/ViewExam.vue'))
const CopyDialog = defineAsyncComponent(() => import('../components/CopyDialog.vue'))
const DMLFormDialog = defineAsyncComponent(() => import('../components/DMLFormDialog.vue'))
interface Props {
id: string
......@@ -87,18 +89,46 @@ const reportRulesVisible = $ref(false)
const dmlURL = computed(() => {
return `${appConfig.dmlURL || import.meta.env.VITE_DML_URL}?experiment_id=${props.id}`
})
// 复制
let copyDialogVisible = $ref(false)
async function handleCopy() {
copyDialogVisible = true
}
// 配置数字营销实验
let dmlDialogVisible = $ref(false)
function handleUpdateDML() {
dmlDialogVisible = true
}
</script>
<template>
<AppCard title="实验管理">
<template #header>
<div>
<h2 class="app-card-hd__title">实验管理</h2>
<div class="btn-all" style="margin-bottom: 15px">
<el-button type="primary" v-if="detail?.type === '4'">
<a :href="dmlURL" target="_blank">进入实验平台</a>
</el-button>
<el-button type="primary" @click="gradeRulesVisible = true">查看成绩规则</el-button>
<el-button type="primary" @click="reportRulesVisible = true">查看报告规则</el-button>
<el-button type="primary" :icon="CopyDocument" @click="handleCopy()">复制实验</el-button>
<el-button type="primary" :icon="Setting" @click="handleUpdateDML()" :disabled="detail?.type !== '4'"
>配置数字营销</el-button
>
</div>
</div>
</template>
<el-descriptions title="基本信息" v-if="detail">
<template #extra>
<!-- <template #extra>
<el-button type="primary" v-if="detail.type === '4'">
<a :href="dmlURL" target="_blank">进入实验平台</a>
</el-button>
<el-button type="primary" @click="gradeRulesVisible = true">查看成绩规则</el-button>
<el-button type="primary" @click="reportRulesVisible = true">查看报告规则</el-button>
</template>
</template> -->
<el-descriptions-item :span="3" 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>
......@@ -163,4 +193,7 @@ const dmlURL = computed(() => {
></StudentListDialog>
<ViewGradeRules v-model="gradeRulesVisible" :data="detail" v-if="gradeRulesVisible && detail"></ViewGradeRules>
<ViewReportRules v-model="reportRulesVisible" :experiment_id="id" v-if="reportRulesVisible"></ViewReportRules>
<CopyDialog v-model="copyDialogVisible" :data="detail" v-if="copyDialogVisible && detail"></CopyDialog>
<!-- 配置数字营销实验 -->
<DMLFormDialog v-model="dmlDialogVisible" :data="detail" v-if="dmlDialogVisible && detail"></DMLFormDialog>
</template>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论