diff --git a/apps/admin/package.json b/apps/admin/package.json index e4134de..2a7477d 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -49,6 +49,7 @@ "path-browserify": "^1.0.1", "pdfjs-dist": "^5.4.530", "pinia": "3.0.4", + "pinyin-pro": "^3.28.0", "quill-toolbar-tip": "^0.1.0", "tailwind-merge": "3.4.0", "vue": "3.5.26", diff --git a/apps/admin/src/assets/imgs/event-card-bg.svg b/apps/admin/src/assets/imgs/event-card-bg.svg new file mode 100644 index 0000000..d3394db --- /dev/null +++ b/apps/admin/src/assets/imgs/event-card-bg.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/home-bg.svg b/apps/admin/src/assets/imgs/home-bg.svg new file mode 100644 index 0000000..5d7b15c --- /dev/null +++ b/apps/admin/src/assets/imgs/home-bg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/read-logo.png b/apps/admin/src/assets/imgs/read-logo.png new file mode 100644 index 0000000..e10a20a Binary files /dev/null and b/apps/admin/src/assets/imgs/read-logo.png differ diff --git a/apps/admin/src/assets/imgs/star-icon.svg b/apps/admin/src/assets/imgs/star-icon.svg new file mode 100644 index 0000000..54840ed --- /dev/null +++ b/apps/admin/src/assets/imgs/star-icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/team-bg.svg b/apps/admin/src/assets/imgs/team-bg.svg new file mode 100644 index 0000000..ca6703b --- /dev/null +++ b/apps/admin/src/assets/imgs/team-bg.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/title-bg.svg b/apps/admin/src/assets/imgs/title-bg.svg new file mode 100644 index 0000000..8b00664 --- /dev/null +++ b/apps/admin/src/assets/imgs/title-bg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/微信图片_20260204103609_668_5591.png b/apps/admin/src/assets/imgs/微信图片_20260204103609_668_5591.png new file mode 100644 index 0000000..204a421 Binary files /dev/null and b/apps/admin/src/assets/imgs/微信图片_20260204103609_668_5591.png differ diff --git a/apps/admin/src/components/common/wang-editor.vue b/apps/admin/src/components/common/wang-editor.vue new file mode 100644 index 0000000..5d99a37 --- /dev/null +++ b/apps/admin/src/components/common/wang-editor.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/apps/admin/src/components/custom/user/CompetitionLayout.vue b/apps/admin/src/components/custom/user/CompetitionLayout.vue new file mode 100644 index 0000000..029d2d9 --- /dev/null +++ b/apps/admin/src/components/custom/user/CompetitionLayout.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/apps/admin/src/enum/business.ts b/apps/admin/src/enum/business.ts index 64c701f..2677dbb 100644 --- a/apps/admin/src/enum/business.ts +++ b/apps/admin/src/enum/business.ts @@ -4,3 +4,13 @@ export enum PublishStatus { Processing = 2, Finished = 3, } + +export enum QuestionType { + SingleChoice = 'SingleChoice', // 单选题 + MultipleChoice = 'MultipleChoice', // 多选题 + TrueFalse = 'TrueFalse', // 判断题 + Text = 'Text', // 文本类型 + FillBlank = 'FillBlank', // 填空题 + Image = 'image', // 图片类型 + // 其他类型... +} diff --git a/apps/admin/src/router/elegant/imports.ts b/apps/admin/src/router/elegant/imports.ts index 4a91a9f..f192ab9 100644 --- a/apps/admin/src/router/elegant/imports.ts +++ b/apps/admin/src/router/elegant/imports.ts @@ -20,15 +20,18 @@ export const views: Record Promise import("@/views/_builtin/500/index.vue"), "iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"), login: () => import("@/views/_builtin/login/index.vue"), + "admin-home": () => import("@/views/admin-home/index.vue"), "competition_competition-add": () => import("@/views/competition/competition-add/index.vue"), "competition_competition-detail": () => import("@/views/competition/competition-detail/index.vue"), "competition_competition-list": () => import("@/views/competition/competition-list/index.vue"), dictionary: () => import("@/views/dictionary/index.vue"), + groups: () => import("@/views/groups/index.vue"), home: () => import("@/views/home/index.vue"), "question-store": () => import("@/views/question-store/index.vue"), "rank_rank-detail": () => import("@/views/rank/rank-detail/index.vue"), "rank_rank-list": () => import("@/views/rank/rank-list/index.vue"), results: () => import("@/views/results/index.vue"), + teams: () => import("@/views/teams/index.vue"), "template_template-detail": () => import("@/views/template/template-detail/index.vue"), "template_template-list": () => import("@/views/template/template-list/index.vue"), user: () => import("@/views/user/index.vue"), diff --git a/apps/admin/src/router/elegant/routes.ts b/apps/admin/src/router/elegant/routes.ts index 3d52f5e..52419cf 100644 --- a/apps/admin/src/router/elegant/routes.ts +++ b/apps/admin/src/router/elegant/routes.ts @@ -39,6 +39,16 @@ export const generatedRoutes: GeneratedRoute[] = [ hideInMenu: true } }, + { + name: 'admin-home', + path: '/admin-home', + component: 'layout.base$view.admin-home', + meta: { + title: 'admin-home', + i18nKey: 'route.admin-home', + hideInMenu: true + } + }, { name: 'competition', path: '/competition', @@ -94,15 +104,29 @@ export const generatedRoutes: GeneratedRoute[] = [ order: 6 } }, + { + name: 'groups', + path: '/groups', + component: 'layout.blank$view.groups', + meta: { + title: 'groups', + i18nKey: 'route.groups', + icon: 'material-symbols:group', + order: 2, + constant: true, + hideInMenu: true + } + }, { name: 'home', path: '/home', - component: 'layout.base$view.home', + component: 'layout.blank$view.home', meta: { title: 'home', i18nKey: 'route.home', - icon: 'mdi:monitor-dashboard', - order: 99, + icon: 'material-symbols:home', + order: 0, + constant: true, hideInMenu: true } }, @@ -189,6 +213,17 @@ export const generatedRoutes: GeneratedRoute[] = [ order: 2 } }, + { + name: 'teams', + path: '/teams', + component: 'layout.blank$view.teams', + meta: { + title: 'teams', + i18nKey: 'route.teams', + constant: true, + hideInMenu: true + } + }, { name: 'template', path: '/template', diff --git a/apps/admin/src/router/elegant/transform.ts b/apps/admin/src/router/elegant/transform.ts index abc66ea..a9bb2d7 100644 --- a/apps/admin/src/router/elegant/transform.ts +++ b/apps/admin/src/router/elegant/transform.ts @@ -166,11 +166,13 @@ const routeMap: RouteMap = { "403": "/403", "404": "/404", "500": "/500", + "admin-home": "/admin-home", "competition": "/competition", "competition_competition-add": "/competition/competition-add", "competition_competition-detail": "/competition/competition-detail", "competition_competition-list": "/competition/competition-list", "dictionary": "/dictionary", + "groups": "/groups", "home": "/home", "iframe-page": "/iframe-page/:url", "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", @@ -179,6 +181,7 @@ const routeMap: RouteMap = { "rank_rank-detail": "/rank/rank-detail", "rank_rank-list": "/rank/rank-list", "results": "/results", + "teams": "/teams", "template": "/template", "template_template-detail": "/template/template-detail", "template_template-list": "/template/template-list", diff --git a/apps/admin/src/router/routes/index.ts b/apps/admin/src/router/routes/index.ts index 910494c..2b5beb7 100644 --- a/apps/admin/src/router/routes/index.ts +++ b/apps/admin/src/router/routes/index.ts @@ -8,7 +8,18 @@ import { transformElegantRoutesToVueRoutes } from '../elegant/transform' * * @link https://github.com/reader-starjs/elegant-router?tab=readme-ov-file#custom-route */ -const customRoutes: CustomRoute[] = [] +const customRoutes: CustomRoute[] = [ + { + name: 'admin', + path: '/admin', + redirect: '/competition/competition-list', + meta: { + title: 'competition/competition-list', + constant: true, + hideInMenu: true, + }, + } as unknown as CustomRoute, +] /** create routes when the auth route mode is static */ export function createStaticRoutes() { diff --git a/apps/admin/src/styles/css/reset.css b/apps/admin/src/styles/css/reset.css index b813d30..e22e547 100644 --- a/apps/admin/src/styles/css/reset.css +++ b/apps/admin/src/styles/css/reset.css @@ -76,7 +76,7 @@ abbr:where([title]) { Remove the default font size and weight for headings. */ -h1, +/* h1, h2, h3, h4, @@ -84,7 +84,7 @@ h5, h6 { font-size: inherit; font-weight: inherit; -} +} */ /* Reset links to optimize for opt-in styling instead of opt-out. diff --git a/apps/admin/src/typings/api/question.d.ts b/apps/admin/src/typings/api/question.d.ts index 19be872..d812287 100644 --- a/apps/admin/src/typings/api/question.d.ts +++ b/apps/admin/src/typings/api/question.d.ts @@ -40,6 +40,8 @@ declare namespace Api { name: string /** question content */ questionContent: string + /** question type */ + questionType: QuestionType } /** add question library params */ diff --git a/apps/admin/src/typings/components.d.ts b/apps/admin/src/typings/components.d.ts index 706a3c1..9d7cda0 100644 --- a/apps/admin/src/typings/components.d.ts +++ b/apps/admin/src/typings/components.d.ts @@ -15,6 +15,7 @@ declare module 'vue' { AppProvider: typeof import('./../components/common/app-provider.vue')['default'] BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default'] ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default'] + CompetitionLayout: typeof import('./../components/custom/user/CompetitionLayout.vue')['default'] CountTo: typeof import('./../components/custom/count-to.vue')['default'] DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default'] ExceptionBase: typeof import('./../components/common/exception-base.vue')['default'] @@ -153,7 +154,7 @@ declare module 'vue' { PageHeader: typeof import('./../components/common/page-header.vue')['default'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] - RestBasicEditor: typeof import('./../components/common/rest-basic-editor/rest-basic-editor.vue')['default'] + RestBasicEditor: typeof import('./../components/common/rest-basic-editor/index.vue')['default'] RestDraggableResizable: typeof import('./../components/common/rest-draggable-resizable/rest-draggable-resizable.vue')['default'] RestHoverAciton: typeof import('./../components/common/rest-hover-aciton/rest-hover-aciton.vue')['default'] RestImgViewer: typeof import('./../components/common/rest-img-viewer/rest-img-viewer.vue')['default'] @@ -169,6 +170,7 @@ declare module 'vue' { TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default'] ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default'] UpFileDialog: typeof import('./../components/common/rest-basic-editor/components/up-file-dialog.vue')['default'] + WangEditor: typeof import('./../components/common/wang-editor.vue')['default'] WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] } } @@ -178,6 +180,7 @@ declare global { const AppProvider: typeof import('./../components/common/app-provider.vue')['default'] const BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default'] const ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default'] + const CompetitionLayout: typeof import('./../components/custom/user/CompetitionLayout.vue')['default'] const CountTo: typeof import('./../components/custom/count-to.vue')['default'] const DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default'] const ExceptionBase: typeof import('./../components/common/exception-base.vue')['default'] @@ -316,7 +319,7 @@ declare global { const PageHeader: typeof import('./../components/common/page-header.vue')['default'] const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] const ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] - const RestBasicEditor: typeof import('./../components/common/rest-basic-editor/rest-basic-editor.vue')['default'] + const RestBasicEditor: typeof import('./../components/common/rest-basic-editor/index.vue')['default'] const RestDraggableResizable: typeof import('./../components/common/rest-draggable-resizable/rest-draggable-resizable.vue')['default'] const RestHoverAciton: typeof import('./../components/common/rest-hover-aciton/rest-hover-aciton.vue')['default'] const RestImgViewer: typeof import('./../components/common/rest-img-viewer/rest-img-viewer.vue')['default'] @@ -332,5 +335,6 @@ declare global { const TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default'] const ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default'] const UpFileDialog: typeof import('./../components/common/rest-basic-editor/components/up-file-dialog.vue')['default'] + const WangEditor: typeof import('./../components/common/wang-editor.vue')['default'] const WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] } \ No newline at end of file diff --git a/apps/admin/src/typings/elegant-router.d.ts b/apps/admin/src/typings/elegant-router.d.ts index 88840d6..dcd099f 100644 --- a/apps/admin/src/typings/elegant-router.d.ts +++ b/apps/admin/src/typings/elegant-router.d.ts @@ -20,11 +20,13 @@ declare module "@elegant-router/types" { "403": "/403"; "404": "/404"; "500": "/500"; + "admin-home": "/admin-home"; "competition": "/competition"; "competition_competition-add": "/competition/competition-add"; "competition_competition-detail": "/competition/competition-detail"; "competition_competition-list": "/competition/competition-list"; "dictionary": "/dictionary"; + "groups": "/groups"; "home": "/home"; "iframe-page": "/iframe-page/:url"; "login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; @@ -33,6 +35,7 @@ declare module "@elegant-router/types" { "rank_rank-detail": "/rank/rank-detail"; "rank_rank-list": "/rank/rank-list"; "results": "/results"; + "teams": "/teams"; "template": "/template"; "template_template-detail": "/template/template-detail"; "template_template-list": "/template/template-list"; @@ -71,14 +74,17 @@ declare module "@elegant-router/types" { | "403" | "404" | "500" + | "admin-home" | "competition" | "dictionary" + | "groups" | "home" | "iframe-page" | "login" | "question-store" | "rank" | "results" + | "teams" | "template" | "user" >; @@ -102,15 +108,18 @@ declare module "@elegant-router/types" { | "500" | "iframe-page" | "login" + | "admin-home" | "competition_competition-add" | "competition_competition-detail" | "competition_competition-list" | "dictionary" + | "groups" | "home" | "question-store" | "rank_rank-detail" | "rank_rank-list" | "results" + | "teams" | "template_template-detail" | "template_template-list" | "user" diff --git a/apps/admin/src/views/admin-home/index.vue b/apps/admin/src/views/admin-home/index.vue new file mode 100644 index 0000000..d6e995f --- /dev/null +++ b/apps/admin/src/views/admin-home/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/apps/admin/src/views/home/modules/card-data.vue b/apps/admin/src/views/admin-home/modules/card-data.vue similarity index 100% rename from apps/admin/src/views/home/modules/card-data.vue rename to apps/admin/src/views/admin-home/modules/card-data.vue diff --git a/apps/admin/src/views/home/modules/creativity-banner.vue b/apps/admin/src/views/admin-home/modules/creativity-banner.vue similarity index 100% rename from apps/admin/src/views/home/modules/creativity-banner.vue rename to apps/admin/src/views/admin-home/modules/creativity-banner.vue diff --git a/apps/admin/src/views/home/modules/header-banner.vue b/apps/admin/src/views/admin-home/modules/header-banner.vue similarity index 100% rename from apps/admin/src/views/home/modules/header-banner.vue rename to apps/admin/src/views/admin-home/modules/header-banner.vue diff --git a/apps/admin/src/views/home/modules/line-chart.vue b/apps/admin/src/views/admin-home/modules/line-chart.vue similarity index 100% rename from apps/admin/src/views/home/modules/line-chart.vue rename to apps/admin/src/views/admin-home/modules/line-chart.vue diff --git a/apps/admin/src/views/home/modules/pie-chart.vue b/apps/admin/src/views/admin-home/modules/pie-chart.vue similarity index 100% rename from apps/admin/src/views/home/modules/pie-chart.vue rename to apps/admin/src/views/admin-home/modules/pie-chart.vue diff --git a/apps/admin/src/views/home/modules/project-news.vue b/apps/admin/src/views/admin-home/modules/project-news.vue similarity index 100% rename from apps/admin/src/views/home/modules/project-news.vue rename to apps/admin/src/views/admin-home/modules/project-news.vue diff --git a/apps/admin/src/views/groups/index.vue b/apps/admin/src/views/groups/index.vue new file mode 100644 index 0000000..3aadfb6 --- /dev/null +++ b/apps/admin/src/views/groups/index.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/apps/admin/src/views/home/index.vue b/apps/admin/src/views/home/index.vue index d6e995f..944ebb0 100644 --- a/apps/admin/src/views/home/index.vue +++ b/apps/admin/src/views/home/index.vue @@ -1,46 +1,278 @@ - - + diff --git a/apps/admin/src/views/question-store/modules/CategoryTree.vue b/apps/admin/src/views/question-store/modules/CategoryTree.vue index 16cc308..5cd4fc6 100644 --- a/apps/admin/src/views/question-store/modules/CategoryTree.vue +++ b/apps/admin/src/views/question-store/modules/CategoryTree.vue @@ -5,11 +5,13 @@ import { NFormItem, NInput, NModal, + NSelect, useDialog, useMessage, } from 'naive-ui' import { onMounted, ref, watch } from 'vue' import SvgIcon from '@/components/custom/svg-icon.vue' +import { QuestionType } from '@/enum/business' import { useDict } from '@/hooks/business/useDict' import { fetchAddQuestion, fetchDeleteQuestion, fetchGetQuestionListAll, fetchUpdateQuestion } from '@/service/api/question' @@ -27,6 +29,7 @@ interface CategoryItem { Id: number Name: string QuestionContent: string + QuestionType: QuestionType } // Mock Data - 模拟分类列表数据 @@ -35,7 +38,7 @@ const categoryList = ref([]) const selectedKey = ref(null) const showCategoryModal = ref(false) -const categoryForm = ref({ questionContent: '', name: '', Id: 0, questionType: 'SingleChoice' }) +const categoryForm = ref({ questionContent: '', name: '', Id: 0, questionType: QuestionType.Text }) const categoryOperation = ref<'add' | 'edit'>('add') const currentOperationNode = ref(null) @@ -63,6 +66,7 @@ function handleAddCategory() { categoryOperation.value = 'add' categoryForm.value.questionContent = '' categoryForm.value.name = '' + categoryForm.value.questionType = QuestionType.Text currentOperationNode.value = null showCategoryModal.value = true } @@ -71,6 +75,7 @@ function handleEditCategory(item: CategoryItem) { categoryOperation.value = 'edit' categoryForm.value.name = item.Name categoryForm.value.questionContent = item.QuestionContent + categoryForm.value.questionType = item.QuestionType currentOperationNode.value = item showCategoryModal.value = true } @@ -116,6 +121,7 @@ async function submitCategory() { questionContent: categoryForm.value.questionContent, name: categoryForm.value.name, id: 0, + questionType: categoryForm.value.questionType, }) if (error) { message.error('添加分类失败') @@ -129,6 +135,7 @@ async function submitCategory() { questionContent: categoryForm.value.questionContent, name: categoryForm.value.name, id: currentOperationNode.value.Id, + questionType: categoryForm.value.questionType, }) if (error) { message.error('更新分类失败') diff --git a/apps/admin/src/views/question-store/modules/QuestionList.vue b/apps/admin/src/views/question-store/modules/QuestionList.vue index b8bbd71..d6185d4 100644 --- a/apps/admin/src/views/question-store/modules/QuestionList.vue +++ b/apps/admin/src/views/question-store/modules/QuestionList.vue @@ -1,5 +1,6 @@ + + + + diff --git a/apps/admin/src/views/user copy/data/mock copy.ts b/apps/admin/src/views/user copy/data/mock copy.ts new file mode 100644 index 0000000..13be6c8 --- /dev/null +++ b/apps/admin/src/views/user copy/data/mock copy.ts @@ -0,0 +1,179 @@ +import type { CompetitionData } from '../types' +import img from '@/assets/imgs/q5-img.png' + +/** + * 模拟数据 + * 真实后端数据可能是: + * 活动详情接口,传活动id 获取 流程nodes + * 每次可能只返回一个n1,前端先展示n1,用户点击“抽题”后,再请求n2。其中questions 可能不是在n1就返回,而是点击抽提时候 传nodeId 去随机获取后前端保存 + * 1. 包含 3 个节点(N1, N2, N3) + * 2. 每个节点包含 2 个批次(汉字听一听, 汉字加一加) + * 3. 每个批次包含 2 个问题(Q1, Q2, Q3, Q4, Q5) + */ +export const mockData: CompetitionData = { + activityInfo: { + name: '星·辞海遨游', + }, + nodes: [ + { + nodeId: 'N1', + roundTitle: '第一轮', + moduleName: '汉字听一听', + batchName: '根据提示写汉字', + description: '请根据汉语拼音提示书写正确的汉字,时间为15秒。', + uiTemplate: 'TEMPLATE_DICTATION_HINT', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q1', + content: { pinyin: 'jiū,表示小鸟的叫声', hint: '表示小鸟的叫声' }, + answer: ['啾'], + }, + { + id: 'Q2', + content: { pinyin: 'yāo', hint: '形容草木茂盛美丽' }, + answer: ['夭'], + }, + ], + }, + { + nodeId: 'N2', + roundTitle: '第二轮', + moduleName: '汉字听一听', + batchName: '同音字', + description: '诗词理解', + uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q3', + content: { mainText: 'táng' }, + answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'], + }, + { + id: 'Q4', + content: { mainText: 'jù' }, + answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'], + }, + ], + }, + { + nodeId: 'N3', + roundTitle: '第二轮', + moduleName: '诗词理解', + batchName: '选择题', + description: '请选择正确的选项,时间为30秒。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q3', + content: { + questionText: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()', + options: [ + { label: 'A', text: '贬官,降职', image: img }, + { label: 'B', text: '搬家', image: img }, + { label: 'C', text: '一路向西游玩', image: img }, + ], + }, + answer: ['A'], + }, + { + id: 'Q4', + content: { + questionText: '《静夜思》中,“举头望明月”的下一句是()', + options: [ + { label: 'A', text: '低头思故乡', image: img }, + { label: 'B', text: '疑是地上霜', image: img }, + ], + }, + answer: ['A'], + }, + ], + }, + { + nodeId: 'N4', + roundTitle: '第三轮', + moduleName: '汉字加一加', + batchName: '部件组合', + description: '请写出含有指定部件的汉字,时间为60秒。', + uiTemplate: 'TEMPLATE_COMPONENT_ADD', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q5', + content: { component: '车' }, + answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'], + }, + ], + }, + { + nodeId: 'N5', + roundTitle: '第四轮', + moduleName: '词语听写', + batchName: '拼音写词', + description: '请根据汉语拼音提示书写正确的词语,时间为30秒。', + uiTemplate: 'TEMPLATE_WORD_DICTATION', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q6', + content: { pinyin: 'zhì rè', hint: '形容温度极高' }, + answer: ['炙热'], + }, + { + id: 'Q7', + content: { pinyin: 'hào hàn', hint: '形容广大繁多' }, + answer: ['浩瀚'], + }, + ], + }, + { + nodeId: 'N6', + roundTitle: '第五轮', + moduleName: '成语写一写', + batchName: '文字要求', + description: '请根据要求书写正确的成语,时间为60秒。', + uiTemplate: 'TEMPLATE_IDIOM_TEXT_REQ', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q8', + content: { requirement: '请写出含有反义字的四字成语。' }, + answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'], + }, + ], + }, + { + nodeId: 'N7', + roundTitle: '第六轮', + moduleName: '成语写一写', + batchName: '看图写成语', + description: '请根据图片书写正确的成语,时间为30秒。', + uiTemplate: 'TEMPLATE_IDIOM_IMAGE', + config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q9', + content: { hint: '比喻事情已定', image: img }, + answer: ['板上钉钉'], + }, + { + id: 'Q10', + content: { hint: '比喻事情已定', image: img }, + answer: ['板上钉钉'], + }, + ], + }, + ], +} + +export const uiTemplatesMapping = { + TEMPLATE_DICTATION_HINT: '汉字听写-提示', + TEMPLATE_DICTATION_HOMOPHONE: '汉字听写-同音', + TEMPLATE_COMPONENT_ADD: '汉字加一加', + TEMPLATE_WORD_DICTATION: '词语听写', + TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求', + TEMPLATE_IDIOM_IMAGE: '成语-看图', + TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题', +} diff --git a/apps/admin/src/views/user copy/data/mock.ts b/apps/admin/src/views/user copy/data/mock.ts new file mode 100644 index 0000000..15539e2 --- /dev/null +++ b/apps/admin/src/views/user copy/data/mock.ts @@ -0,0 +1,223 @@ +import type { CompetitionData } from '../types' +import img from '@/assets/imgs/q5-img.png' + +/** + * 模拟数据 + * 真实后端数据可能是: + * 活动详情接口,传活动id 获取 流程nodes + * 每次可能只返回一个n1,前端先展示n1,用户点击“抽题”后,再请求n2。其中questions 可能不是在n1就返回,而是点击抽提时候 传nodeId 去随机获取后前端保存 + * 1. 包含 3 个节点(N1, N2, N3) + * 2. 每个节点包含 2 个批次(汉字听一听, 汉字加一加) + * 3. 每个批次包含 2 个问题(Q1, Q2, Q3, Q4, Q5) + */ +export const mockData: CompetitionData = { + activityInfo: { + name: '星·辞海遨游', + }, + nodes: [ + { + nodeId: 'N3', + roundTitle: '第二轮', + moduleName: '诗词理解', + batchName: '选择题', + description: '请选择正确的选项,时间为30秒。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 300, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q3', + content: { + title: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()', + options: [ + { label: 'A', text: '贬官,降职', image: img }, + { label: 'B', text: '搬家', image: img }, + { label: 'C', text: '一路向西游玩', image: img }, + ], + }, + answer: ['A'], + }, + { + id: 'Q4', + content: { + title: '《静夜思》中,“举头望明月”的下一句是()', + options: [ + { label: 'A', text: '低头思故乡', image: img }, + { label: 'B', text: '疑是地上霜', image: img }, + ], + }, + answer: ['A'], + }, + ], + }, + { + nodeId: 'N1', + roundTitle: '第一轮', + moduleName: '汉字听一听', + batchName: '根据提示写汉字', + description: '请根据汉语拼音提示书写正确的汉字,时间为15秒。', + uiTemplate: 'TEMPLATE_DICTATION_HINT', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q1', + content: { + title: 'jiū,表示小鸟的叫声', + meta: { hint: '表示小鸟的叫声' }, + }, + answer: ['啾'], + }, + { + id: 'Q2', + content: { + title: 'yāo', + meta: { hint: '形容草木茂盛美丽' }, + }, + answer: ['夭'], + }, + ], + }, + { + nodeId: 'N2', + roundTitle: '第二轮', + moduleName: '汉字听一听', + batchName: '同音字', + description: '诗词理解', + uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q3', + content: { title: 'táng' }, + answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'], + }, + { + id: 'Q4', + content: { title: 'jù' }, + answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'], + }, + ], + }, + + { + nodeId: 'N4', + roundTitle: '第三轮', + moduleName: '汉字加一加', + batchName: '部件组合', + description: '请写出含有指定部件的汉字,时间为60秒。', + uiTemplate: 'TEMPLATE_COMPONENT_ADD', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q5', + content: { title: '车' }, + answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'], + }, + ], + }, + { + nodeId: 'N5', + roundTitle: '第四轮', + moduleName: '词语听写', + batchName: '拼音写词', + description: '请根据汉语拼音提示书写正确的词语,时间为30秒。', + uiTemplate: 'TEMPLATE_WORD_DICTATION', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q6', + content: { + title: 'zhì rè', + meta: { hint: '形容温度极高' }, + }, + answer: ['炙热'], + }, + { + id: 'Q7', + content: { + title: 'hào hàn', + meta: { hint: '形容广大繁多' }, + }, + answer: ['浩瀚'], + }, + ], + }, + { + nodeId: 'N6', + roundTitle: '第五轮', + moduleName: '成语写一写', + batchName: '文字要求', + description: '请根据要求书写正确的成语,时间为60秒。', + uiTemplate: 'TEMPLATE_IDIOM_TEXT_REQ', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q8', + content: { title: '请写出含有反义字的四字成语。' }, + answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'], + }, + ], + }, + { + nodeId: 'N7', + roundTitle: '第六轮', + moduleName: '成语写一写', + batchName: '看图写成语', + description: '请根据图片书写正确的成语,时间为30秒。', + uiTemplate: 'TEMPLATE_IDIOM_IMAGE', + config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q9', + content: { + title: '', + titleImage: img, + meta: { hint: '比喻事情已定' }, + }, + answer: ['板上钉钉'], + }, + { + id: 'Q10', + content: { + title: '', + titleImage: img, + meta: { hint: '比喻事情已定' }, + }, + answer: ['板上钉钉'], + }, + ], + }, + { + nodeId: 'N8', + roundTitle: '第七轮', + moduleName: '汉字辨析', + batchName: '选字填空', + description: '请选择正确的汉字填入括号中。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 20, questionCount: 1, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q11', + content: { + title: '下列哪个字是“没”的繁体字?', + options: [ + { label: 'A', text: '没', renderType: 'tianzige' }, + { label: 'B', text: '沒', renderType: 'tianzige' }, + { label: 'C', text: '殁', renderType: 'tianzige' }, + ], + }, + answer: ['B'], + }, + ], + }, + ], +} + +export const uiTemplatesMapping = { + TEMPLATE_DICTATION_HINT: '汉字听写-提示', + TEMPLATE_DICTATION_HOMOPHONE: '汉字听写-同音', + TEMPLATE_COMPONENT_ADD: '汉字加一加', + TEMPLATE_WORD_DICTATION: '词语听写', + TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求', + TEMPLATE_IDIOM_IMAGE: '成语-看图', + TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题', +} diff --git a/apps/admin/src/views/user copy/data/流程.png b/apps/admin/src/views/user copy/data/流程.png new file mode 100644 index 0000000..4a5965a Binary files /dev/null and b/apps/admin/src/views/user copy/data/流程.png differ diff --git a/apps/admin/src/views/user copy/index.vue b/apps/admin/src/views/user copy/index.vue new file mode 100644 index 0000000..cc8772d --- /dev/null +++ b/apps/admin/src/views/user copy/index.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/apps/admin/src/views/user copy/modules/AnswerAnalysisComp.vue b/apps/admin/src/views/user copy/modules/AnswerAnalysisComp.vue new file mode 100644 index 0000000..b55ffa5 --- /dev/null +++ b/apps/admin/src/views/user copy/modules/AnswerAnalysisComp.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/apps/admin/src/views/user copy/modules/CoverComp.vue b/apps/admin/src/views/user copy/modules/CoverComp.vue new file mode 100644 index 0000000..0f3e3ca --- /dev/null +++ b/apps/admin/src/views/user copy/modules/CoverComp.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/apps/admin/src/views/user copy/modules/GameComp.vue b/apps/admin/src/views/user copy/modules/GameComp.vue new file mode 100644 index 0000000..86b38b7 --- /dev/null +++ b/apps/admin/src/views/user copy/modules/GameComp.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/apps/admin/src/views/user copy/modules/IntroComp.vue b/apps/admin/src/views/user copy/modules/IntroComp.vue new file mode 100644 index 0000000..dea1dba --- /dev/null +++ b/apps/admin/src/views/user copy/modules/IntroComp.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/admin/src/views/user copy/modules/QuestionRenderer copy.vue b/apps/admin/src/views/user copy/modules/QuestionRenderer copy.vue new file mode 100644 index 0000000..ccf654d --- /dev/null +++ b/apps/admin/src/views/user copy/modules/QuestionRenderer copy.vue @@ -0,0 +1,120 @@ + + + diff --git a/apps/admin/src/views/user copy/modules/QuestionRenderer.vue b/apps/admin/src/views/user copy/modules/QuestionRenderer.vue new file mode 100644 index 0000000..2da2a86 --- /dev/null +++ b/apps/admin/src/views/user copy/modules/QuestionRenderer.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/apps/admin/src/views/user copy/readme.md b/apps/admin/src/views/user copy/readme.md new file mode 100644 index 0000000..c6cf668 --- /dev/null +++ b/apps/admin/src/views/user copy/readme.md @@ -0,0 +1,41 @@ +### 一、 文字版流程说明 + +1. 封面 (COVER) + - 动作 :点击“开始比赛”按钮。 + - 跳转 :进入第一轮的【抽题/介绍页面】。 + +2. 抽题/介绍页面 (INTRO) + - 展示 :当前环节名称(如“汉字听一听”)、规则说明、大圆形的“抽题”按钮。 + - 动作 :点击“抽题”或“开始答题”按钮。 + - 跳转 :进入当前题目的【答题页面】,同时开始倒计时。 + +3. 答题页面 (GAME) + - 展示 :题目内容(如拼音、图片)、倒计时、实时答题进度图表。 + - 触发条件 A :倒计时归零。 + - 触发条件 B :点击隐藏的“下一题”区域(手动结束)。 + - 跳转 :停止计时,进入当前题目的【答题图解页面】。 + +4. 答题图解页面 (ANSWER_ANALYSIS) + - 展示 :4个战队的答题原笔迹(支持点击放大)、AI评分结果、人工改分输入框。 + - 动作 :点击右上角“下一题”按钮。 + - 逻辑判断 : + - 情况 A(本轮还有题) :索引指向下一题 -> 回到【抽题/介绍页面】 (准备抽取下一题)。 + - 情况 B(本轮已结束,还有下一轮) :索引指向下一轮第一题 -> 回到【抽题/介绍页面】 (展示新环节介绍)。 + - 情况 C(全场结束) :提示“全场比赛结束” -> 回到【封面】 。 + +阅读之星 +feat(user): 新增用户端答题流程页面与相关功能 + +- 新增用户端答题流程主页面,包含封面、介绍、答题、答题图解四个状态 + +- 新增用户端组件:封面、介绍页、答题页、答题图解页、题目渲染器 + +- 新增用户端类型定义、模拟数据及业务常量 + +- 新增用户路由及类型声明 + +- 优化题库分类树,改为扁平化列表结构 + +- 完善比赛创建功能,增加轮次类型、题目分数类型及关联AP字段 + +- 修复模板删除函数调用参数错误 diff --git a/apps/admin/src/views/user copy/types.ts b/apps/admin/src/views/user copy/types.ts new file mode 100644 index 0000000..fe7b6dc --- /dev/null +++ b/apps/admin/src/views/user copy/types.ts @@ -0,0 +1,64 @@ +export type UiTemplateType = + | 'TEMPLATE_DICTATION_HINT' // 汉字听写-提示 (jiu) + | 'TEMPLATE_DICTATION_HOMOPHONE' // 汉字听写-同音 (tang) + | 'TEMPLATE_COMPONENT_ADD' // 汉字加一加 (车) + | 'TEMPLATE_WORD_DICTATION' // 词语听写 (zhi re) + | 'TEMPLATE_IDIOM_TEXT_REQ' // 成语-文字要求 (反义字) + | 'TEMPLATE_IDIOM_IMAGE' // 成语-看图 + | 'TEMPLATE_POETRY_MULTIPLE_CHOICE' // 诗词理解-选择题 + +export interface QuestionOption { + label: string // A, B, C + text: string // 选项文本/值 + image?: string // 选项图片 + renderType?: 'text' | 'image' | 'tianzige' | 'mixed' // 关键字段:控制选项渲染样式 +} + +export interface QuestionContent { + // 核心标准字段 (对应数据库列) + title: string // 题干/主要内容 (原 word, questionText, mainText, pinyin, requirement, component) + titleImage?: string // 题干图片 (原 image) + titleAudio?: string // 题干音频 (原 soundUrl) + options?: QuestionOption[] // 选项列表 + + // 扩展元数据 (对应数据库 JSON 字段) + meta?: { + hint?: string // 提示 + example?: string // 示例 + pinyin?: string // 如果 title 是汉字,这里存拼音;反之亦然 + definition?: string // 释义 + strokeCount?: number // 笔画数 + radicals?: string // 部首 + [key: string]: any + } +} + +export interface Question { + id: string + content: QuestionContent + answer: string[] +} + +export interface NodeConfig { + timeLimit: number + questionCount: number + scorePerQuestion: number +} + +export interface ActivityNode { + nodeId: string + roundTitle: string + moduleName: string + batchName: string + description: string + uiTemplate: UiTemplateType + config: NodeConfig + questions: Question[] +} + +export interface CompetitionData { + activityInfo: { + name: string + } + nodes: ActivityNode[] +} diff --git a/apps/admin/src/views/user/data/mock copy.ts b/apps/admin/src/views/user/data/mock copy.ts new file mode 100644 index 0000000..c9ddf67 --- /dev/null +++ b/apps/admin/src/views/user/data/mock copy.ts @@ -0,0 +1,178 @@ +import img from '@/assets/imgs/q5-img.png' + +/** + * 模拟数据 + * 真实后端数据可能是: + * 活动详情接口,传活动id 获取 流程nodes + * 每次可能只返回一个n1,前端先展示n1,用户点击“抽题”后,再请求n2。其中questions 可能不是在n1就返回,而是点击抽提时候 传nodeId 去随机获取后前端保存 + * 1. 包含 3 个节点(N1, N2, N3) + * 2. 每个节点包含 2 个批次(汉字听一听, 汉字加一加) + * 3. 每个批次包含 2 个问题(Q1, Q2, Q3, Q4, Q5) + */ +export const mockData = { + activityInfo: { + name: '星·辞海遨游', + }, + nodes: [ + { + nodeId: 'N1', + roundTitle: '第一轮', + moduleName: '汉字听一听', + batchName: '根据提示写汉字', + description: '请根据汉语拼音提示书写正确的汉字,时间为15秒。', + uiTemplate: 'TEMPLATE_DICTATION_HINT', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q1', + content: { pinyin: 'jiū,表示小鸟的叫声', hint: '表示小鸟的叫声' }, + answer: ['啾'], + }, + { + id: 'Q2', + content: { pinyin: 'yāo', hint: '形容草木茂盛美丽' }, + answer: ['夭'], + }, + ], + }, + { + nodeId: 'N2', + roundTitle: '第二轮', + moduleName: '汉字听一听', + batchName: '同音字', + description: '诗词理解', + uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q3', + content: { mainText: 'táng' }, + answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'], + }, + { + id: 'Q4', + content: { mainText: 'jù' }, + answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'], + }, + ], + }, + { + nodeId: 'N3', + roundTitle: '第二轮', + moduleName: '诗词理解', + batchName: '选择题', + description: '请选择正确的选项,时间为30秒。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q3', + content: { + questionText: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()', + options: [ + { label: 'A', text: '贬官,降职', image: img }, + { label: 'B', text: '搬家', image: img }, + { label: 'C', text: '一路向西游玩', image: img }, + ], + }, + answer: ['A'], + }, + { + id: 'Q4', + content: { + questionText: '《静夜思》中,“举头望明月”的下一句是()', + options: [ + { label: 'A', text: '低头思故乡', image: img }, + { label: 'B', text: '疑是地上霜', image: img }, + ], + }, + answer: ['A'], + }, + ], + }, + { + nodeId: 'N4', + roundTitle: '第三轮', + moduleName: '汉字加一加', + batchName: '部件组合', + description: '请写出含有指定部件的汉字,时间为60秒。', + uiTemplate: 'TEMPLATE_COMPONENT_ADD', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q5', + content: { component: '车' }, + answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'], + }, + ], + }, + { + nodeId: 'N5', + roundTitle: '第四轮', + moduleName: '词语听写', + batchName: '拼音写词', + description: '请根据汉语拼音提示书写正确的词语,时间为30秒。', + uiTemplate: 'TEMPLATE_WORD_DICTATION', + config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q6', + content: { pinyin: 'zhì rè', hint: '形容温度极高' }, + answer: ['炙热'], + }, + { + id: 'Q7', + content: { pinyin: 'hào hàn', hint: '形容广大繁多' }, + answer: ['浩瀚'], + }, + ], + }, + { + nodeId: 'N6', + roundTitle: '第五轮', + moduleName: '成语写一写', + batchName: '文字要求', + description: '请根据要求书写正确的成语,时间为60秒。', + uiTemplate: 'TEMPLATE_IDIOM_TEXT_REQ', + config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 }, + questions: [ + { + id: 'Q8', + content: { requirement: '请写出含有反义字的四字成语。' }, + answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'], + }, + ], + }, + { + nodeId: 'N7', + roundTitle: '第六轮', + moduleName: '成语写一写', + batchName: '看图写成语', + description: '请根据图片书写正确的成语,时间为30秒。', + uiTemplate: 'TEMPLATE_IDIOM_IMAGE', + config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q9', + content: { hint: '比喻事情已定', image: img }, + answer: ['板上钉钉'], + }, + { + id: 'Q10', + content: { hint: '比喻事情已定', image: img }, + answer: ['板上钉钉'], + }, + ], + }, + ], +} + +export const uiTemplatesMapping = { + TEMPLATE_DICTATION_HINT: '汉字听写-提示', + TEMPLATE_DICTATION_HOMOPHONE: '汉字听写-同音', + TEMPLATE_COMPONENT_ADD: '汉字加一加', + TEMPLATE_WORD_DICTATION: '词语听写', + TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求', + TEMPLATE_IDIOM_IMAGE: '成语-看图', + TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题', +} diff --git a/apps/admin/src/views/user/data/mock.ts b/apps/admin/src/views/user/data/mock.ts index 4346a08..15539e2 100644 --- a/apps/admin/src/views/user/data/mock.ts +++ b/apps/admin/src/views/user/data/mock.ts @@ -15,6 +15,40 @@ export const mockData: CompetitionData = { name: '星·辞海遨游', }, nodes: [ + { + nodeId: 'N3', + roundTitle: '第二轮', + moduleName: '诗词理解', + batchName: '选择题', + description: '请选择正确的选项,时间为30秒。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 300, questionCount: 2, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q3', + content: { + title: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()', + options: [ + { label: 'A', text: '贬官,降职', image: img }, + { label: 'B', text: '搬家', image: img }, + { label: 'C', text: '一路向西游玩', image: img }, + ], + }, + answer: ['A'], + }, + { + id: 'Q4', + content: { + title: '《静夜思》中,“举头望明月”的下一句是()', + options: [ + { label: 'A', text: '低头思故乡', image: img }, + { label: 'B', text: '疑是地上霜', image: img }, + ], + }, + answer: ['A'], + }, + ], + }, { nodeId: 'N1', roundTitle: '第一轮', @@ -26,12 +60,18 @@ export const mockData: CompetitionData = { questions: [ { id: 'Q1', - content: { pinyin: 'jiū,表示小鸟的叫声', hint: '表示小鸟的叫声' }, + content: { + title: 'jiū,表示小鸟的叫声', + meta: { hint: '表示小鸟的叫声' }, + }, answer: ['啾'], }, { id: 'Q2', - content: { pinyin: 'yāo', hint: '形容草木茂盛美丽' }, + content: { + title: 'yāo', + meta: { hint: '形容草木茂盛美丽' }, + }, answer: ['夭'], }, ], @@ -41,24 +81,25 @@ export const mockData: CompetitionData = { roundTitle: '第二轮', moduleName: '汉字听一听', batchName: '同音字', - description: '请根据拼音书写同音字,时间为30秒。', + description: '诗词理解', uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE', config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 }, questions: [ { id: 'Q3', - content: { mainText: 'táng' }, + content: { title: 'táng' }, answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'], }, { id: 'Q4', - content: { mainText: 'jù' }, + content: { title: 'jù' }, answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'], }, ], }, + { - nodeId: 'N3', + nodeId: 'N4', roundTitle: '第三轮', moduleName: '汉字加一加', batchName: '部件组合', @@ -68,13 +109,13 @@ export const mockData: CompetitionData = { questions: [ { id: 'Q5', - content: { component: '车' }, + content: { title: '车' }, answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'], }, ], }, { - nodeId: 'N4', + nodeId: 'N5', roundTitle: '第四轮', moduleName: '词语听写', batchName: '拼音写词', @@ -84,18 +125,24 @@ export const mockData: CompetitionData = { questions: [ { id: 'Q6', - content: { pinyin: 'zhì rè', hint: '形容温度极高' }, + content: { + title: 'zhì rè', + meta: { hint: '形容温度极高' }, + }, answer: ['炙热'], }, { id: 'Q7', - content: { pinyin: 'hào hàn', hint: '形容广大繁多' }, + content: { + title: 'hào hàn', + meta: { hint: '形容广大繁多' }, + }, answer: ['浩瀚'], }, ], }, { - nodeId: 'N5', + nodeId: 'N6', roundTitle: '第五轮', moduleName: '成语写一写', batchName: '文字要求', @@ -105,13 +152,13 @@ export const mockData: CompetitionData = { questions: [ { id: 'Q8', - content: { requirement: '请写出含有反义字的四字成语。' }, + content: { title: '请写出含有反义字的四字成语。' }, answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'], }, ], }, { - nodeId: 'N6', + nodeId: 'N7', roundTitle: '第六轮', moduleName: '成语写一写', batchName: '看图写成语', @@ -121,16 +168,47 @@ export const mockData: CompetitionData = { questions: [ { id: 'Q9', - content: { hint: '比喻事情已定', image: img }, + content: { + title: '', + titleImage: img, + meta: { hint: '比喻事情已定' }, + }, answer: ['板上钉钉'], }, { id: 'Q10', - content: { hint: '比喻事情已定', image: img }, + content: { + title: '', + titleImage: img, + meta: { hint: '比喻事情已定' }, + }, answer: ['板上钉钉'], }, ], }, + { + nodeId: 'N8', + roundTitle: '第七轮', + moduleName: '汉字辨析', + batchName: '选字填空', + description: '请选择正确的汉字填入括号中。', + uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE', + config: { timeLimit: 20, questionCount: 1, scorePerQuestion: 2 }, + questions: [ + { + id: 'Q11', + content: { + title: '下列哪个字是“没”的繁体字?', + options: [ + { label: 'A', text: '没', renderType: 'tianzige' }, + { label: 'B', text: '沒', renderType: 'tianzige' }, + { label: 'C', text: '殁', renderType: 'tianzige' }, + ], + }, + answer: ['B'], + }, + ], + }, ], } @@ -141,4 +219,5 @@ export const uiTemplatesMapping = { TEMPLATE_WORD_DICTATION: '词语听写', TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求', TEMPLATE_IDIOM_IMAGE: '成语-看图', + TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题', } diff --git a/apps/admin/src/views/user/index.vue b/apps/admin/src/views/user/index.vue index cc8772d..d415be9 100644 --- a/apps/admin/src/views/user/index.vue +++ b/apps/admin/src/views/user/index.vue @@ -1,5 +1,6 @@ diff --git a/apps/admin/src/views/user/modules/QuestionRenderer.vue b/apps/admin/src/views/user/modules/QuestionRenderer.vue index ea3aaac..2da2a86 100644 --- a/apps/admin/src/views/user/modules/QuestionRenderer.vue +++ b/apps/admin/src/views/user/modules/QuestionRenderer.vue @@ -8,8 +8,8 @@ const props = defineProps<{ }>() const pinyinChars = computed(() => { - if (props.template === 'TEMPLATE_WORD_DICTATION' && props.content.pinyin) { - return props.content.pinyin.split(' ') + if (props.template === 'TEMPLATE_WORD_DICTATION' && props.content.title) { + return props.content.title.split(' ') } return [] }) @@ -20,24 +20,24 @@ const pinyinChars = computed(() => {
- “{{ content.pinyin }}” + “{{ content.title }}”
- “{{ content.mainText }}” + “{{ content.title }}”
- {{ content.component }} + {{ content.title }}
@@ -71,10 +71,10 @@ const pinyinChars = computed(() => {
- {{ content.requirement }} + {{ content.title }}
@@ -82,15 +82,220 @@ const pinyinChars = computed(() => {
- idiom image + idiom image
+ +
+
+ {{ content.title }} +
+
+
+ +
+ +
+ + +
+ +
+
+
+ + + + +
+ {{ option.text }} +
+ + +
+
+ {{ option.label }} +
+ + + {{ option.text }} + +
+
+
+
+
未知的题目模板: {{ template }}
+ + diff --git a/apps/admin/src/views/user/types.ts b/apps/admin/src/views/user/types.ts index 9ea76a3..fe7b6dc 100644 --- a/apps/admin/src/views/user/types.ts +++ b/apps/admin/src/views/user/types.ts @@ -5,15 +5,32 @@ export type UiTemplateType = | 'TEMPLATE_WORD_DICTATION' // 词语听写 (zhi re) | 'TEMPLATE_IDIOM_TEXT_REQ' // 成语-文字要求 (反义字) | 'TEMPLATE_IDIOM_IMAGE' // 成语-看图 + | 'TEMPLATE_POETRY_MULTIPLE_CHOICE' // 诗词理解-选择题 + +export interface QuestionOption { + label: string // A, B, C + text: string // 选项文本/值 + image?: string // 选项图片 + renderType?: 'text' | 'image' | 'tianzige' | 'mixed' // 关键字段:控制选项渲染样式 +} export interface QuestionContent { - pinyin?: string - hint?: string - mainText?: string // 大字展示 - component?: string // 部件 - requirement?: string // 题目要求 - example?: string // 例子 - image?: string // 图片路径 + // 核心标准字段 (对应数据库列) + title: string // 题干/主要内容 (原 word, questionText, mainText, pinyin, requirement, component) + titleImage?: string // 题干图片 (原 image) + titleAudio?: string // 题干音频 (原 soundUrl) + options?: QuestionOption[] // 选项列表 + + // 扩展元数据 (对应数据库 JSON 字段) + meta?: { + hint?: string // 提示 + example?: string // 示例 + pinyin?: string // 如果 title 是汉字,这里存拼音;反之亦然 + definition?: string // 释义 + strokeCount?: number // 笔画数 + radicals?: string // 部首 + [key: string]: any + } } export interface Question { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 445fc82..9785b04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,6 +108,9 @@ importers: pinia: specifier: 3.0.4 version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) + pinyin-pro: + specifier: ^3.28.0 + version: 3.28.0 quill-toolbar-tip: specifier: ^0.1.0 version: 0.1.0(quill@2.0.3) @@ -4285,6 +4288,9 @@ packages: typescript: optional: true + pinyin-pro@3.28.0: + resolution: {integrity: sha512-mMRty6RisoyYNphJrTo3pnvp3w8OMZBrXm9YSWkxhAfxKj1KZk2y8T2PDIZlDDRsvZ0No+Hz6FI4sZpA6Ey25g==} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -9841,6 +9847,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + pinyin-pro@3.28.0: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8