updates
This commit is contained in:
parent
a67857a012
commit
1b7baf85df
|
|
@ -0,0 +1,226 @@
|
||||||
|
# IFLOW.md - 核心工作规则
|
||||||
|
|
||||||
|
## Global Protocols
|
||||||
|
|
||||||
|
所有操作必须严格遵循以下系统约束:
|
||||||
|
|
||||||
|
- **交互语言**:技术术语、工具与模型交互强制使用 **English**;用户输出强制使用 **中文**。
|
||||||
|
- **最小改动**:仅对需求做针对性改动,严禁影响用户现有的其他功能。
|
||||||
|
- **风格一致**:遵循项目现有的代码风格,使用项目已有的工具函数。
|
||||||
|
|
||||||
|
## Tool Priority
|
||||||
|
|
||||||
|
在执行任何操作前,必须按照以下顺序选择工具,严禁跳级使用:
|
||||||
|
|
||||||
|
**1. MCP 工具**:当 MCP 工具能够完成任务时,必须使用 MCP,禁止降级到内置工具或 Shell 命令。
|
||||||
|
|
||||||
|
**2. 内置工具**:仅当 MCP 工具**无法覆盖**该功能时,使用内置工具。
|
||||||
|
|
||||||
|
**3. Shell 命令**:Shell 命令是最后手段,同时遵循以下规则:
|
||||||
|
|
||||||
|
- 只读类安全操作允许直接执行
|
||||||
|
|
||||||
|
| 类别 | 安全操作示例 |
|
||||||
|
| ---------------- | ------------------------------------------------- |
|
||||||
|
| Git 只读操作 | `git status`、`git log`、`git diff`、`git branch` |
|
||||||
|
| 包管理器只读操作 | `npm list`、`pnpm why`、`pip show` |
|
||||||
|
| 容器只读操作 | `docker ps`、`docker logs` |
|
||||||
|
| 环境检查 | `node -v`、`python -version`、`which xxx` |
|
||||||
|
|
||||||
|
- 写入/删除/修改/安装等危险操作必须征得用户同意
|
||||||
|
|
||||||
|
| 类别 | 危险操作示例 |
|
||||||
|
| ------------ | ------------------------------------------------------------ |
|
||||||
|
| Git 写操作 | `commit`、`push`、`pull`、`merge`、`rebase`、`reset`、`checkout <branch>` |
|
||||||
|
| 文件删除 | `rm`、`rmdir`、清空目录 |
|
||||||
|
| 批量文件修改 | `sed -i`(多文件)、批量重命名 |
|
||||||
|
| 包管理写操作 | `pnpm install/uninstall`、`pnpm add/remove`、`uv add/remove` |
|
||||||
|
| 容器写操作 | `docker rm`、`docker rmi`、`docker-compose down` |
|
||||||
|
| 系统级操作 | 修改环境变量、修改系统配置文件 |
|
||||||
|
|
||||||
|
- 触发危险操作时告知用户
|
||||||
|
|
||||||
|
```
|
||||||
|
# 告知示例
|
||||||
|
!!!即将执行危险操作!!!:
|
||||||
|
命令:git push origin main
|
||||||
|
影响:将本地 main 分支的提交推送到远程仓库
|
||||||
|
|
||||||
|
是否继续?请回复"确认"或"取消"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
如果是对已有项目二次开发/修改bug,则遵循项目已有技术栈。
|
||||||
|
|
||||||
|
如果是从0到1开发新的项目,尽可能使用下方给出的技术栈:
|
||||||
|
|
||||||
|
### 后端 - Go(主力)
|
||||||
|
|
||||||
|
| 配置项 | 要求 |
|
||||||
|
| -------- | -------------------------------------- |
|
||||||
|
| 语言版本 | Go 1.21+ |
|
||||||
|
| 开发框架 | Gin |
|
||||||
|
| ORM框架 | GORM |
|
||||||
|
| 代码规范 | Google Go 编程规范 |
|
||||||
|
|
||||||
|
### 后端 - Java
|
||||||
|
|
||||||
|
| 配置项 | 要求 |
|
||||||
|
| -------- | -------------------------------------- |
|
||||||
|
| 语言版本 | Java 17 |
|
||||||
|
| 开发框架 | Spring Boot 3.x + Spring Cloud Alibaba |
|
||||||
|
| ORM框架 | MyBatis Plus |
|
||||||
|
| 包管理器 | Maven |
|
||||||
|
| 代码规范 | 阿里巴巴Java开发手册(嵩山版) |
|
||||||
|
|
||||||
|
### 后端 - Python(辅助/小工具)
|
||||||
|
|
||||||
|
| 配置项 | 要求 |
|
||||||
|
| ---------- | ------------------------------------------------------------ |
|
||||||
|
| 语言版本 | Python 3.10+ |
|
||||||
|
| 开发框架 | FastAPI(轻量级API)/ Typer(CLI工具)/ Streamlit(数据可视化) |
|
||||||
|
| 包管理工具 | uv |
|
||||||
|
| 代码规范 | PEP 8 + Google Python Style Guide |
|
||||||
|
| 虚拟环境 | **强制启用**(uv venv) |
|
||||||
|
|
||||||
|
### 后端 - 其他组件
|
||||||
|
|
||||||
|
| 组件 | 选型 |
|
||||||
|
| -------- | --------- |
|
||||||
|
| 数据库 | MySQL 8.x |
|
||||||
|
| 缓存 | Redis |
|
||||||
|
|
||||||
|
### 前端 - TypeScript + Vue 3
|
||||||
|
|
||||||
|
| 配置项 | 要求 |
|
||||||
|
| -------- | ---------------------------- |
|
||||||
|
| 语言版本 | TypeScript 5.x |
|
||||||
|
| 开发框架 | Vue 3(Composition API) |
|
||||||
|
| UI组件库 | TailWind CSS |
|
||||||
|
| 包管理器 | pnpm |
|
||||||
|
| 构建工具 | Vite |
|
||||||
|
| 代码规范 | ESLint(严格模式)+ Prettier |
|
||||||
|
|
||||||
|
### 桌面端 - Electron
|
||||||
|
|
||||||
|
| 配置项 | 要求 |
|
||||||
|
| -------- | ------------------ |
|
||||||
|
| 基础框架 | Vue 3 + TypeScript |
|
||||||
|
| 打包工具 | electron-builder |
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
在开发过程中,严格按照以下阶段顺序执行任务。
|
||||||
|
|
||||||
|
**格式要求**: 每次回复必须在开头标注 `【当前阶段: [阶段名称]】`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 0:上下文全量检索
|
||||||
|
|
||||||
|
**执行条件**:在生成任何建议或代码前。
|
||||||
|
|
||||||
|
**调用工具**:`mcp__auggie-mcp__codebase-retrieval`
|
||||||
|
|
||||||
|
**检索策略**:
|
||||||
|
|
||||||
|
- 禁止基于假设(Assumption)回答。
|
||||||
|
- 使用自然语言(NL)构建语义查询(Where/What/How)。
|
||||||
|
- **完整性检查**:必须获取相关类、函数、变量的完整定义与签名。若上下文不足,触发递归检索。
|
||||||
|
|
||||||
|
**需求对齐**:若检索后需求仍有模糊空间,**必须**向用户输出引导性问题列表,直至需求边界清晰(无遗漏、无冗余)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1: 产品需求分析
|
||||||
|
|
||||||
|
**角色**:产品经理
|
||||||
|
|
||||||
|
**方法**:通过`AskUserQuestion`工具进行多轮提问引导,直到需求完全量化。
|
||||||
|
|
||||||
|
**最小维度**:
|
||||||
|
|
||||||
|
- 目标用户与使用场景。
|
||||||
|
- 核心功能清单(按优先级 P0/P1/P2 排列)。
|
||||||
|
- 业务规则与约束条件。
|
||||||
|
|
||||||
|
**输出**:`requirement.md`(需求规格书)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2: UI/UX 设计
|
||||||
|
|
||||||
|
**角色**:UI/UX 设计师
|
||||||
|
|
||||||
|
**方法**:基于`requirement.md`,通过多轮提问引导,定义交互与视觉规范。
|
||||||
|
|
||||||
|
**最小维度**:
|
||||||
|
|
||||||
|
- 核心用户流程。
|
||||||
|
- 页面结构与布局。
|
||||||
|
- 组件状态定义。
|
||||||
|
|
||||||
|
**冲突检测**:与`requirement.md`中的约束进行一致性校验,如有冲突,必须提问澄清后再继续。
|
||||||
|
|
||||||
|
**输出**:`ui_ux_specifications.md`(UI/UX 规范)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3: 架构设计
|
||||||
|
|
||||||
|
**角色**:系统架构师
|
||||||
|
|
||||||
|
**方法**:基于`requirement.md`和`ui_ux_specifications.md`,通过多轮提问引导,设计技术方案。
|
||||||
|
|
||||||
|
**最小维度**:
|
||||||
|
|
||||||
|
- 技术栈选型(遵循本文档`Technology Stack`章节)。
|
||||||
|
- 系统分层、模块划分、目录结构。
|
||||||
|
- API 契约定义。
|
||||||
|
|
||||||
|
**冲突检测**:与`requirement.md`中的约束进行一致性校验,如有冲突,必须提问澄清后再继续。
|
||||||
|
|
||||||
|
**输出**:`architecture_design_document.md`(架构设计文档)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4: 代码实现
|
||||||
|
|
||||||
|
**角色**:全栈开发工程师
|
||||||
|
|
||||||
|
**方法**:
|
||||||
|
|
||||||
|
1. 根据 `requirement.md` 和 `architecture_design_document.md`,拆分开发任务
|
||||||
|
2. 在 `task_list.md` 中记录任务清单,将**待开发/已开发/跳过**的任务通过不同的复选框进行标记
|
||||||
|
3. 逐个任务开发,每个任务完成后更新状态
|
||||||
|
|
||||||
|
**输出**:`task_list.md`(任务清单,持续更新)、`deployment.md`(部署文档)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5: 代码审计
|
||||||
|
|
||||||
|
**执行条件**:每个任务模块开发完成后进行增量审计,全部完成后进行最终审计。
|
||||||
|
|
||||||
|
**角色**:代码审计工程师
|
||||||
|
|
||||||
|
**方法**:根据`task_list.md`,逐个对已完成代码进行 Code Review。
|
||||||
|
|
||||||
|
**审计范围**:
|
||||||
|
|
||||||
|
- 功能完整性:是否覆盖`requirement.md`对应功能的全部需求
|
||||||
|
- 代码质量:命名规范、无重复代码、适当抽象、注释完整
|
||||||
|
- 安全检查:输入验证、SQL注入防护、XSS防护、敏感数据处理、权限控制
|
||||||
|
- 性能检查:算法效率、数据库查询优化、资源释放
|
||||||
|
|
||||||
|
**问题分级与处理**:
|
||||||
|
|
||||||
|
| 级别 | 定义 | 处理方式 |
|
||||||
|
| ---- | -------------------------------- | ------------------ |
|
||||||
|
| P0 | 安全漏洞、数据风险、核心功能缺失 | 阻断发布,立即修复 |
|
||||||
|
| P1 | 功能不完整、明显性能问题 | 当前迭代必须修复 |
|
||||||
|
| P2 | 代码规范、可维护性问题 | 可选 |
|
||||||
|
| P3 | 优化建议 | 可选 |
|
||||||
|
|
||||||
|
**输出**:`audit_report.md`(审计报告)、`fix_changelog.md`(修复记录)
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
# 字典系统使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
字典系统是为了解决项目中数据字典不统一、硬编码问题而设计的统一管理方案。系统支持静态字典(本地定义)和动态字典(API获取),并提供了便捷的访问接口。
|
||||||
|
|
||||||
|
## 系统架构
|
||||||
|
|
||||||
|
字典系统由以下几个部分组成:
|
||||||
|
|
||||||
|
1. **工具类** (`src/utils/dict.ts`) - 提供静态字典数据和工具函数
|
||||||
|
2. **Store** (`src/stores/dict.ts`) - 管理动态字典数据,与现有Pinia架构集成
|
||||||
|
3. **API服务** (`src/service/api/dict.ts`) - 提供动态字典获取接口
|
||||||
|
4. **组件示例** - 展示如何在组件中使用字典系统
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 在组件中使用字典
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
|
import { getDictLabel, getDictColor } from '~/utils/dict'
|
||||||
|
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
|
||||||
|
// 获取字典项列表
|
||||||
|
const professionalCategoryItems = computed(() => dictStore.getDictItems('professionalCategory'))
|
||||||
|
|
||||||
|
// 获取字典项标签
|
||||||
|
const label = dictStore.getDictLabel('professionalCategory', 'science')
|
||||||
|
|
||||||
|
// 获取字典项颜色
|
||||||
|
const color = dictStore.getDictColor('educationalLevel', 'undergraduate')
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 在模板中使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<select v-model="selectedValue">
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('professionalCategory')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 显示标签 -->
|
||||||
|
<span>选中值: {{ dictStore.getDictLabel('professionalCategory', selectedValue) }}</span>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 静态字典工具函数
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取字典项列表
|
||||||
|
const items = getDictItems('professionalCategory')
|
||||||
|
|
||||||
|
// 获取字典标签
|
||||||
|
const label = getDictLabel('professionalCategory', 'science')
|
||||||
|
|
||||||
|
// 获取字典值
|
||||||
|
const value = getDictValue('professionalCategory', '理工类')
|
||||||
|
|
||||||
|
// 获取字典颜色
|
||||||
|
const color = getDictColor('professionalCategory', 'science')
|
||||||
|
|
||||||
|
// 获取字典项对象
|
||||||
|
const item = getDictItem('professionalCategory', 'science')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 动态字典管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 加载动态字典
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
|
||||||
|
// 加载特定类型的动态字典
|
||||||
|
await dictStore.loadDynamicDicts(['dynamic_type1', 'dynamic_type2'])
|
||||||
|
|
||||||
|
// 手动设置动态字典
|
||||||
|
dictStore.setDynamicDict('custom_type', [
|
||||||
|
{ label: '自定义项1', value: 'custom1' },
|
||||||
|
{ label: '自定义项2', value: 'custom2' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 清空动态字典
|
||||||
|
dictStore.clearDynamicDicts()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 字典数据结构
|
||||||
|
|
||||||
|
字典项接口定义:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface DictItem {
|
||||||
|
label: string // 显示标签
|
||||||
|
value: string | number // 实际值
|
||||||
|
disabled?: boolean // 是否禁用
|
||||||
|
color?: string // 颜色值
|
||||||
|
order?: number // 排序
|
||||||
|
[key: string]: any // 扩展属性
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 静态字典类型
|
||||||
|
|
||||||
|
目前系统已内置以下静态字典:
|
||||||
|
|
||||||
|
- `professionalCategory` - 专业类别
|
||||||
|
- `educationalLevel` - 学历层次
|
||||||
|
- `provinces` - 省份
|
||||||
|
- `subjectList` - 科目列表
|
||||||
|
- `gender` - 性别
|
||||||
|
- `status` - 状态
|
||||||
|
- `type` - 类型
|
||||||
|
|
||||||
|
## 动态字典API
|
||||||
|
|
||||||
|
动态字典通过API获取,支持以下接口:
|
||||||
|
|
||||||
|
- `GET /dict/list` - 获取字典列表
|
||||||
|
- `GET /dict/type/{type}` - 获取指定类型的字典
|
||||||
|
|
||||||
|
## 在现有组件中集成
|
||||||
|
|
||||||
|
以ScoreForm.vue为例,展示了如何将现有硬编码的选项替换为字典系统:
|
||||||
|
|
||||||
|
1. 导入字典Store
|
||||||
|
2. 使用computed属性获取字典项
|
||||||
|
3. 在模板中使用字典数据
|
||||||
|
|
||||||
|
## 扩展字典类型
|
||||||
|
|
||||||
|
如需添加新的静态字典类型:
|
||||||
|
|
||||||
|
1. 在 `src/utils/dict.ts` 中的 `staticDicts` 对象中添加新类型
|
||||||
|
2. 确保遵循 `DictItem[]` 的数据结构
|
||||||
|
|
||||||
|
如需添加新的动态字典类型:
|
||||||
|
|
||||||
|
1. 在后端API中提供相应的字典数据接口
|
||||||
|
2. 在前端调用 `dictStore.loadDynamicDicts()` 时指定类型
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **优先使用字典系统** - 避免在代码中硬编码选项数据
|
||||||
|
2. **统一管理** - 所有字典数据通过字典系统统一管理
|
||||||
|
3. **性能优化** - 字典数据通常在应用初始化时加载一次,后续直接使用
|
||||||
|
4. **扩展性** - 支持静态和动态字典,满足不同业务场景需求
|
||||||
|
5. **类型安全** - 提供完整的TypeScript类型定义
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 动态字典加载失败时,系统会继续使用静态字典数据
|
||||||
|
2. 字典数据在应用生命周期内会被缓存,避免重复请求
|
||||||
|
3. 在组件中使用字典前,确保字典数据已加载完成
|
||||||
|
4. 动态字典会覆盖同名的静态字典数据
|
||||||
|
|
@ -169,6 +169,7 @@ declare global {
|
||||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||||
|
const useDictStore: typeof import('./stores/dict')['useDictStore']
|
||||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||||
|
|
@ -495,6 +496,7 @@ declare module 'vue' {
|
||||||
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
||||||
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
||||||
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
||||||
|
readonly useDictStore: UnwrapRef<typeof import('./stores/dict')['useDictStore']>
|
||||||
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
||||||
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
||||||
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
BackToTop: typeof import('./components/BackToTop.vue')['default']
|
BackToTop: typeof import('./components/BackToTop.vue')['default']
|
||||||
copy: typeof import('./components/ScoreForm copy.vue')['default']
|
copy: typeof import('./components/ScoreForm copy.vue')['default']
|
||||||
|
DictDemo: typeof import('./components/DictDemo.vue')['default']
|
||||||
FilterBar: typeof import('./components/FilterBar.vue')['default']
|
FilterBar: typeof import('./components/FilterBar.vue')['default']
|
||||||
LoginForm: typeof import('./components/LoginForm.vue')['default']
|
LoginForm: typeof import('./components/LoginForm.vue')['default']
|
||||||
README: typeof import('./components/README.md')['default']
|
README: typeof import('./components/README.md')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
ScoreDictForm: typeof import('./components/ScoreDictForm.vue')['default']
|
||||||
ScoreForm: typeof import('./components/ScoreForm.vue')['default']
|
ScoreForm: typeof import('./components/ScoreForm.vue')['default']
|
||||||
TheCounter: typeof import('./components/TheCounter.vue')['default']
|
TheCounter: typeof import('./components/TheCounter.vue')['default']
|
||||||
TheFooter: typeof import('./components/TheFooter.vue')['default']
|
TheFooter: typeof import('./components/TheFooter.vue')['default']
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
<template>
|
||||||
|
<div class="dict-demo-container p-6">
|
||||||
|
<h2 class="text-xl font-bold mb-4">字典系统使用示例</h2>
|
||||||
|
|
||||||
|
<!-- 使用静态字典 -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">1. 使用静态字典</h3>
|
||||||
|
|
||||||
|
<!-- 专业类别选择 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block mb-1">专业类别:</label>
|
||||||
|
<select
|
||||||
|
v-model="selectedProfessionalCategory"
|
||||||
|
class="border rounded px-3 py-2 w-64"
|
||||||
|
@change="onProfessionalCategoryChange"
|
||||||
|
>
|
||||||
|
<option value="">请选择专业类别</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictItems.professionalCategory"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="mt-1 text-sm text-gray-600">
|
||||||
|
选中的标签: {{ selectedProfessionalCategoryLabel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学历层次选择 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block mb-1">学历层次:</label>
|
||||||
|
<select
|
||||||
|
v-model="selectedEducationalLevel"
|
||||||
|
class="border rounded px-3 py-2 w-64"
|
||||||
|
>
|
||||||
|
<option value="">请选择学历层次</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictItems.educationalLevel"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="mt-1 text-sm text-gray-600">
|
||||||
|
选中的颜色:
|
||||||
|
<span
|
||||||
|
:style="{ color: selectedEducationalLevelColor }"
|
||||||
|
class="font-semibold"
|
||||||
|
>
|
||||||
|
{{ selectedEducationalLevelColor }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 使用字典Store -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">2. 使用字典Store</h3>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block mb-1">省份选择:</label>
|
||||||
|
<select
|
||||||
|
v-model="selectedProvince"
|
||||||
|
class="border rounded px-3 py-2 w-64"
|
||||||
|
>
|
||||||
|
<option value="">请选择省份</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStoreItems.provinces"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="mt-1 text-sm text-gray-600">
|
||||||
|
选中的标签: {{ selectedProvinceLabel }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="loadDynamicDicts"
|
||||||
|
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 mr-2"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
{{ loading ? '加载中...' : '加载动态字典' }}
|
||||||
|
</button>
|
||||||
|
<span v-if="loadingMsg" class="text-sm text-gray-600">{{ loadingMsg }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 字典项展示 -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h3 class="text-lg font-semibold mb-2">3. 字典项展示</h3>
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div v-for="dictType in dictTypes" :key="dictType" class="border rounded p-3">
|
||||||
|
<h4 class="font-medium mb-2">{{ dictType }}</h4>
|
||||||
|
<ul class="text-sm">
|
||||||
|
<li
|
||||||
|
v-for="item in getDictItems(dictType)"
|
||||||
|
:key="item.value"
|
||||||
|
class="py-1"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="item.color"
|
||||||
|
class="inline-block w-3 h-3 rounded-full mr-1"
|
||||||
|
:style="{ backgroundColor: item.color }"
|
||||||
|
></span>
|
||||||
|
{{ item.label }} ({{ item.value }})
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { getDictItems, getDictLabel, getDictColor } from '~/utils/dict'
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
|
|
||||||
|
// 使用字典Store
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
|
||||||
|
// 静态数据
|
||||||
|
const dictItems = {
|
||||||
|
professionalCategory: getDictItems('professionalCategory'),
|
||||||
|
educationalLevel: getDictItems('educationalLevel'),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const selectedProfessionalCategory = ref('')
|
||||||
|
const selectedEducationalLevel = ref('')
|
||||||
|
const selectedProvince = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadingMsg = ref('')
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const selectedProfessionalCategoryLabel = computed(() => {
|
||||||
|
return getDictLabel('professionalCategory', selectedProfessionalCategory.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedEducationalLevelColor = computed(() => {
|
||||||
|
return getDictColor('educationalLevel', selectedEducationalLevel.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedProvinceLabel = computed(() => {
|
||||||
|
return dictStore.getDictLabel('provinces', selectedProvince.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dictStoreItems = computed(() => ({
|
||||||
|
provinces: dictStore.getDictItems('provinces')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const dictTypes = ['professionalCategory', 'educationalLevel', 'subjectList', 'gender', 'status']
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const onProfessionalCategoryChange = () => {
|
||||||
|
console.log('选中的专业类别:', selectedProfessionalCategory.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDynamicDicts = async () => {
|
||||||
|
loading.value = true
|
||||||
|
loadingMsg.value = '正在加载动态字典...'
|
||||||
|
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
loadingMsg.value = '动态字典加载完成'
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载动态字典失败:', error)
|
||||||
|
loadingMsg.value = '加载失败,请检查控制台'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取所有字典项
|
||||||
|
onMounted(() => {
|
||||||
|
// 可以在这里预加载需要的动态字典
|
||||||
|
// dictStore.loadDynamicDicts(['dynamic_type1', 'dynamic_type2'])
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dict-demo-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
<template>
|
||||||
|
<div class="score-form-container p-6 max-w-4xl mx-auto">
|
||||||
|
<h2 class="text-2xl font-bold mb-6">成绩信息表单</h2>
|
||||||
|
|
||||||
|
<form @submit.prevent="submitForm" class="space-y-6">
|
||||||
|
<!-- 省份 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">省份 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.province"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择省份</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('provinces')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学历层次 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">学历层次 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.educationalLevel"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择学历层次</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('educationalLevel')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 专业类别 -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">专业类别 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.professionalCategory"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择专业类别</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('professionalCategory')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 科目选择(多选) -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">科目列表 *</label>
|
||||||
|
<div class="grid grid-cols-3 gap-2">
|
||||||
|
<label
|
||||||
|
v-for="item in dictStore.getDictItems('subjectList')"
|
||||||
|
:key="item.value"
|
||||||
|
class="flex items-center space-x-2 p-2 border rounded cursor-pointer hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:value="item.value"
|
||||||
|
v-model="formData.subjectList"
|
||||||
|
class="h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分数输入 -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">专业分数</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.professionalScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">文化分数</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.culturalScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">英语分数</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.englishScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">语文分数</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.chineseScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<div class="flex justify-end space-x-4 pt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="resetForm"
|
||||||
|
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
:disabled="submitting"
|
||||||
|
>
|
||||||
|
{{ submitting ? '提交中...' : '提交' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- 表单数据预览 -->
|
||||||
|
<div v-if="Object.keys(formData).length" class="mt-8 p-4 bg-gray-50 rounded-md">
|
||||||
|
<h3 class="text-lg font-medium mb-2">表单数据预览</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm">
|
||||||
|
<div><strong>省份:</strong> {{ dictStore.getDictLabel('provinces', formData.province) }}</div>
|
||||||
|
<div><strong>学历层次:</strong> {{ dictStore.getDictLabel('educationalLevel', formData.educationalLevel) }}</div>
|
||||||
|
<div><strong>专业类别:</strong> {{ dictStore.getDictLabel('professionalCategory', formData.professionalCategory) }}</div>
|
||||||
|
<div><strong>科目列表:</strong> {{ getSubjectLabels(formData.subjectList).join(', ') }}</div>
|
||||||
|
<div><strong>专业分数:</strong> {{ formData.professionalScore }}</div>
|
||||||
|
<div><strong>文化分数:</strong> {{ formData.culturalScore }}</div>
|
||||||
|
<div><strong>英语分数:</strong> {{ formData.englishScore }}</div>
|
||||||
|
<div><strong>语文分数:</strong> {{ formData.chineseScore }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
|
import { useScoreStore } from '~/stores/score'
|
||||||
|
import type { SaveScoreRequest } from '~/service/api/score'
|
||||||
|
|
||||||
|
// 使用字典Store
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
const scoreStore = useScoreStore()
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = reactive<SaveScoreRequest>({
|
||||||
|
cognitioPolyclinic: '',
|
||||||
|
subjectList: [],
|
||||||
|
professionalCategory: '',
|
||||||
|
professionalCategoryChildren: [],
|
||||||
|
professionalCategoryChildrenScore: {},
|
||||||
|
professionalScore: 0,
|
||||||
|
culturalScore: 0,
|
||||||
|
englishScore: 0,
|
||||||
|
chineseScore: 0,
|
||||||
|
province: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取科目标签
|
||||||
|
const getSubjectLabels = (subjectValues: string[]): string[] => {
|
||||||
|
return subjectValues.map(value => dictStore.getDictLabel('subjectList', value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = async () => {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await scoreStore.saveScore(formData)
|
||||||
|
alert('提交成功!')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
alert('提交失败,请检查控制台')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.cognitioPolyclinic = ''
|
||||||
|
formData.subjectList = []
|
||||||
|
formData.professionalCategory = ''
|
||||||
|
formData.professionalCategoryChildren = []
|
||||||
|
formData.professionalCategoryChildrenScore = {}
|
||||||
|
formData.professionalScore = 0
|
||||||
|
formData.culturalScore = 0
|
||||||
|
formData.englishScore = 0
|
||||||
|
formData.chineseScore = 0
|
||||||
|
formData.province = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
// 加载必要的字典数据
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts(['dynamic_score_types']) // 如果有动态字典的话
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载字典数据失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有已保存的成绩数据,加载它
|
||||||
|
try {
|
||||||
|
const scoreInfo = await scoreStore.fetchScore()
|
||||||
|
if (scoreInfo) {
|
||||||
|
Object.assign(formData, {
|
||||||
|
cognitioPolyclinic: scoreInfo.cognitioPolyclinic || '',
|
||||||
|
subjectList: scoreInfo.subjectList || [],
|
||||||
|
professionalCategory: scoreInfo.professionalCategory || '',
|
||||||
|
professionalCategoryChildren: scoreInfo.professionalCategoryChildren || [],
|
||||||
|
professionalCategoryChildrenScore: scoreInfo.professionalCategoryChildrenScore || {},
|
||||||
|
professionalScore: scoreInfo.professionalScore || 0,
|
||||||
|
culturalScore: scoreInfo.culturalScore || 0,
|
||||||
|
englishScore: scoreInfo.englishScore || 0,
|
||||||
|
chineseScore: scoreInfo.chineseScore || 0,
|
||||||
|
province: scoreInfo.province || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载成绩数据失败:', error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, onMounted, watch, computed } from 'vue'
|
||||||
import { useScoreStore } from '~/stores/score'
|
import { useScoreStore } from '~/stores/score'
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
import { type SaveScoreRequest } from '~/service/api/score'
|
import { type SaveScoreRequest } from '~/service/api/score'
|
||||||
import message from '~/utils/message'
|
import message from '~/utils/message'
|
||||||
|
|
||||||
const scoreStore = useScoreStore()
|
const scoreStore = useScoreStore()
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
|
||||||
// 定义回传的数据类型
|
// 定义回传的数据类型
|
||||||
export interface ScoreFormData {
|
export interface ScoreFormData {
|
||||||
|
|
@ -54,15 +56,20 @@ const errors = ref({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- Options Data ---
|
// --- Computed Properties using Dict System ---
|
||||||
const electiveOptions = [
|
const electiveOptions = computed(() => {
|
||||||
|
// 使用自定义科目字典,如果没有则使用默认科目
|
||||||
|
return dictStore.getDictItems('subjectList') || [
|
||||||
{ label: '地理', value: '地理' },
|
{ label: '地理', value: '地理' },
|
||||||
{ label: '政治', value: '政治' },
|
{ label: '政治', value: '政治' },
|
||||||
{ label: '化学', value: '化学' },
|
{ label: '化学', value: '化学' },
|
||||||
{ label: '生物', value: '生物' },
|
{ label: '生物', value: '生物' },
|
||||||
]
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const majorCategoryOptions = [
|
const majorCategoryOptions = computed(() => {
|
||||||
|
// 使用专业类别字典
|
||||||
|
return dictStore.getDictItems('professionalCategory') || [
|
||||||
{ label: '美术与设计类', value: '美术与设计类' },
|
{ label: '美术与设计类', value: '美术与设计类' },
|
||||||
{ label: '播音与主持类', value: '播音与主持类' },
|
{ label: '播音与主持类', value: '播音与主持类' },
|
||||||
{ label: '表演类', value: '表演类' },
|
{ label: '表演类', value: '表演类' },
|
||||||
|
|
@ -72,27 +79,7 @@ const majorCategoryOptions = [
|
||||||
{ label: '戏曲类', value: '戏曲类' },
|
{ label: '戏曲类', value: '戏曲类' },
|
||||||
{ label: '体育类', value: '体育类' },
|
{ label: '体育类', value: '体育类' },
|
||||||
]
|
]
|
||||||
|
})
|
||||||
// --- Logic Methods ---
|
|
||||||
|
|
||||||
function getSubMajorOptions() {
|
|
||||||
switch (majorCategory.value) {
|
|
||||||
case '表演类':
|
|
||||||
return [
|
|
||||||
{ label: '服装表演', value: '服装表演' },
|
|
||||||
{ label: '戏剧影视导演', value: '戏剧影视导演' },
|
|
||||||
{ label: '戏剧影视表演', value: '戏剧影视表演' },
|
|
||||||
]
|
|
||||||
case '音乐类':
|
|
||||||
return [
|
|
||||||
{ label: '音乐表演声乐', value: '音乐表演声乐', disabled: selectedSubMajors.value.includes('音乐表演器乐') },
|
|
||||||
{ label: '音乐表演器乐', value: '音乐表演器乐', disabled: selectedSubMajors.value.includes('音乐表演声乐') },
|
|
||||||
{ label: '音乐教育', value: '音乐教育' },
|
|
||||||
]
|
|
||||||
default:
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleElectiveChange(value: string) {
|
function handleElectiveChange(value: string) {
|
||||||
console.warn('handleElectiveChange', value)
|
console.warn('handleElectiveChange', value)
|
||||||
|
|
@ -259,7 +246,16 @@ function initForm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
// 尝试加载字典数据
|
||||||
|
if (Object.keys(dictStore.allDicts).length === 0) {
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载字典数据失败,使用默认值:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!scoreStore.scoreInfo) {
|
if (!scoreStore.scoreInfo) {
|
||||||
// scoreStore.fetchScore().catch(() => {
|
// scoreStore.fetchScore().catch(() => {
|
||||||
// // 忽略错误,可能用户未设置成绩
|
// // 忽略错误,可能用户未设置成绩
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold mb-6">字典系统演示页面</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<!-- 字典展示区域 -->
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">字典项展示</h2>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-medium mb-2">专业类别</h3>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span
|
||||||
|
v-for="item in professionalCategoryItems"
|
||||||
|
:key="item.value"
|
||||||
|
class="px-3 py-1 rounded-full text-sm"
|
||||||
|
:style="{ backgroundColor: item.color ? item.color + '20' : '#f0f0f0', color: item.color || '#333' }"
|
||||||
|
>
|
||||||
|
{{ item.label }} ({{ item.value }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="font-medium mb-2">学历层次</h3>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span
|
||||||
|
v-for="item in educationalLevelItems"
|
||||||
|
:key="item.value"
|
||||||
|
class="px-3 py-1 rounded-full text-sm"
|
||||||
|
:style="{ backgroundColor: item.color ? item.color + '20' : '#f0f0f0', color: item.color || '#333' }"
|
||||||
|
>
|
||||||
|
{{ item.label }} ({{ item.value }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 class="font-medium mb-2">科目列表</h3>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span
|
||||||
|
v-for="item in subjectListItems"
|
||||||
|
:key="item.value"
|
||||||
|
class="px-3 py-1 rounded-full text-sm"
|
||||||
|
:style="{ backgroundColor: item.color ? item.color + '20' : '#f0f0f0', color: item.color || '#333' }"
|
||||||
|
>
|
||||||
|
{{ item.label }} ({{ item.value }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表单使用示例 -->
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">表单使用示例</h2>
|
||||||
|
|
||||||
|
<form @submit.prevent="submitForm" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">省份</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.province"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2"
|
||||||
|
>
|
||||||
|
<option value="">请选择省份</option>
|
||||||
|
<option
|
||||||
|
v-for="item in provincesItems"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="text-sm text-gray-500 mt-1">
|
||||||
|
选中值: {{ formData.province }}, 标签: {{ dictStore.getDictLabel('provinces', formData.province) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">学历层次</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.educationalLevel"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2"
|
||||||
|
>
|
||||||
|
<option value="">请选择学历层次</option>
|
||||||
|
<option
|
||||||
|
v-for="item in educationalLevelItems"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">专业类别</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.professionalCategory"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2"
|
||||||
|
>
|
||||||
|
<option value="">请选择专业类别</option>
|
||||||
|
<option
|
||||||
|
v-for="item in professionalCategoryItems"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">科目选择 (多选)</label>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<label
|
||||||
|
v-for="item in subjectListItems"
|
||||||
|
:key="item.value"
|
||||||
|
class="flex items-center space-x-2 p-2 border rounded cursor-pointer hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:value="item.value"
|
||||||
|
v-model="formData.subjectList"
|
||||||
|
class="h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
提交
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="loadDynamicDicts"
|
||||||
|
class="ml-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700"
|
||||||
|
>
|
||||||
|
加载动态字典
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 动态加载状态 -->
|
||||||
|
<div v-if="loading" class="mt-4 p-4 bg-blue-50 rounded-md">
|
||||||
|
<p>正在加载动态字典...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表单数据预览 -->
|
||||||
|
<div v-if="Object.keys(formData).length" class="mt-6 p-4 bg-gray-50 rounded-md">
|
||||||
|
<h3 class="text-lg font-medium mb-2">表单数据预览</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm">
|
||||||
|
<div><strong>省份:</strong> {{ dictStore.getDictLabel('provinces', formData.province) }}</div>
|
||||||
|
<div><strong>学历层次:</strong> {{ dictStore.getDictLabel('educationalLevel', formData.educationalLevel) }}</div>
|
||||||
|
<div><strong>专业类别:</strong> {{ dictStore.getDictLabel('professionalCategory', formData.professionalCategory) }}</div>
|
||||||
|
<div><strong>科目列表:</strong> {{ getSubjectLabels(formData.subjectList).join(', ') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
|
|
||||||
|
// 使用字典Store
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref({
|
||||||
|
province: '',
|
||||||
|
educationalLevel: '',
|
||||||
|
professionalCategory: '',
|
||||||
|
subjectList: [] as string[],
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 计算属性,获取字典项
|
||||||
|
const professionalCategoryItems = computed(() => dictStore.getDictItems('professionalCategory'))
|
||||||
|
const educationalLevelItems = computed(() => dictStore.getDictItems('educationalLevel'))
|
||||||
|
const subjectListItems = computed(() => dictStore.getDictItems('subjectList'))
|
||||||
|
const provincesItems = computed(() => dictStore.getDictItems('provinces'))
|
||||||
|
|
||||||
|
// 获取科目标签
|
||||||
|
const getSubjectLabels = (subjectValues: string[]): string[] => {
|
||||||
|
return subjectValues.map(value => dictStore.getDictLabel('subjectList', value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = () => {
|
||||||
|
alert(`表单数据已提交:\n${JSON.stringify(formData.value, null, 2)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载动态字典
|
||||||
|
const loadDynamicDicts = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
alert('动态字典加载成功!')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载动态字典失败:', error)
|
||||||
|
alert('加载动态字典失败,请查看控制台')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
// 检查是否已有字典数据,如果没有则加载
|
||||||
|
if (Object.keys(dictStore.allDicts).length === 0) {
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载动态字典失败,使用静态字典:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-6 max-w-4xl mx-auto">
|
||||||
|
<h1 class="text-2xl font-bold mb-6">高校专业志愿填报</h1>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">基本信息</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">省份 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.province"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择省份</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('provinces')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">学历层次 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.educationalLevel"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择学历层次</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('educationalLevel')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 专业选择 -->
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">专业选择</h2>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">专业类别 *</label>
|
||||||
|
<select
|
||||||
|
v-model="formData.professionalCategory"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择专业类别</option>
|
||||||
|
<option
|
||||||
|
v-for="item in dictStore.getDictItems('professionalCategory')"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">选考科目 (最多3门) *</label>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
|
||||||
|
<label
|
||||||
|
v-for="item in dictStore.getDictItems('subjectList')"
|
||||||
|
:key="item.value"
|
||||||
|
class="flex items-center space-x-2 p-2 border rounded cursor-pointer hover:bg-gray-50"
|
||||||
|
:class="{ 'bg-blue-50 border-blue-300': formData.subjectList.includes(item.value) }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:value="item.value"
|
||||||
|
v-model="formData.subjectList"
|
||||||
|
:disabled="formData.subjectList.length >= 3 && !formData.subjectList.includes(item.value)"
|
||||||
|
class="h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 mt-1">
|
||||||
|
已选择 {{ formData.subjectList.length }}/3 门科目
|
||||||
|
<span v-if="formData.subjectList.length >= 3" class="text-red-500">已达到上限</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 成绩信息 -->
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow">
|
||||||
|
<h2 class="text-xl font-semibold mb-4">成绩信息</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">专业成绩</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.professionalScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="300"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="请输入专业成绩 (0-300)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">文化成绩</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.culturalScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="750"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="请输入文化成绩 (0-750)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">语文成绩</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.chineseScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="150"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="请输入语文成绩 (0-150)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">英语成绩</label>
|
||||||
|
<input
|
||||||
|
v-model.number="formData.englishScore"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
max="150"
|
||||||
|
class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="请输入英语成绩 (0-150)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex justify-end space-x-4 pt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="resetForm"
|
||||||
|
class="px-6 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
@click="submitForm"
|
||||||
|
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
:disabled="!isFormValid"
|
||||||
|
>
|
||||||
|
{{ isSubmitting ? '提交中...' : '提交' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表单验证错误提示 -->
|
||||||
|
<div v-if="errors.length > 0" class="mt-4 p-4 bg-red-50 border border-red-200 rounded-md">
|
||||||
|
<h3 class="text-red-800 font-medium mb-2">请修正以下错误:</h3>
|
||||||
|
<ul class="list-disc list-inside text-red-700 space-y-1">
|
||||||
|
<li v-for="(error, index) in errors" :key="index">{{ error }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { useDictStore } from '~/stores/dict'
|
||||||
|
import { useScoreStore } from '~/stores/score'
|
||||||
|
import { SaveScoreRequest } from '~/service/api/score'
|
||||||
|
import message from '~/utils/message'
|
||||||
|
|
||||||
|
// 使用字典Store和成绩Store
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
const scoreStore = useScoreStore()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = reactive<SaveScoreRequest>({
|
||||||
|
cognitioPolyclinic: '',
|
||||||
|
subjectList: [],
|
||||||
|
professionalCategory: '',
|
||||||
|
professionalCategoryChildren: [],
|
||||||
|
professionalCategoryChildrenScore: {},
|
||||||
|
professionalScore: 0,
|
||||||
|
culturalScore: 0,
|
||||||
|
englishScore: 0,
|
||||||
|
chineseScore: 0,
|
||||||
|
province: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
const errors = ref<string[]>([])
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const isFormValid = computed(() => {
|
||||||
|
return formData.province &&
|
||||||
|
formData.professionalCategory &&
|
||||||
|
formData.subjectList.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = async () => {
|
||||||
|
errors.value = []
|
||||||
|
|
||||||
|
// 基本验证
|
||||||
|
if (!formData.province) {
|
||||||
|
errors.value.push('请选择省份')
|
||||||
|
}
|
||||||
|
if (!formData.professionalCategory) {
|
||||||
|
errors.value.push('请选择专业类别')
|
||||||
|
}
|
||||||
|
if (formData.subjectList.length === 0) {
|
||||||
|
errors.value.push('请至少选择一门选考科目')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.value.length > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true
|
||||||
|
try {
|
||||||
|
// 更新认知门诊字段 - 根据省份和专业类别推断
|
||||||
|
formData.cognitioPolyclinic = formData.province.includes('beijing') || formData.province.includes('shanghai')
|
||||||
|
? '综合改革'
|
||||||
|
: formData.subjectList.includes('physics') || formData.subjectList.includes('chemistry')
|
||||||
|
? '理科'
|
||||||
|
: '文科'
|
||||||
|
|
||||||
|
// 保存数据
|
||||||
|
await scoreStore.saveScore(formData)
|
||||||
|
message.success('志愿信息保存成功!', 2000)
|
||||||
|
|
||||||
|
// 可以在这里添加导航到其他页面的逻辑
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
message.error('提交失败,请检查网络连接或稍后重试', 3000)
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.cognitioPolyclinic = ''
|
||||||
|
formData.subjectList = []
|
||||||
|
formData.professionalCategory = ''
|
||||||
|
formData.professionalCategoryChildren = []
|
||||||
|
formData.professionalCategoryChildrenScore = {}
|
||||||
|
formData.professionalScore = 0
|
||||||
|
formData.culturalScore = 0
|
||||||
|
formData.englishScore = 0
|
||||||
|
formData.chineseScore = 0
|
||||||
|
formData.province = ''
|
||||||
|
errors.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
// 尝试加载字典数据
|
||||||
|
if (Object.keys(dictStore.allDicts).length === 0) {
|
||||||
|
try {
|
||||||
|
await dictStore.loadDynamicDicts()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载字典数据失败,使用默认值:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试加载已保存的成绩数据
|
||||||
|
try {
|
||||||
|
const scoreInfo = await scoreStore.fetchScore()
|
||||||
|
if (scoreInfo) {
|
||||||
|
// 使用解构赋值更新表单数据
|
||||||
|
Object.assign(formData, {
|
||||||
|
cognitioPolyclinic: scoreInfo.cognitioPolyclinic || '',
|
||||||
|
subjectList: scoreInfo.subjectList || [],
|
||||||
|
professionalCategory: scoreInfo.professionalCategory || '',
|
||||||
|
professionalCategoryChildren: scoreInfo.professionalCategoryChildren || [],
|
||||||
|
professionalCategoryChildrenScore: scoreInfo.professionalCategoryChildrenScore || {},
|
||||||
|
professionalScore: scoreInfo.professionalScore || 0,
|
||||||
|
culturalScore: scoreInfo.culturalScore || 0,
|
||||||
|
englishScore: scoreInfo.englishScore || 0,
|
||||||
|
chineseScore: scoreInfo.chineseScore || 0,
|
||||||
|
province: scoreInfo.province || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('加载成绩数据失败:', error)
|
||||||
|
// 忽略错误,用户可能没有保存过数据
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* 字典API服务
|
||||||
|
* 用于获取动态字典数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
import request from '~/service/request'
|
||||||
|
|
||||||
|
// 字典项接口
|
||||||
|
export interface DictItem {
|
||||||
|
label: string
|
||||||
|
value: string | number
|
||||||
|
disabled?: boolean
|
||||||
|
color?: string
|
||||||
|
order?: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典响应数据接口
|
||||||
|
export interface DictData {
|
||||||
|
type: string
|
||||||
|
items: DictItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典列表
|
||||||
|
* @param types 字典类型列表,如果为空则获取所有字典
|
||||||
|
* @returns 字典数据列表
|
||||||
|
*/
|
||||||
|
export function getDictionaryList(types?: string[]): Promise<DictData[]> {
|
||||||
|
const params: any = {}
|
||||||
|
if (types && types.length > 0) {
|
||||||
|
params.types = types.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.get<DictData[]>('/dict/list', {
|
||||||
|
params,
|
||||||
|
showLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个字典类型的数据
|
||||||
|
* @param type 字典类型
|
||||||
|
* @returns 字典项列表
|
||||||
|
*/
|
||||||
|
export function getDictionaryByType(type: string): Promise<DictItem[]> {
|
||||||
|
return request.get<DictItem[]>(`/dict/type/${type}`, {
|
||||||
|
showLoading: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要其他字典相关API,可以在这里添加
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { DictItem, DictType, getDictItems, staticDicts } from '~/utils/dict'
|
||||||
|
import { getDictionaryList } from '~/service/api/dict'
|
||||||
|
|
||||||
|
export const useDictStore = defineStore('dict', () => {
|
||||||
|
// 存储动态字典数据
|
||||||
|
const dynamicDicts = ref<DictType>({})
|
||||||
|
|
||||||
|
// 合并静态和动态字典
|
||||||
|
const allDicts = computed(() => ({
|
||||||
|
...staticDicts,
|
||||||
|
...dynamicDicts.value
|
||||||
|
}))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @returns 字典项列表
|
||||||
|
*/
|
||||||
|
function getDictItems(dictType: string): DictItem[] {
|
||||||
|
return allDicts.value[dictType] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项标签
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项标签
|
||||||
|
*/
|
||||||
|
function getDictLabel(dictType: string, value: string | number): string {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
const item = dictItems.find(item => item.value === value)
|
||||||
|
return item ? item.label : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项颜色
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项颜色
|
||||||
|
*/
|
||||||
|
function getDictColor(dictType: string, value: string | number): string {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
const item = dictItems.find(item => item.value === value)
|
||||||
|
return item ? item.color || '' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项对象
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项对象
|
||||||
|
*/
|
||||||
|
function getDictItem(dictType: string, value: string | number): DictItem | undefined {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
return dictItems.find(item => item.value === value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载动态字典数据
|
||||||
|
* @param dictTypes 字典类型列表,如果为空则加载所有动态字典
|
||||||
|
*/
|
||||||
|
async function loadDynamicDicts(dictTypes?: string[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 调用API获取动态字典数据
|
||||||
|
const response = await getDictionaryList(dictTypes)
|
||||||
|
|
||||||
|
// 更新动态字典数据
|
||||||
|
if (response && Array.isArray(response)) {
|
||||||
|
response.forEach((dictData: any) => {
|
||||||
|
if (dictData.type && Array.isArray(dictData.items)) {
|
||||||
|
dynamicDicts.value[dictData.type] = dictData.items
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载动态字典失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加或更新动态字典
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param items 字典项列表
|
||||||
|
*/
|
||||||
|
function setDynamicDict(dictType: string, items: DictItem[]): void {
|
||||||
|
dynamicDicts.value[dictType] = items
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空动态字典
|
||||||
|
*/
|
||||||
|
function clearDynamicDicts(): void {
|
||||||
|
dynamicDicts.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
dynamicDicts,
|
||||||
|
allDicts,
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
getDictItems,
|
||||||
|
getDictLabel,
|
||||||
|
getDictColor,
|
||||||
|
getDictItem,
|
||||||
|
|
||||||
|
// 动作
|
||||||
|
loadDynamicDicts,
|
||||||
|
setDynamicDict,
|
||||||
|
clearDynamicDicts,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot)
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useDictStore as any, import.meta.hot))
|
||||||
|
|
@ -24,6 +24,7 @@ declare module 'vue-router/auto-routes' {
|
||||||
'/agreement': RouteRecordInfo<'/agreement', '/agreement', Record<never, never>, Record<never, never>>,
|
'/agreement': RouteRecordInfo<'/agreement', '/agreement', Record<never, never>, Record<never, never>>,
|
||||||
'/contact-us': RouteRecordInfo<'/contact-us', '/contact-us', Record<never, never>, Record<never, never>>,
|
'/contact-us': RouteRecordInfo<'/contact-us', '/contact-us', Record<never, never>, Record<never, never>>,
|
||||||
'/demo/pop-confirm': RouteRecordInfo<'/demo/pop-confirm', '/demo/pop-confirm', Record<never, never>, Record<never, never>>,
|
'/demo/pop-confirm': RouteRecordInfo<'/demo/pop-confirm', '/demo/pop-confirm', Record<never, never>, Record<never, never>>,
|
||||||
|
'/dict-demo': RouteRecordInfo<'/dict-demo', '/dict-demo', Record<never, never>, Record<never, never>>,
|
||||||
'/hi/[name]': RouteRecordInfo<'/hi/[name]', '/hi/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
'/hi/[name]': RouteRecordInfo<'/hi/[name]', '/hi/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||||
'/majors': RouteRecordInfo<'/majors', '/majors', Record<never, never>, Record<never, never>>,
|
'/majors': RouteRecordInfo<'/majors', '/majors', Record<never, never>, Record<never, never>>,
|
||||||
'/privacy-policy': RouteRecordInfo<'/privacy-policy', '/privacy-policy', Record<never, never>, Record<never, never>>,
|
'/privacy-policy': RouteRecordInfo<'/privacy-policy', '/privacy-policy', Record<never, never>, Record<never, never>>,
|
||||||
|
|
@ -31,5 +32,6 @@ declare module 'vue-router/auto-routes' {
|
||||||
'/school/[schoolCode]': RouteRecordInfo<'/school/[schoolCode]', '/school/:schoolCode', { schoolCode: ParamValue<true> }, { schoolCode: ParamValue<false> }>,
|
'/school/[schoolCode]': RouteRecordInfo<'/school/[schoolCode]', '/school/:schoolCode', { schoolCode: ParamValue<true> }, { schoolCode: ParamValue<false> }>,
|
||||||
'/simulate': RouteRecordInfo<'/simulate', '/simulate', Record<never, never>, Record<never, never>>,
|
'/simulate': RouteRecordInfo<'/simulate', '/simulate', Record<never, never>, Record<never, never>>,
|
||||||
'/universities': RouteRecordInfo<'/universities', '/universities', Record<never, never>, Record<never, never>>,
|
'/universities': RouteRecordInfo<'/universities', '/universities', Record<never, never>, Record<never, never>>,
|
||||||
|
'/volunteer': RouteRecordInfo<'/volunteer', '/volunteer', Record<never, never>, Record<never, never>>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
/**
|
||||||
|
* 字典工具类 - 用于管理应用中的所有字典数据
|
||||||
|
* 包括静态字典(本地定义)和动态字典(API获取)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 字典项接口
|
||||||
|
export interface DictItem {
|
||||||
|
label: string
|
||||||
|
value: string | number
|
||||||
|
disabled?: boolean
|
||||||
|
color?: string
|
||||||
|
order?: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字典类型
|
||||||
|
export interface DictType {
|
||||||
|
[key: string]: DictItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态字典数据
|
||||||
|
const staticDicts: DictType = {
|
||||||
|
// 专业类别
|
||||||
|
professionalCategory: [
|
||||||
|
{ label: '理工类', value: 'science', color: '#108ee9' },
|
||||||
|
{ label: '文史类', value: 'liberal_arts', color: '#2db7f5' },
|
||||||
|
{ label: '艺术类', value: 'art', color: '#87d068' },
|
||||||
|
{ label: '体育类', value: 'sports', color: '#ff5500' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 学历层次
|
||||||
|
educationalLevel: [
|
||||||
|
{ label: '本科', value: 'undergraduate', color: '#108ee9' },
|
||||||
|
{ label: '专科', value: 'college', color: '#2db7f5' },
|
||||||
|
{ label: '研究生', value: 'graduate', color: '#87d068' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 省份
|
||||||
|
provinces: [
|
||||||
|
{ label: '北京市', value: 'beijing', order: 1 },
|
||||||
|
{ label: '上海市', value: 'shanghai', order: 2 },
|
||||||
|
{ label: '广东省', value: 'guangdong', order: 3 },
|
||||||
|
{ label: '江苏省', value: 'jiangsu', order: 4 },
|
||||||
|
{ label: '浙江省', value: 'zhejiang', order: 5 },
|
||||||
|
{ label: '山东省', value: 'shandong', order: 6 },
|
||||||
|
{ label: '河南省', value: 'henan', order: 7 },
|
||||||
|
{ label: '河北省', value: 'hebei', order: 8 },
|
||||||
|
{ label: '山西省', value: 'shanxi', order: 9 },
|
||||||
|
{ label: '辽宁省', value: 'liaoning', order: 10 },
|
||||||
|
{ label: '吉林省', value: 'jilin', order: 11 },
|
||||||
|
{ label: '黑龙江省', value: 'heilongjiang', order: 12 },
|
||||||
|
{ label: '安徽省', value: 'anhui', order: 13 },
|
||||||
|
{ label: '福建省', value: 'fujian', order: 14 },
|
||||||
|
{ label: '江西省', value: 'jiangxi', order: 15 },
|
||||||
|
{ label: '湖北省', value: 'hubei', order: 16 },
|
||||||
|
{ label: '湖南省', value: 'hunan', order: 17 },
|
||||||
|
{ label: '四川省', value: 'sichuan', order: 18 },
|
||||||
|
{ label: '贵州省', value: 'guizhou', order: 19 },
|
||||||
|
{ label: '云南省', value: 'yunnan', order: 20 },
|
||||||
|
{ label: '陕西省', value: 'shaanxi', order: 21 },
|
||||||
|
{ label: '甘肃省', value: 'gansu', order: 22 },
|
||||||
|
{ label: '青海省', value: 'qinghai', order: 23 },
|
||||||
|
{ label: '海南省', value: 'hainan', order: 24 },
|
||||||
|
{ label: '台湾省', value: 'taiwan', order: 25 },
|
||||||
|
{ label: '内蒙古自治区', value: 'neimenggu', order: 26 },
|
||||||
|
{ label: '广西壮族自治区', value: 'guangxi', order: 27 },
|
||||||
|
{ label: '西藏自治区', value: 'xizang', order: 28 },
|
||||||
|
{ label: '宁夏回族自治区', value: 'ningxia', order: 29 },
|
||||||
|
{ label: '新疆维吾尔自治区', value: 'xinjiang', order: 30 },
|
||||||
|
{ label: '香港特别行政区', value: 'hongkong', order: 31 },
|
||||||
|
{ label: '澳门特别行政区', value: 'aomen', order: 32 },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 科目列表
|
||||||
|
subjectList: [
|
||||||
|
{ label: '语文', value: 'chinese', color: '#108ee9' },
|
||||||
|
{ label: '数学', value: 'mathematics', color: '#2db7f5' },
|
||||||
|
{ label: '英语', value: 'english', color: '#87d068' },
|
||||||
|
{ label: '物理', value: 'physics', color: '#ff5500' },
|
||||||
|
{ label: '化学', value: 'chemistry', color: '#f5222d' },
|
||||||
|
{ label: '生物', value: 'biology', color: '#fa8c16' },
|
||||||
|
{ label: '政治', value: 'politics', color: '#faad14' },
|
||||||
|
{ label: '历史', value: 'history', color: '#a0d911' },
|
||||||
|
{ label: '地理', value: 'geography', color: '#52c41a' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 性别
|
||||||
|
gender: [
|
||||||
|
{ label: '男', value: 'male' },
|
||||||
|
{ label: '女', value: 'female' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
status: [
|
||||||
|
{ label: '启用', value: 'enabled', color: '#52c41a' },
|
||||||
|
{ label: '禁用', value: 'disabled', color: '#f5222d' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 类型
|
||||||
|
type: [
|
||||||
|
{ label: '系统', value: 'system', color: '#108ee9' },
|
||||||
|
{ label: '用户', value: 'user', color: '#2db7f5' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @returns 字典项列表
|
||||||
|
*/
|
||||||
|
export function getDictItems(dictType: string): DictItem[] {
|
||||||
|
return staticDicts[dictType] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项标签
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项标签
|
||||||
|
*/
|
||||||
|
export function getDictLabel(dictType: string, value: string | number): string {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
const item = dictItems.find(item => item.value === value)
|
||||||
|
return item ? item.label : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项值
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param label 字典标签
|
||||||
|
* @returns 字典项值
|
||||||
|
*/
|
||||||
|
export function getDictValue(dictType: string, label: string): string | number | undefined {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
const item = dictItems.find(item => item.label === label)
|
||||||
|
return item ? item.value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项颜色
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项颜色
|
||||||
|
*/
|
||||||
|
export function getDictColor(dictType: string, value: string | number): string {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
const item = dictItems.find(item => item.value === value)
|
||||||
|
return item ? item.color || '' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有字典类型
|
||||||
|
* @returns 字典类型列表
|
||||||
|
*/
|
||||||
|
export function getAllDictTypes(): string[] {
|
||||||
|
return Object.keys(staticDicts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典项对象
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @param value 字典值
|
||||||
|
* @returns 字典项对象
|
||||||
|
*/
|
||||||
|
export function getDictItem(dictType: string, value: string | number): DictItem | undefined {
|
||||||
|
const dictItems = getDictItems(dictType)
|
||||||
|
return dictItems.find(item => item.value === value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出静态字典
|
||||||
|
export { staticDicts }
|
||||||
Loading…
Reference in New Issue