diff --git a/AGENTS.md b/AGENTS.md index dec33c0..2b49c4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,143 +1,212 @@ # AGENTS.md - Swimming uni-app Project -## Build & Development Commands +## Scope +- This file applies to the entire repository rooted at `D:\Project\游泳\swimming`. +- There are currently no deeper `AGENTS.md` files in subdirectories. +- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` files exist in this repo. -### Development Servers +## Project Overview +- This is a `uni-app` + Vue 3 + TypeScript project built with Vite. +- Main app source lives under `src/`. +- The app appears to target H5, app, and multiple mini-program platforms. +- Primary UI library is `uview-plus`. +- Charting-related code exists under `src/uni_modules/qiun-data-charts` and `src/uni_modules/lime-echart`. +- Service-layer code is centralized under `src/Service/`. + +## Tooling Snapshot +- Package manager: `npm`. +- Bundler/dev server: `vite` via `@dcloudio/vite-plugin-uni`. +- Type checking: `vue-tsc --noEmit`. +- Language level: TypeScript `^4.9.4`. +- Framework: Vue `^3.4.21`. +- No ESLint config is present. +- No Prettier config is present. +- No unit or e2e test runner config is present. + +## Install ```bash -npm run dev:h5 # Run H5 development server -npm run dev:mp-weixin # Run WeChat mini-program development -npm run dev:app # Run app development -npm run dev:app-android # Run Android app development -npm run dev:app-ios # Run iOS app development -npm run dev:mp-alipay # Run Alipay mini-program -npm run dev:mp-baidu # Run Baidu mini-program -npm run dev:mp-qq # Run QQ mini-program -npm run dev:mp-toutiao # Run Toutiao mini-program +npm install ``` -### Build Commands +## Build And Dev Commands + +### Common Development ```bash -npm run build:h5 # Build for H5 -npm run build:mp-weixin # Build for WeChat mini-program -npm run build:app # Build for app -npm run build:app-android # Build for Android -npm run build:app-ios # Build for iOS +npm run dev:h5 +npm run dev:h5:ssr +npm run dev:app +npm run dev:app-android +npm run dev:app-ios +npm run dev:mp-weixin +npm run dev:mp-alipay +npm run dev:mp-baidu +npm run dev:mp-jd +npm run dev:mp-kuaishou +npm run dev:mp-lark +npm run dev:mp-qq +npm run dev:mp-toutiao +npm run dev:mp-xhs +npm run dev:quickapp-webview +npm run dev:quickapp-webview-huawei +npm run dev:quickapp-webview-union ``` -### Type Checking +### Common Build ```bash -npm run type-check # Run TypeScript type checking (vue-tsc --noEmit) +npm run build:h5 +npm run build:h5:ssr +npm run build:app +npm run build:app-android +npm run build:app-ios +npm run build:mp-weixin +npm run build:mp-alipay +npm run build:mp-baidu +npm run build:mp-jd +npm run build:mp-kuaishou +npm run build:mp-lark +npm run build:mp-qq +npm run build:mp-toutiao +npm run build:mp-xhs +npm run build:quickapp-webview +npm run build:quickapp-webview-huawei +npm run build:quickapp-webview-union ``` -### Testing -- **Note**: No test framework configured in this project yet. - -## Code Style Guidelines - -### Imports & Path Aliases -- Use `@/` alias for imports from `src/` directory (configured in `tsconfig.json`) -- Example: `import { Service } from '@/Service/Service'` -- Import Vue composition API functions from 'vue' -- Import uni-app lifecycle hooks from `@dcloudio/uni-app` - -### File Structure +### Validation +```bash +npm run type-check ``` + +## Lint And Test Status +- There is no configured lint command in `package.json`. +- There is no ESLint or Prettier configuration in the repository root. +- There is no configured test framework such as Vitest, Jest, Playwright, Cypress, Mocha, or Ava. +- There are no repository test files matching `*.spec.*` or `*.test.*` in app code. +- Because no test runner exists, there is currently no supported command for running a single test. +- For validation, prefer `npm run type-check` and, when relevant, a targeted platform build such as `npm run build:h5`. + +## Single-Test Guidance +- Single-test execution is not available in the current repo state. +- If a future test runner is added, update this file with: + - the root test command, + - the single-test command pattern, + - any platform-specific test setup, + - and the location/naming convention for test files. + +## Important Paths +- `src/main.ts` bootstraps the app and registers `uview-plus`. +- `src/App.vue` contains global app shell styles and setup. +- `src/pages.json` defines pages and tab bar configuration. +- `src/uni.scss` contains shared uni-app styling variables and theme-level styles. +- `src/Service/` contains application services and shared app behavior. +- `src/common/` contains shared domain models and utilities. +- `src/components/` contains reusable Vue components. +- `src/pages/` contains page-level Vue SFCs. +- `src/uni_modules/` contains vendored or external uni-app modules. + +## Source Layout Conventions +```text src/ -├── pages/ # Page components (with sub-packages) -├── components/ # Reusable Vue components -├── Service/ # API services and utilities -├── common/ # Common utilities and helpers -├── static/ # Static assets (images, etc.) -├── types/ # TypeScript type definitions -├── uni_modules/ # Uni-app modules -└── colorui/ # ColorUI CSS framework +├── Service/ Service classes and base config +├── common/ Shared utilities and domain models +├── components/ Reusable Vue SFC components +├── pages/ Routed uni-app pages +├── static/ Static assets +├── types/ Type declarations +├── uni_modules/ Third-party or packaged uni modules +├── colorui/ ColorUI-related assets/components +├── App.vue Root application component +├── main.ts App bootstrap +├── pages.json Route and tab-bar registration +└── uni.scss Global uni-app SCSS ``` -### Naming Conventions -- **Files**: PascalCase for components (e.g., `ImageCropper.vue`), camelCase for utilities -- **Components**: PascalCase for component names -- **Variables**: camelCase for local variables and functions -- **Constants**: UPPER_SNAKE_CASE for constants -- **Classes**: PascalCase for class names (e.g., `Service`) -- **CSS Classes**: kebab-case (e.g., `home-container`, `timer-card`) +## Imports And Module Usage +- Use the `@/` alias for imports rooted in `src/`. +- `tsconfig.json` maps `@/*` to `./src/*`. +- Prefer alias imports for app code instead of long relative paths. +- Import Vue composition helpers from `vue`. +- Import uni-app page lifecycle APIs from `@dcloudio/uni-app` when needed. +- Keep imports grouped with framework imports first, then app services/types, then local modules. +- Existing code sometimes re-exports shared utilities from `src/common/Common.ts`; preserve that pattern where it already exists. -### TypeScript -- Use `lang="ts"` in Vue SFC ` +## Service Layer Patterns +- Base configuration lives in `src/Service/BaseConfig.ts`. +- Shared service helpers live in `src/Service/Service.ts`. +- Feature services under `src/Service/swimming/` usually: + - define endpoint path constants as `private static` fields, + - expose `static` methods, + - delegate network calls to `Service.Request()`, + - and export both the service class and `Service` where existing files already do so. +- `Service.Request()` wraps request handling and normalizes API responses into `ResultData`. +- `ResultData` lives in `src/common/Domain/ResultData.ts`. - -``` +## Error Handling And User Feedback +- Use `Service.Msg()` for toast-style user feedback. +- Use `Service.Alert()` or `Service.Confirm()` for modal interactions. +- Route authenticated API requests through `Service.Request()`. +- Be aware that `Service.Request()` already handles several auth and permission codes such as `401`, `40101`, `1004`, and `40188`. +- When adding new service methods, return the request promise rather than swallowing errors silently. +- Follow existing user-facing Chinese messaging style unless the surrounding screen uses a different language. -### Formatting -- **Indentation**: Use tabs (not spaces) for indentation -- **Quotes**: Single quotes for strings in TypeScript/JavaScript -- **Semicolons**: Optional but consistent (project uses semicolons) -- **Line endings**: CRLF (Windows) -- **SCSS**: Use nested selectors, kebab-case class names +## Naming Conventions +- Vue component filenames: PascalCase, for example `ImageCropper.vue`. +- Utility and service helper filenames: usually camelCase or existing established names; match nearby files. +- Service classes: PascalCase for class names, for example `PlanService`. +- Some existing service class identifiers start lowercase, such as `studentService`; preserve existing public names unless a broader refactor is requested. +- Local variables and functions: camelCase. +- Constants: UPPER_SNAKE_CASE when they are true constants. +- CSS class names: kebab-case. +- Route paths must match entries in `src/pages.json`. -### Error Handling -- Use `Service.Msg()` for toast messages -- Use `Service.Alert()` for modal dialogs -- API requests through `Service.Request()` handle 401 (token expired) automatically -- Return `ResultData` objects from service methods -- Clean up intervals in `onUnmounted()` lifecycle hook +## Formatting Expectations +- Repository convention is tabs for indentation in source files. +- Existing project code mostly uses single quotes in application TypeScript, though some files contain double quotes; prefer the dominant local style in the file you touch. +- Semicolons are used frequently enough that new changes should stay consistent within the edited file. +- Keep line endings as CRLF on Windows-oriented files when possible. +- Use nested SCSS selectors where that is already the local pattern. +- Avoid introducing broad formatting-only diffs. -### UI Components -- Use **uview-plus** as primary UI component library -- Use **ColorUI** for CSS framework and icons -- Component prefix: `u-` for uview-plus components (e.g., ``, ``) +## Styling And UI +- Prefer `uview-plus` components where the project already uses them. +- ColorUI assets/components are also present; preserve established usage where relevant. +- Use `rpx` units for responsive uni-app layouts. +- Keep mobile-first layout assumptions. +- Match the visual language already present on the page you are editing rather than redesigning unrelated UI. -### Service Layer Pattern -- Extend `BaseConfig` for service classes -- Use static methods for utility functions -- `Service` class provides: - - API requests: `Service.Request()` - - Navigation: `Service.GoPage()`, `Service.GoPageTab()`, `Service.GoPageBack()` - - Storage: `Service.SetStorageCache()`, `Service.GetStorageCache()` - - Messages: `Service.Msg()`, `Service.Alert()` - - Loading: `Service.LoadIng()`, `Service.LoadClose()` +## Platform And Runtime Notes +- Use `uni.*` APIs for platform-specific behavior. +- Navigation typically goes through `Service.GoPage()`, `Service.GoPageTab()`, `Service.GoPageBack()`, or `Service.GoPageDelse()`. +- App state and auth token access are commonly wrapped by `Service.GetStorageCache()` and related helpers. +- Be careful when changing files inside `src/uni_modules/`; many are third-party modules and should only be edited when necessary. -### uni-app Specifics -- Use `uni.` API for platform-specific operations -- Pages registered in `src/pages.json` -- Tab bar configured in `src/pages.json` -- Use `rpx` units for responsive design -- Global styles in `src/App.vue` and `src/uni.scss` +## Agent Guidance +- Before changing code, inspect nearby files and follow their existing conventions. +- Keep edits focused and minimal; do not refactor unrelated areas opportunistically. +- Do not invent lint or test commands that are not actually configured. +- When describing validation, use real commands from `package.json`. +- If you add a test framework or lint setup later, update this file immediately. +- If you touch vendored `uni_modules`, call that out explicitly in your final summary. -### Git & Commit -- `.gitignore` includes `node_modules/`, `dist/`, `unpackage/` -- Commit messages should be descriptive (in Chinese or English) - -### Key Dependencies -- Vue 3.4.21 -- TypeScript 4.9.4 -- Vite 5.2.8 -- uni-app 3.0.0 -- uview-plus 3.3.54 -- dayjs (date manipulation) -- echarts (charts) -- vue-i18n (internationalization) - -### No Existing Rules -- No `.cursor/rules/` or `.cursorrules` found -- No `.github/copilot-instructions.md` found -- No ESLint or Prettier configuration found -- Follow existing code patterns when making changes +## Current Gaps To Remember +- No lint pipeline is configured. +- No automated test pipeline is configured. +- No single-test command exists yet. +- No Cursor or Copilot repository instruction files exist. +- Agents should rely on local code patterns plus this file when making changes. diff --git a/package-lock.json b/package-lock.json index 49b9869..87431db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "clipboard": "^2.0.11", "dayjs": "^1.11.13", "echarts": "^5.5.1", - "uview-plus": "^3.3.54", + "uview-plus": "^3.7.32", "vue": "^3.4.21", "vue-i18n": "^9.1.9" }, @@ -12682,9 +12682,9 @@ } }, "node_modules/uview-plus": { - "version": "3.7.13", - "resolved": "https://registry.npmmirror.com/uview-plus/-/uview-plus-3.7.13.tgz", - "integrity": "sha512-vHByf0kxKReYxam6BuU6wn/80giCkMaMUHEblhkf4kAjP852b86V3ctkjfGtV17MEIORFo3Vkve+HFnHNXpwNg==", + "version": "3.7.32", + "resolved": "https://registry.npmmirror.com/uview-plus/-/uview-plus-3.7.32.tgz", + "integrity": "sha512-sSUHHP0xgGkMoipRFB9XpiqTR/eaBn4WwF+rFQRcPLbr3CCey0hwH700HoqX6Uo6IsKensFIhv6Tw6Rs8XWv5w==", "engines": { "HBuilderX": "^3.1.0", "uni-app": "^4.66", diff --git a/src/Service/swimming/PlanService.ts b/src/Service/swimming/PlanService.ts index 3130e9a..5dfac15 100644 --- a/src/Service/swimming/PlanService.ts +++ b/src/Service/swimming/PlanService.ts @@ -40,7 +40,84 @@ class PlanService { return result; } - + + private static UpdateGroupStudentPath : string = '/api/Plan/UpdateGroupStudent'; + /*****修改分组学生学生接口*****/ + static UpdateGroupStudent(data:any) { + var result = Service.Request(this.UpdateGroupStudentPath, "POST", data); + return result; + } + + + private static AddStudentToGroupPath : string = '/api/Plan/AddStudentToGroup'; + /*****添加分组学生接口*****/ + static AddStudentToGroup(data:any) { + var result = Service.Request(this.AddStudentToGroupPath, "POST", data); + return result; + } + + + private static RemoveStudentFromGroupPath : string = '/api/Plan/RemoveStudentFromGroup'; + /*****从分组中删除学生接口*****/ + static RemoveStudentFromGroup(data:any) { + var result = Service.Request(this.RemoveStudentFromGroupPath, "POST", data); + return result; + } + + private static AddPlanLogPath : string = '/api/Plan/AddPlanLog'; + /*****添加记录接口*****/ + static AddPlanLog(planId:string,planType:string,timingEvents:string,subsection:string,baogan:string) { + var result = Service.Request(this.AddPlanLogPath, "POST", {planId,planType,timingEvents,subsection,baogan }); + return result; + } + + private static GetPlanListNoPagePath : string = '/api/Plan/GetPlanListNoPage'; + /*****不分页项目接口*****/ + static GetPlanListNoPage(planType:string) { + var result = Service.Request(this.GetPlanListNoPagePath, "GET", {planType}); + return result; + } + + + private static GetBaoganLogPath : string = '/api/Plan/GetBaoganLog'; + /*****包干数据接口*****/ + static GetBaoganLog(data:any) { + var result = Service.Request(this.GetBaoganLogPath, "GET", data); + return result; + } + + + private static GetJishiLogPath : string = '/api/Plan/GetJishiLog'; + /*****计时数据接口*****/ + static GetJishiLog(data:any) { + var result = Service.Request(this.GetJishiLogPath, "GET", data); + return result; + } + + + private static GetQuxianLogPath : string = '/api/Plan/GetQuxianLog'; + /*****曲线数据接口*****/ + static GetQuxianLog(data:any) { + var result = Service.Request(this.GetQuxianLogPath, "GET", data); + return result; + } + + + private static GetFenduanLogPath : string = '/api/Plan/GetFenduanLog'; + /*****分段接口*****/ + static GetFenduanLog(data:any) { + var result = Service.Request(this.GetFenduanLogPath, "GET", data); + return result; + } + + + private static GetRankDataPath : string = '/api/Plan/GetRankData'; + /*****成绩排名接口*****/ + static GetRankData(planId:string) { + var result = Service.Request(this.GetRankDataPath, "GET", {planId }); + return result; + } + } export { diff --git a/src/pages/dataAnalyze/Curve.vue b/src/pages/dataAnalyze/Curve.vue index 3704517..5f05ec3 100644 --- a/src/pages/dataAnalyze/Curve.vue +++ b/src/pages/dataAnalyze/Curve.vue @@ -8,33 +8,49 @@ - - - - 选择项目 - - - - - {{ selectedProjectName }} + + + + 选择项目 + + {{ selectProcet || '请选择项目' }} - + + + + + + + + + + 开始日期 + {{ begin || '请选择' }} + + + + + + 结束日期 + {{ end || '请选择' }} + + - - + + 选择学员(最多3人) 已选:{{ selectedStudentIds.length }}/3 - + {{ student.name.charAt(0) }} @@ -42,7 +58,7 @@ {{ student.name }} - + @@ -57,18 +73,14 @@ 选中项目 - {{ selectedProjectName }} + {{ selectProcet }} 对比学员 {{ selectedStudentIds.length }}人 - - - 训练次数 - {{ maxTrainingCount }}次 - + @@ -85,18 +97,12 @@ - - - 图例说明 - - - - {{ student.name }} - ({{ getStudentBestTime(student.id) }}s 最佳) - - - + + + + @@ -104,6 +110,7 @@ import { onShow, onLoad } from "@dcloudio/uni-app" import { Service } from '@/Service/Service' import { ref, computed, watch } from 'vue' + import { PlanService } from '@/Service/swimming/PlanService' // ==================== 常量定义 ==================== @@ -115,30 +122,17 @@ // ==================== 响应式数据 - 项目相关 ==================== - // 项目列表数据 - const projectList = ref([ - { id: '1', name: '100米自由泳' }, - { id: '2', name: '50米自由泳' }, - { id: '3', name: '200米由泳' }, - { id: '4', name: '100米蛙泳' } - ]) + // 项目选择 + let showProject = ref(false) + let selectProcet = ref('') + let selectId = ref('') - // 项目选择器选项(用于 picker 组件) - const projectOptions = ref(projectList.value) + // 日期选择 + const begin = ref('') + const end = ref('') + const showCalendar = ref(false) - // 当前选中的项目索引(用于 picker 组件的显示) - const selectedProjectIndex = ref(-1) - - // 当前选中的项目 ID - const selectedProjectId = ref('') - - // 当前选中的项目名称(用于显示) - const selectedProjectName = computed(() => { - if (selectedProjectIndex.value === -1) { - return '请选择项目' - } - return projectList.value[selectedProjectIndex.value]?.name || '请选择项目' - }) + const projectOptions = ref>([[]]) // ==================== TypeScript 接口定义 ==================== @@ -147,7 +141,7 @@ * 定义学生的基本信息 */ interface Student { - id : string + studentId : string name : string } @@ -175,7 +169,7 @@ // 已选中的学生详细信息列表((计算属性) const selectedStudents = computed(() => { return projectStudents.value.filter(student => - selectedStudentIds.value.includes(student.id) + selectedStudentIds.value.includes(student.studentId) ) }) @@ -227,129 +221,59 @@ series: [] as any[] }) - // 最大训练次数(计算属性) - const maxTrainingCount = computed(() => { - const allRecords = mockTrainingRecords[selectedProjectId.value] || [] - const studentCounts = new Map() - allRecords.forEach(record => { - studentCounts.set(record.studentId, (studentCounts.get(record.studentId) || 0) + 1) - }) - return Math.max(0, ...Array.from(studentCounts.values())) - }) - // ==================== 模拟数据 ==================== - - // 模拟的学生数据(按项目分组) - const mockProjectStudents : Record = { - '1': [ - { id: 's1', name: '张小明' }, - { id: 's2', name: '李小红' }, - { id: 's3', name: '王小明' }, - { id: 's4', name: '赵小芳' }, - { id: 's5', name: '陈小刚' } - ], - '2': [ - { id: 's6', name: '刘小华' }, - { id: 's7', name: '张小丽' }, - { id: 's8', name: '杨小龙' } - ], - '3': [ - { id: 's9', name: '黄小东' }, - { id: 's10', name: '吴小西' }, - { id: 's11', name: '周小南' }, - { id: 's12', name: '徐小北' } - ], - '4': [ - { id: 's13', name: '马小兵' }, - { id: 's14', name: '朱小红' } - ] - } - - // 模拟的训练记录数据(按项目分组) - const mockTrainingRecords : Record = { - '1': [ - { studentId: 's1', studentName: '张小明', time: 28.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's1', studentName: '张小明', time: 27.2, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's1', studentName: '张小明', time: 26.5, recordDate: '03-16', recordFullDate: '2026-03-16', round: 1 }, - { studentId: 's1', studentName: '张小明', time: 25.8, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 }, - { studentId: 's1', studentName: '张小明', time: 25.2, recordDate: '03-24', recordFullDate: '2026-03-24', round: 1 }, - { studentId: 's2', studentName: '李小红', time: 32.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's2', studentName: '李小红', time: 31.2, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's2', studentName: '李小红', time: 30.5, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 }, - { studentId: 's2', studentName: '李小红', time: 29.8, recordDate: '03-24', recordFullDate: '2026-03-24', round: 1 }, - { studentId: 's3', studentName: '王小明', time: 25.8, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's3', studentName: '王小明', time: 24.5, recordDate: '03-16', recordFullDate: '2026-03-16', round: 1 }, - { studentId: 's3', studentName: '王小明', time: 23.8, recordDate: '03-24', recordFullDate: '2026-03-24', round: 1 }, - { studentId: 's4', studentName: '赵小芳', time: 33.5, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's4', studentName: '赵小芳', time: 32.2, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 }, - { studentId: 's5', studentName: '陈小刚', time: 35.5, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 } - ], - '2': [ - { studentId: 's6', studentName: '刘小华', time: 24.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's6', studentName: '刘小华', time: 23.8, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's6', studentName: '刘小华', time: 23.2, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 }, - { studentId: 's7', studentName: '张小丽', time: 26.8, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's7', studentName: '张小丽', time: 26.2, recordDate: '03-16', recordFullDate: '2026-03-16', round: 1 }, - { studentId: 's8', studentName: '杨小龙', time: 25.5, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's8', studentName: '杨小龙', time: 24.8, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 } - ], - '3': [ - { studentId: 's9', studentName: '黄小东', time: 125.8, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's9', studentName: '黄小东', time: 124.5, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's10', studentName: '吴小西', time: 128.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's10', studentName: '吴小西', time: 127.2, recordDate: '03-16', recordFullDate: '2026-03-16', round: 1 }, - { studentId: 's10', studentName: '吴小西', time: 126.8, recordDate: '03-19', recordFullDate: '2026-03-19', round: 1 } - ], - '4': [ - { studentId: 's13', studentName: '马小兵', time: 82.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 }, - { studentId: 's13', studentName: '马小兵', time: 81.8, recordDate: '03-14', recordFullDate: '2026-03-14', round: 1 }, - { studentId: 's14', studentName: '朱小红', time: 88.5, recordDate: '03-12', recordFullDate: '2026-03-12', round: 1 } - ] - } - - // ==================== 生命周期钩子 ==================== onLoad(() => { - loadProjectData() + getProjectData() }) onShow(() => { - // TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑 + }) - // ==================== 监听器 ==================== - // 监听选中学生 ID 列表的变化 - // 使用 getter 函数返回数组副本,这样可以确保监听器触发 - watch(() => [...selectedStudentIds.value], (newIds, oldIds) => { - console.log('selectedStudentIds changed:', newIds) - console.log('oldIds:', oldIds) - updateChartData() - }) - // ==================== 业务逻辑方法 ==================== - const loadProjectData = () => { - // TODO: 调用后端 API 获取项目列表 + + const getProjectData = () => { + PlanService.GetPlanListNoPage('计时项目').then(res => { + if (res.code == 0) { + projectOptions.value[0] = res.data.list + } else { + Service.Msg(res.msg) + } + }) } - const handleProjectChange = (e : any) => { - const index = e.detail.value - selectedProjectIndex.value = index - const selectedProject = projectList.value[index] - if (selectedProject) { - selectedProjectId.value = selectedProject.id - loadProjectStudents(selectedProject.id) - } + const selectProcetFunc = (e : any) => { + selectProcet.value = e.value[0].name + selectId.value = e.value[0].planId selectedStudentIds.value = [] + loadProjectStudents(selectId.value) + } + + const openCalendar = () => { + showCalendar.value = true + } + + const calendarConfirm = (e : any) => { + begin.value = e[0] + end.value = e[e.length - 1] + showCalendar.value = false + } + + const calendarClose = () => { + showCalendar.value = false } const loadProjectStudents = (projectId : string) => { - if (mockProjectStudents[projectId]) { - projectStudents.value = mockProjectStudents[projectId] - } else { - projectStudents.value = [] - } + PlanService.GetPlanInfo(projectId).then(res => { + if (res.code == 0) { + projectStudents.value = res.data.plan.users + } else { + Service.Msg(res.msg) + } + }) } const toggleStudent = (studentId : string) => { @@ -363,57 +287,44 @@ } selectedStudentIds.value.push(studentId) } - } - - const getStudentBestTime = (studentId : string) : number => { - const records = mockTrainingRecords[selectedProjectId.value] || [] - const studentRecords = records.filter(r => r.studentId === studentId) - if (studentRecords.length === 0) return 0 - return Math.min(...studentRecords.map(r => r.time)) - } - - const updateChartData = () => { - if (selectedStudentIds.value.length === 0 || !selectedProjectId.value) { - lineChartData.value.series = [] - lineChartData.value.categories = [] - return + let studentIdList = selectedStudentIds.value.map((item) => { + return item + }) + let data = { + planId: selectId.value, + studentId: JSON.stringify(studentIdList), + sTime: begin.value, + eTime: end.value + } + interface DataItem { + name : string + data : any[] } - const projectRecords = mockTrainingRecords[selectedProjectId.value] || [] - const allDates = new Set() - projectRecords.forEach(record => { - allDates.add(record.recordFullDate) + + const xData = ref( + Array.from({ length: studentIdList.length }, () => ({ + name: '', + data: [] + })) + ) + PlanService.GetQuxianLog(data).then(res => { + if (res.code == 0) { + + res.data.list.map((item : any) => { + lineChartData.value.categories.push(item.dayTime) + item.data.map((content : any, index : any) => { + xData.value[index].name = content.studentName + xData.value[index].data.push(content.time) + }) + }) + console.log(xData.value); + lineChartData.value.series = xData.value + } else { + Service.Msg(res.msg) + } }) - const sortedDates = Array.from(allDates).sort() - - lineChartData.value.categories = sortedDates.map(date => { - const parts = date.split('-') - return `${parts[1]}-${parts[2]}` - }) - - const seriesData : any[] = [] - - selectedStudentIds.value.forEach((studentId, index) => { - const studentRecords = projectRecords.filter(r => r.studentId === studentId) - studentRecords.sort((a, b) => a.recordFullDate.localeCompare(b.recordFullDate)) - - const timeMap = new Map() - studentRecords.forEach(record => { - timeMap.set(record.recordFullDate, record.time) - }) - - const timeData = sortedDates.map(date => timeMap.get(date) || null) - const studentName = projectStudents.value.find(s => s.id === studentId)?.name || '未知学员' - - seriesData.push({ - name: studentName, - data: timeData, - color: chartColors[index] - }) - }) - console.log(); - lineChartData.value.series = seriesData } @@ -466,12 +377,55 @@ } } - .project-select-section { + .date-filter { + display: flex; + gap: 24rpx; + + .date-picker { + flex: 1; + display: flex; + align-items: center; + gap: 16rpx; + background-color: #fff; + border-radius: 16rpx; + padding: 20rpx; + + .date-text { + .label { + display: block; + font-size: 26rpx; + color: #999; + } + + .value { + display: block; + font-size: 30rpx; + color: #333; + margin-top: 4rpx; + font-weight: 600; + } + } + } + } + + .select-section { background-color: #fff; margin: 0 20rpx 20rpx; - border-radius: 16rpx; + border-radius: 20rpx; padding: 28rpx; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + position: relative; + overflow: hidden; + + .select-label { + margin-bottom: 16rpx; + + .label-text { + font-size: 26rpx; + font-weight: 600; + color: #666; + } + } .picker-wrapper { display: flex; @@ -485,7 +439,8 @@ &:active { background-color: #f0f0f0; - border-color: #52c41a; + border-color: #faad14; + transform: scale(0.98); } .picker-text { @@ -573,126 +528,126 @@ - .chart-section { - margin: 0 20rpx; + .chart-section { + margin: 0 20rpx; - .stats-card { - background-color: #fff; - border-radius: 16rpx; - padding: 24rpx; - margin-bottom: 20rpx; - display: flex; - align-items: center; - justify-content: space-around; - box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + .stats-card { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 20rpx; + display: flex; + align-items: center; + justify-content: space-around; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); - .stat-item { - text-align: center; + .stat-item { + text-align: center; - .stat-label { - font-size: 24rpx; - color: #999; - display: block; - margin-bottom: 8rpx; - } - - .stat-value { - font-size: 32rpx; - font-weight: 700; - color: #52c41a; - } + .stat-label { + font-size: 24rpx; + color: #999; + display: block; + margin-bottom: 8rpx; } - .stat-divider { - width: 1rpx; - height: 50rpx; - background-color: #eee; + .stat-value { + font-size: 32rpx; + font-weight: 700; + color: #52c41a; } } - .chart-card { - background-color: #fff; - border-radius: 16rpx; - padding: 24rpx; - margin-bottom: 20rpx; - box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); - - .chart-header { - margin-bottom: 20rpx; - padding-left: 12rpx; - border-left: 6rpx solid #52c41a; - - .chart-title { - font-size: 30rpx; - font-weight: 600; - color: #333; - display: block; - margin-bottom: 6rpx; - } - - .chart-desc { - font-size: 24rpx; - color: #999; - } - } - - .chart-scroll { - width: 100%; - height: 500rpx; - background-color: #fafafa; - border-radius: 12rpx; - overflow: hidden; - white-space: nowrap; - - .chart-box { - height: 100%; - display: inline-block; - } - } + .stat-divider { + width: 1rpx; + height: 50rpx; + background-color: #eee; } + } - .legend-section { - background-color: #fff; - border-radius: 16rpx; - padding: 24rpx; - box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + .chart-card { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + margin-bottom: 20rpx; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); - .legend-title { - font-size: 28rpx; + .chart-header { + margin-bottom: 20rpx; + padding-left: 12rpx; + border-left: 6rpx solid #52c41a; + + .chart-title { + font-size: 30rpx; font-weight: 600; color: #333; - margin-bottom: 20rpx; + display: block; + margin-bottom: 6rpx; } - .legend-list { + .chart-desc { + font-size: 24rpx; + color: #999; + } + } + + .chart-scroll { + width: 100%; + height: 500rpx; + background-color: #fafafa; + border-radius: 12rpx; + overflow: hidden; + white-space: nowrap; + + .chart-box { + height: 100%; + display: inline-block; + } + } + } + + .legend-section { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); + + .legend-title { + font-size: 28rpx; + font-weight: 600; + color: #333; + margin-bottom: 20rpx; + } + + .legend-list { + display: flex; + flex-direction: column; + gap: 16rpx; + + .legend-item { display: flex; - flex-direction: column; - gap: 16rpx; + align-items: center; + gap: 12rpx; - .legend-item { - display: flex; - align-items: center; - gap: 12rpx; + .legend-color { + width: 24rpx; + height: 24rpx; + border-radius: 50%; + flex-shrink: 0; + } - .legend-color { - width: 24rpx; - height: 24rpx; - border-radius: 50%; - flex-shrink: 0; - } + .legend-name { + font-size: 26rpx; + color: #333; + font-weight: 600; + } - .legend-name { - font-size: 26rpx; - color: #333; - font-weight: 600; - } - - .legend-desc { - font-size: 24rpx; - color: #999; - } + .legend-desc { + font-size: 24rpx; + color: #999; } } } } + } \ No newline at end of file diff --git a/src/pages/dataAnalyze/baoganAnalyze.vue b/src/pages/dataAnalyze/baoganAnalyze.vue index c3e7f63..c5df7b2 100644 --- a/src/pages/dataAnalyze/baoganAnalyze.vue +++ b/src/pages/dataAnalyze/baoganAnalyze.vue @@ -11,31 +11,41 @@ - 选择项目 - - - - {{ selectedProjectName }} + 选择项目 + + {{ selectProcet || '请选择项目' }} - + + - - - - + + + + + + 开始日期 + {{ begin || '请选择' }} + + + + + + 结束日期 + {{ end || '请选择' }} + + - + 选择学生 - {{ selectedStudentDisplay }} + {{ selectedStudentDisplay }} @@ -51,7 +61,7 @@ - @@ -61,40 +71,29 @@ - + {{ student.name.charAt(0) }} {{ student.name }} - - - {{ student.gender }} - - {{ student.age }}岁 - + - - - - + + - + + + @@ -102,6 +101,7 @@ import { onShow, onLoad } from "@dcloudio/uni-app" import { Service } from '@/Service/Service' import { ref, computed } from 'vue' + import { PlanService } from '@/Service/swimming/PlanService' interface Project { id : string @@ -134,65 +134,62 @@ const currentMonth = ref(new Date().getMonth() + 1) const selectedDate = ref('') + // 项目选择 + let showProject = ref(false) + let selectProcet = ref('') + let selectId = ref('') + + // 日期选择 + const begin = ref('') + const end = ref('') + const showCalendar = ref(false) + const calendarType = ref<'begin' | 'end'>('begin') + + // 分页相关 + let page = ref(1) + let status = ref('loadmore') + + let pageTotal = ref(10) + const columns = ref([ { - label: '日期', - prop: 'date', - width: '100px' + label: '训练日期', + name: 'addTime', }, { label: '姓名', - prop: 'name', - width: '100px' + name: 'studentName', }, { label: '项目名称', - prop: 'projectName', - width: '120px' + name: 'name', }, { - label: '包干计划', - prop: 'plan', - width: '100px' + label: '项目类型', + name: 'planType', }, { label: '完成比例', - prop: 'completion', - width: '100px' + name: 'completion', }, { label: '详细数据', - prop: 'detail', - width: '120px' + name: 'detail', } ]) const tableData = ref([]) - const projectList = ref([ - { id: '1', name: '100米自由泳' }, - { id: '2', name: '200米自由泳' }, - { id: '3', name: '400米自由泳' }, - { id: '4', name: '100米蛙泳' } - ]) - const projectOptions = ref(projectList.value) + + const projectOptions = ref>([[]]) const selectedProjectIndex = ref(-1) const selectedProjectId = ref('') - const selectedProjectName = computed(() => { - if (selectedProjectIndex.value === -1) { - return '请选择项目' - } - return projectList.value[selectedProjectIndex.value]?.name || '请选择项目' - }) - const selectedDates = ref([ - { date: '2026-03-28', info: '训练' }, - { date: '2026-03-29', info: '训练' }, - { date: '2026-03-30', info: '训练' } - ]) + + const studentList = ref([]) @@ -215,69 +212,12 @@ return Math.round(total / tableData.value.length) }) - const mockData : Record> = { - '1': { - '2026-03-28': [ - { name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' }, - { name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' }, - { name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' } - ], - '2026-03-29': [ - { name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' }, - { name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' }, - { name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' } - ] - }, - '2': { - '2026-03-28': [ - { name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' }, - { name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 92, detail: '1380/1500' }, - { name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 85, detail: '1275/1500' } - ], - '2026-03-29': [ - { name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 90, detail: '1350/1500' }, - { name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' } - ] - }, - '3': { - '2026-03-28': [ - { name: '张小明', projectName: '400米自由泳', plan: '1200米', completion: 85, detail: '1020/1200' }, - { name: '李小红', projectName: '400米自由泳', plan: '1200米', completion: 90, detail: '1080/1200' } - ] - }, - '4': { - '2026-03-28': [ - { name: '赵小芳', projectName: '100米蛙泳', plan: '1800米', completion: 88, detail: '1584/1800' }, - { name: '陈小' + '刚', projectName: '100米蛙泳', plan: '1800米', completion: 95, detail: '1710/1800' } - ] - } - } - const mockStudents : Record = { - '1': [ - { id: 's1', name: '张小明', gender: '男', age: 12 }, - { id: 's2', name: '李小红', gender: '女', age: 11 }, - { id: 's3', name: '王小明', gender: '男', age: 13 } - - ], - '2': [ - { id: 's1', name: '张小明', gender: '男', age: 12 }, - { id: 's2', name: '李小红', gender: '女', age: 11 }, - { id: 's3', name: '王小明', gender: '男', age: 13 } - - ], - '3': [ - { id: 's1', name: '张小明', gender: '男', age: 12 }, - { id: 's2', name: '李小红', gender: '女', age: 11 } - ], - '4': [ - { id: 's4', name: '赵小芳', gender: '女', age: 10 }, - { id: 's5', name: '陈小' + '刚', gender: '男', age: 14 } - ] - } + onLoad(() => { loadData() + getProjectData() }) onShow(() => { @@ -288,50 +228,53 @@ } - const handleCellClick = (event : any) => { - console.log('单元格点击事件:', event) + + const getProjectData = () => { + PlanService.GetPlanListNoPage('计时项目').then(res => { + if (res.code == 0) { + projectOptions.value[0] = res.data.list + } else { + Service.Msg(res.msg) + } + }) } - const handleProjectChange = (e : any) => { - const index = e.detail.value - selectedProjectIndex.value = index - const selectedProject = projectList.value[index] - - if (selectedProject) { - selectedProjectId.value = selectedProject.id - selectedDate.value = '' - selectedStudentIndexes.value = [] - selectedStudentIds.value = [] - tableData.value = [] - loadProjectStudents(selectedProject.id) - } + const selectProcetFunc = (e : any) => { + selectProcet.value = e.value[0].name + selectId.value = e.value[0].planId + getProjectDetail() } - const handleMonthSwitch = (e : any) => { - currentYear.value = e.year - currentMonth.value = e.month - loadData() - selectedDate.value = '' - selectedStudentIndexes.value = [] - selectedStudentIds.value = [] - tableData.value = [] + + const getProjectDetail = () => { + PlanService.GetPlanInfo(selectId.value).then(res => { + if (res.code == 0) { + studentList.value = res.data.plan.users + } else { + Service.Msg(res.msg) + } + }) } - const handleDateChange = (e : any) => { - console.log(e); - const date = e.fulldate - selectedDate.value = date - selectedStudentIndexes.value = [] - selectedStudentIds.value = [] - tableData.value = [] + + + + const openCalendar = () => { + showCalendar.value = true } - const loadProjectStudents = (projectId : string) => { - const students = mockStudents[projectId] || [] - studentList.value = students - console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`) + const calendarConfirm = (e : any) => { + begin.value = e[0] + end.value = e[e.length - 1] + showCalendar.value = false } + const calendarClose = () => { + showCalendar.value = false + } + + + const selectAllStudents = () => { selectedStudentIndexes.value = studentList.value.map((_, index) => index) } @@ -350,36 +293,50 @@ } const confirmStudentSelection = () => { - selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].id) + selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].studentId) showStudentPicker.value = false - filterAndLoadData() + + getRecord() } + const getRecord = () => { + page.value = 1 + getRecordList() + } + + const getRecordList = () => { + let studentIdList = selectedStudentIndexes.value.map(index => { + return studentList.value[index].studentId + }) + + let data = { + planId: selectId.value, + studentId: JSON.stringify(studentIdList), + sTime: begin.value, + eTime: end.value, + page: page.value + } + + PlanService.GetBaoganLog(data).then(res => { + if (res.code == 0) { + pageTotal.value = res.data.pageTotal + tableData.value = res.data.list + } else { + Service.Msg(res.msg) + } + }) + } + + + const pageChange = (e) => { + page.value = e + getRecordList() + } + + const closeStudentPicker = () => { showStudentPicker.value = false } - - const filterAndLoadData = () => { - if (!selectedDate.value || selectedStudentIndexes.value.length === 0) { - tableData.value = [] - return - } - - const projectData = mockData[selectedProjectId.value] - if (!projectData) { - tableData.value = [] - return - } - - const dateData = projectData[selectedDate.value] - if (!dateData) { - tableData.value = [] - return - } - - const selectedStudentNames = selectedStudentIndexes.value.map(index => studentList.value[index].name) - tableData.value = dateData.filter(item => selectedStudentNames.includes(item.name)) - } \ No newline at end of file diff --git a/src/pages/dataAnalyze/timingAnalze.vue b/src/pages/dataAnalyze/timingAnalze.vue index 27da24b..b670af2 100644 --- a/src/pages/dataAnalyze/timingAnalze.vue +++ b/src/pages/dataAnalyze/timingAnalze.vue @@ -1,5 +1,5 @@ @@ -66,250 +47,143 @@ import { onShow, onLoad } from "@dcloudio/uni-app" import { Service } from '@/Service/Service' import { ref, computed } from 'vue' + import { PlanService } from '@/Service/swimming/PlanService' - // ==================== 响应式数据定义 ==================== + interface Project { + id : string + name : string + } + + interface Student { + id : string + name : string + gender : string + age : number + } + + interface TableDataItem { + date : string + name : string + projectName : string + plan : string + completion : number + detail : string + } + + interface StudentRecord { + student : Student + date : string + segments : TableDataItem[] + } - // 当前年份,用于月份切换时记录当前年份 const currentYear = ref(new Date().getFullYear()) - - // 当前月份(1-12),用于月份切换时记录当前月份 const currentMonth = ref(new Date().getMonth() + 1) - - // 当前选中的日期,格式为 'YYYY-MM-DD' - // 空字符串表示未选择日期 const selectedDate = ref('') - // ==================== 表格配置 ==================== + // 项目选择 + let showProject = ref(false) + let selectProcet = ref('') + let selectId = ref('') + + // 日期选择 + const begin = ref('') + const end = ref('') + const showCalendar = ref(false) + const calendarType = ref<'begin' | 'end'>('begin') + + // 分页相关 + let page = ref(1) + let status = ref('loadmore') + + let pageTotal = ref(10) - // 表格列定义 - // label: 列标题 - // prop: 数据项中对应的字段名 - // width: 列宽度 const columns = ref([ { label: '姓名', - prop: 'name', - width: '100px' + name: 'studentName', }, { label: '项目名称', - prop: 'projectName', - width: '150px' + name: 'planName', }, { - label: '最大速度', - prop: 'maxSpeed', - width: '120px' + label: '最快速度', + name: 'quicklyTime', }, { - label: '计时数据', - prop: 'timingData', - width: '150px' + label: '详细数据', + name: 'time', + width:'200' } ]) - // ==================== TypeScript 接口定义 ==================== - - // 表格数据项接口定义 - // 描述一条计时记录的数据结构 - interface TableDataItem { - name: string // 学员姓名 - projectName: string // 项目名称(如:100米自由泳) - maxSpeed: number // 最大速度,单位:m/s - timingData: string // 计时数据(如:1.23, 1.25, 1.24) - } - - // ==================== 响应式数据 - 表格数据 ==================== - - // 表格数据列表 - // 存储当前选中日期的所有计时记录 - // 初始状态为空数组,等待用户选择日期后加载数据 const tableData = ref([]) - // ==================== 响应式数据 - 日历标记 ==================== - - // 日历打点数据 - // 用于在日历上标记有训练记录的日期 - // date: 日期字符串,格式为 'YYYY-MM-DD' - // info: 显示在日期上的标记信息 - const selectedDates = ref([ - { date: '2026-03-12', info: '训练' }, - { date: '2026-03-14', info: '训练' }, - { date: '2026-03-16', info: '训练' }, - { date: '2026-03-19', info: '训练' }, - { date: '2026-03-24', info: '训练' } - ]) - - // ==================== 计算属性 ==================== - - // 平均速度计算属性 - // 计算当前选中日期所有学员的平均最大速度 - // 如果没有数据则返回 0 - const averageSpeed = computed(() => { - // 如果没有数据,返回 0 - if (tableData.value.length === 0) return 0 - - // 计算所有学员最大速度的总和 - const total = tableData.value.reduce((sum, item) => sum + item.maxSpeed, 0) - - // 计算平均值,保留两位小数 - return (total / tableData.value.length).toFixed(2) - }) - - // ======================= 模拟数据 ==================== - - // 模拟的训练数据 - // 键为日期字符串,值为该日期的计时记录列表 - // 在实际项目中,这里应该从后端 API 获取数据 - const mockData: Record = { - '2026-03-12': [ - { name: '张小明', projectName: '50米自由泳', maxSpeed: 2.15, timingData: '25.35s, 25.42s, 25.28s' }, - { name: '李小红', projectName: '50米自由泳', maxSpeed: 1.98, timingData: '27.15s, 27.32s, 27.08s' }, - { name: '王小明', projectName: '50米自由泳', maxSpeed: 2.22, timingData: '24.68s, 24.75s, 24.62s' } - ], - '2026-03-14': [ - { name: '张小明', projectName: '50米自由泳', maxSpeed: 2.18, timingData: '25.12s, 25.18s, 25.08s' }, - { name: '李小红', projectName: '50米自由泳', maxSpeed: 2.02, timingData: '26.85s, 26.92s, 26.78s' }, - { name: '赵小芳', projectName: '50米自由泳', maxSpeed: 1.92, timingData: '27.95s, 28.12s, 27.88s' }, - { name: '王小明', projectName: '50米自由泳', maxSpeed: 2.25, timingData: '24.52s, 24.58s, 24.48s' } - ], - '2026-03-16': [ - { name: '张小明', projectName: '50米自由泳', maxSpeed: 2.12, timingData: '25.55s, 25.62s, 25.48s' }, - { name: '李小红', projectName: '50米自由泳', maxSpeed: 2.05, timingData: '26.72s, 26.85s, 26.68s' } - ], - '2026-03-19': [ - { name: '张小明', projectName: '50米自由泳', maxSpeed: 2.20, timingData: '24.98s, 25.05s, 24.92s' }, - { name: '李小红', projectName: '50米自由泳', maxSpeed: 2.08, timingData: '26.45s, 26.52s, 26.38s' }, - { name: '赵小芳', projectName: '50米自由泳', maxSpeed: 1.95, timingData: '27.75s, 27.88s, 27.65s' }, - { name: '王小明', projectName: '50米自由泳', maxSpeed: 2.28, timingData: '24.35s, 24.42s, 24.28s' }, - { name: '陈小刚', projectName: '50米自由泳', maxSpeed: 1.88, timingData: '28.25s, 28.38s, 28.15s' } - ], - '2026-03-24': [ - { name: '张小明', projectName: '50米自由泳', maxSpeed: 2.15, timingData: '25.30s, 25.38s, 25.25s' }, - { name: '李小红', projectName: '50米自由泳', maxSpeed: 2.10, timingData: '26.25s, 26.32s, 26.18s' }, - { name: '赵小芳', projectName: '50米自由泳', maxSpeed: 1.98, timingData: '27.65s, 27.78s, 27.55s' } - ] - } - - // ==================== 生命周期钩子 ==================== - - // 页面加载时触发 - // 在页面初始化时执行,只执行一次 onLoad(() => { - // 加载当前月份的数据 - loadData() + }) - // 页面显示时触发 - // 每次页面从后台切换到前台时执行 - // 可用于刷新页面显示的数据 onShow(() => { - // TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑 - // 例如:调用接口获取最新数据 + }) - // ==================== 业务逻辑方法 ==================== - - /** - * 加载数据 - * 调用后端 API 获取数据 - * 当前为模拟数据,实际开发中应替换为真实 API 调用 - */ - const loadData = () => { - // TODO: 调用后端 API 获取数据 - // 示例代码: - // Service.Request('/api/timing/data', 'GET', { - // year: currentYear.value, - // month: currentMonth.value - // }).then(res => { - // // 处理返回的数据 - // }) + const openCalendar = () => { + showCalendar.value = true } - /** - * 处理表格单元格点击事件 - * @param event 点击事件对象,包含行索引、列索引、单元格数据等信息 - */ - const handleCellClick = (event: any) => { - // 在控制台输出点击事件信息,用于调试 - console.log('表格单元格点击事件:', event) - - // TODO: 根据业务需求处理单元格点击 - // 例如:点击某行可以查看详细信息,点击某列可以进行排序等 + const calendarConfirm = (e : any) => { + begin.value = e[0] + end.value = e[e.length - 1] + showCalendar.value = false + getRecord() } - /** - * 处理日历月份切换事件 - * 当用户切换日历的月份时触发 - * @param e 切换事件对象,包含 year 和 month 属性 - */ - const handleMonthSwitch = (e: any) => { - // 更新当前年份 - currentYear.value = e.year - - // 更新当前月份 - currentMonth.value = e.month - - // 重新加载数据 - // 获取切换后月份的训练记录和日历标记 - loadData() - - // 清空选中的日期 - // 因为切换了月份,之前选择的日期可能不再显示 - selectedDate.value = '' - - // 清空表格数据 - tableData.value = [] + const calendarClose = () => { + showCalendar.value = false } - /** - * 处理日历日期选择事件 - * 当用户点击日历上的某个日期时触发 - * @param e 选择事件对象,fulldate 属性包含完整的日期字符串 - */ - const handleDateChange = (e: any) => { - // 获取选择的完整日期,格式为 'YYYY-MM-DD' - const date = e.fulldate - // 更新选中的日期 - selectedDate.value = date - // 根据选择的日期加载对应的训练数据 - // 检查模拟数据中是否存在该日期的数据 - if (mockData[date]) { - // 如果存在,加载数据 - tableData.value = mockData[date] - } else { - // 如果不存在,清空数据(表格将显示空状态) - tableData.value = [] + + const getRecord = () => { + page.value = 1 + getRecordList() + } + + const getRecordList = () => { + let data = { + sTime: begin.value, + eTime: end.value } + PlanService.GetJishiLog(data).then(res => { + if (res.code == 0) { + pageTotal.value = res.data.pageTotal + tableData.value = res.data.list + } else { + Service.Msg(res.msg) + } + }) + } - // TODO: 实际项目中应该调用 API 获取该日期的计时数据 - // 示例代码: - // Service.Request('/api/timing/detail', 'GET', { - // date: date - // }).then(res => { - // // 处理返回的数据并更新表格 - // tableData.value = res.data - // }) + + const pageChange = (e) => { + page.value = e + getRecordList() } + + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @keyframes slideUp { + from { + transform: translateY(100%); + } + + to { + transform: translateY(0); + } + } + \ No newline at end of file diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue index 4175b5e..8ac07fb 100644 --- a/src/pages/index/index.vue +++ b/src/pages/index/index.vue @@ -97,7 +97,7 @@ 项目列表 - + @@ -110,7 +110,7 @@ {{ project.users.length }}人 - + 暂无项目 @@ -125,18 +125,18 @@ @@ -652,6 +657,7 @@ display: flex; flex-direction: column; gap: 16rpx; + max-height: 240rpx; } .project-item { @@ -660,7 +666,7 @@ gap: 20rpx; padding: 28rpx 24rpx; background: linear-gradient(135deg, #fff 0%, #fafbfc 100%); - border-radius: 20rpx; + // border-radius: 20rpx; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06), 0 0 0 1rpx rgba(0, 0, 0, 0.04) inset; position: relative; diff --git a/src/pages/userFunc/project.vue b/src/pages/userFunc/project.vue index 841eecc..d9cd77a 100644 --- a/src/pages/userFunc/project.vue +++ b/src/pages/userFunc/project.vue @@ -9,7 +9,7 @@ - + 全局计时 @@ -26,17 +26,15 @@ {{ group.name }} - - - + + + --> @@ -50,17 +48,15 @@ - - + - - {{ timer.studentName || '计时器 ' + (index + 1) }} + {{ timer.studentName }} @@ -78,29 +74,25 @@ {{ timer.lapCount }}/{{ timer.totalLapCount || 4 }}圈 - - + - - - 点击添加学生到本组 @@ -116,24 +108,21 @@ - + - + - - - + + + @@ -114,8 +146,8 @@ // 计划ID const planId = ref('') // 项目名称 - let planName=ref('') - + let planName = ref('') + // 选手列表 const athletes = ref>([]) @@ -123,29 +155,32 @@ const groupSize = ref('0') + + // 计时器状态 const isRunning = ref(false) const currentTime = ref(0) let timerInterval : number | null = null let startTime : number = 0 - + // 当前要记录的选手索引 const currentAthleteIndex = ref(0) - + // 秒表模式 const stopwatchMode = ref<'interval' | 'together'>('interval') // 间隔时间(秒) const intervalTime = ref('0') - + let record=ref>([]) + let showRecord=ref(false) onLoad((data : any) => { planId.value = data.id - + }) - - onShow(()=>{ + + onShow(() => { getPlanInfo() }) - + // 获取计划详情 const getPlanInfo = () => { PlanService.GetPlanInfo(planId.value).then(res => { @@ -153,9 +188,9 @@ planName.value = res.data.plan.name // 将计划数据转换为选手数据 athletes.value = res.data.plan.users - groupSize.value=res.data.plan.groupInt - stopwatchMode.value=res.data.plan.departType=='间隔出发'?'interval':'together' - intervalTime.value=res.data.plan.interval + groupSize.value = res.data.plan.groupInt + stopwatchMode.value = res.data.plan.departType == '间隔出发' ? 'interval' : 'together' + intervalTime.value = res.data.plan.interval } else { Service.Msg(res.msg) } @@ -193,7 +228,7 @@ const index = athletes.value.findIndex(a => a.studentId === athlete.studentId) const interval = parseFloat(intervalTime.value) || 10 const size = parseInt(groupSize.value) || 2 - + // 根据分组计算偏移:第n组偏移 (n-1) * interval const groupNumber = Math.floor(index / size) return groupNumber * interval @@ -349,6 +384,52 @@ }) }) } + // 处理数据 + const formatMinuteTime = (seconds : number) : string => { + const mins = Math.floor(seconds / 60) + const secs = Math.floor(seconds % 60) + const ms = Math.floor((seconds % 1) * 1000) + return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}` + } + + // 提交数据 + const submitData = () => { + stopTimer() + const hasData = athletes.value.some(a => a.time > 0) + if (!hasData) { + Service.Msg('暂无数据可提交') + return + } + let data = [{ + planName: "", + studentId: "", + studentName: "", + time: "" + }] + data = [] + athletes.value.map((item : any) => { + data.push({ + planName: planName.value, + studentId: item.studentId, + studentName: item.name, + time: formatMinuteTime(item.time) + }) + }) + + PlanService.AddPlanLog(planId.value,'计时项目',JSON.stringify(data),'','','').then(res=>{ + if(res.code==0){ + Service.Msg('提交成功!') + + if(res.data.record.length>0){ + record.value=res.data.record + showRecord.value=true + } + }else{ + Service.Msg(res.msg) + } + }) + + } // 页面卸载时清理计时器 @@ -403,7 +484,7 @@ /* 选手列表区域 */ .athletes-section { - margin-top: 20rpx; + margin-top: 80rpx; margin-bottom: 120rpx; .section-header { @@ -586,6 +667,34 @@ } } + /* 右下角悬浮提交按钮 */ + .fab-submit { + position: fixed; + right: 30rpx; + bottom: 180rpx; + width: 120rpx; + height: 120rpx; + border-radius: 50%; + background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%); + display: flex; + align-items: center; + justify-content: center; + box-shadow: + 0 8rpx 24rpx rgba(250, 140, 22, 0.5), + 0 16rpx 40rpx rgba(0, 0, 0, 0.2); + z-index: 200; + + &:active { + transform: scale(0.92); + } + + .fab-text { + font-size: 28rpx; + color: #ffffff; + font-weight: 600; + } + } + /* 底部控制按钮 */ .control-buttons { display: flex; @@ -597,7 +706,7 @@ right: 0; background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%); padding: 32rpx 40rpx; - padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); + // padding-bottom: calc(32rpx + env(safe-area-inset-bottom)); border-top: 1rpx solid rgba(0, 0, 0, 0.06); box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08); @@ -611,7 +720,8 @@ .start-btn, .pause-btn, .reset-all-btn, - .record-btn { + .record-btn, + .submit-btn { flex: 1; height: 100rpx; border-radius: 20rpx; @@ -719,4 +829,84 @@ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); } } + + /* 记录提示弹窗 */ + .record-notice-modal { + width: 560rpx; + padding: 30rpx 40rpx 48rpx; + background-color: #ffffff; + border-radius: 20rpx; + text-align: center; + + .notice-header { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 40rpx; + + .notice-title { + font-size: 36rpx; + font-weight: 600; + color: #333333; + margin-top: 16rpx; + } + } + + .notice-table { + width: 100%; + + .table-header { + display: flex; + background-color: #f5f5f5; + border-radius: 12rpx; + padding: 20rpx 0; + margin-bottom: 16rpx; + + .header-cell { + font-size: 26rpx; + font-weight: 600; + color: #666666; + text-align: center; + } + } + + .table-body { + max-height: 360rpx; + + .table-row { + display: flex; + padding: 20rpx 0; + border-bottom: 1rpx solid #f0f0f0; + + &:last-child { + border-bottom: none; + } + + .row-cell { + font-size: 28rpx; + color: #333333; + text-align: center; + } + + .time-cell { + color: #ff0000; + font-family: 'DIN Alternate', monospace; + font-weight: 600; + } + } + } + + .index-cell { + width: 80rpx; + } + + .name-cell { + flex: 1; + } + + .time-cell { + width: 160rpx; + } + } + } \ No newline at end of file diff --git a/src/uni_modules/next-table/changelog.md b/src/uni_modules/next-table/changelog.md new file mode 100644 index 0000000..020016c --- /dev/null +++ b/src/uni_modules/next-table/changelog.md @@ -0,0 +1,48 @@ +## 1.2.3(2024-10-17) +更新demo +## 1.2.2(2024-09-26) +更新说明 +## 1.2.1(2024-09-25) +更新说明文档 +## 1.2.0(2024-09-14) +更新说明 +## 1.1.9(2024-09-12) +app demo安装包 +## 1.1.8(2024-08-19) +更新demo +## 1.1.7(2024-08-19) +更新说明 +## 1.1.6(2024-07-31) +增加超集功能演示demo +## 1.1.5(2024-07-25) +修复width配置bug +## 1.1.4(2024-04-08) +增加禁用功能 +## 1.1.3(2024-01-31) +修复align配置不生效问题 +## 1.1.2(2024-01-26) +更新vue2使用说明 +## 1.1.1(2024-01-25) +修复vue2版本使用兼容问题 +## 1.1.0(2023-12-21) +修复vue2编译报错问题 +## 1.0.9(2023-11-17) +修复图片fixed时无法固定的bug +## 1.0.8(2023-11-17) +修复主题样式bug +## 1.0.7(2023-09-21) +修复说明 +## 1.0.6(2023-07-20) +更新使用要求说明 +## 1.0.5(2023-07-20) +更新primaryColor的支持说明 +## 1.0.4(2023-07-14) +更新package.json文件 +## 1.0.3(2023-07-14) +增加表格row激活主题颜色配置 +## 1.0.2(2023-07-13) +增加动态颜色环境变量 +## 1.0.1(2023-07-13) +增加主体颜色配置 +## 1.0.0(2023-07-13) +初始化next-table diff --git a/src/uni_modules/next-table/components/next-table/components/table-checkbox.vue b/src/uni_modules/next-table/components/next-table/components/table-checkbox.vue new file mode 100644 index 0000000..431c1e3 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-checkbox.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-empty.vue b/src/uni_modules/next-table/components/next-table/components/table-empty.vue new file mode 100644 index 0000000..d411814 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-empty.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-h5-summary.vue b/src/uni_modules/next-table/components/next-table/components/table-h5-summary.vue new file mode 100644 index 0000000..be79e36 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-h5-summary.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-load-more.vue b/src/uni_modules/next-table/components/next-table/components/table-load-more.vue new file mode 100644 index 0000000..c2fc898 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-load-more.vue @@ -0,0 +1,50 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-paging.vue b/src/uni_modules/next-table/components/next-table/components/table-paging.vue new file mode 100644 index 0000000..9d8012b --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-paging.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-side-summary.vue b/src/uni_modules/next-table/components/next-table/components/table-side-summary.vue new file mode 100644 index 0000000..6a47ada --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-side-summary.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/components/table-summary.vue b/src/uni_modules/next-table/components/next-table/components/table-summary.vue new file mode 100644 index 0000000..8b0dbeb --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/components/table-summary.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/uni_modules/next-table/components/next-table/js/props.js b/src/uni_modules/next-table/components/next-table/js/props.js new file mode 100644 index 0000000..b46e4d6 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/js/props.js @@ -0,0 +1,71 @@ +export default { + props: { + highlight: { + type: Boolean, + default: false + }, + itemDate: { + type: Object, + default: () => {} + }, + columns: { + type: Array, + default: () => [] + }, + showSummary: { + type: Boolean, + default: false + }, + isShowLoadMore: { + type: Boolean, + default: false + }, + data: { + type: Array, + default: () => [] + }, + sumText: { + type: String, + default: '合计' + }, + showHeader: { + type: Boolean, + default: true + }, + border: { + type: Boolean, + default: false + }, + stripe: { + type: Boolean, + default: true + }, + fit: { + type: Boolean, + default: false + }, + showPaging: { + type: Boolean, + default: false + }, + pageIndex: { + type: Number, + default: 1 + }, + pageTotal: { + type: Number, + default: 0 + }, + primaryColor: { + type: String, + default: '#f0ad4e' + }, + rowKey: [String, Function], + summaryMethod: Function, + pullUpLoading: Function, + formatter: Function, + cellStyle: Function, + cellHeaderStyle: Function, + permissionBtn: Function, + } +} diff --git a/src/uni_modules/next-table/components/next-table/js/summary.js b/src/uni_modules/next-table/components/next-table/js/summary.js new file mode 100644 index 0000000..fbd81f1 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/js/summary.js @@ -0,0 +1,88 @@ +export default { + props:{ + scrollbarSize:{ + type:Number, + default:0 + }, + fixedLeftColumns:{ + type:Array, + default:()=>[] + }, + data:{ + type:Array, + default:()=>[] + }, + transColumns:{ + type:Array, + default:()=>[] + }, + border:{ + type:Boolean, + default:false + }, + showSummary:{ + type:Boolean, + default:false + }, + summaryMethod:{ + type:Function + }, + sumText:{ + type:String, + default:'合计' + }, + headerFooterTableLeft:{ + type:Number, + default:0 + }, + handleFooterTableScrollLeft:Function, + }, + data(){ + return{ + sums:[] + } + }, + watch:{ + 'data':{ + deep:true, + immediate:true, + handler(newValue,oldValue){ + let sums = []; + if (this.summaryMethod) { + sums = this.summaryMethod({ columns: this.transColumns, data: this.data }); + } else { + this.transColumns.forEach((column, index) => { + if (index === 0) { + sums[index] = this.sumText; + return; + } + const values = this.data.map(item => Number(item[column.name])); + const precisions = []; + let notNumber = true; + values.forEach(value => { + if (!isNaN(value)) { + notNumber = false; + let decimal = ('' + value).split('.')[1]; + precisions.push(decimal ? decimal.length : 0); + } + }); + const precision = Math.max.apply(null, precisions); + if (!notNumber) { + sums[index] = values.reduce((prev, curr) => { + const value = Number(curr); + if (!isNaN(value)) { + return parseFloat((prev + curr).toFixed(Math.min(precision, 20))); + } else { + return prev; + } + }, 0); + } else { + sums[index] = ''; + } + }); + } + this.sums = sums + }, + } + } +} diff --git a/src/uni_modules/next-table/components/next-table/js/util.js b/src/uni_modules/next-table/components/next-table/js/util.js new file mode 100644 index 0000000..aa0ddbd --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/js/util.js @@ -0,0 +1,91 @@ +/** + * 获取滚动条宽度 + */ +let cached = undefined; + +export const getScrollbarSize = fresh => { + // #ifdef H5 + + if (fresh || cached === undefined) { + let inner = document.createElement("div"); + let innerStyle = inner.style; + + innerStyle.width = "100%"; + innerStyle.height = "200px"; + + let outer = document.createElement("div"); + let outerStyle = outer.style; + + outerStyle.position = "absolute"; + outerStyle.top = 0; + outerStyle.left = 0; + outerStyle.pointerEvents = "none"; + outerStyle.width = "200px"; + outerStyle.height = "150px"; + outerStyle.visibility = "hidden"; + + outer.appendChild(inner); + document.body.appendChild(outer); + + // 设置子元素超出部分隐藏 + outerStyle.overflow = "hidden"; + + let width1 = inner.offsetWidth; + + // 设置子元素超出部分滚动 + outer.style.overflow = "scroll"; + + let width2 = inner.offsetWidth; + + if (width1 === width2) { + width2 = outer.clientWidth; + } + + document.body.removeChild(outer); + + cached = width1 - width2; + } + //#endif + + return cached; +}; + +// 16进制转换rgba +export const colorHextoRGB = (val, opacity = 0.7) => { + let color = val; + const t = {}, + bits = (color.length == 4) ? 4 : 8,//假设是shorthand。 #fff, 那么bits为4位, 每一位代表的个属性, 其他的为8位 每两位代表一个属性 #ffffff00 + mask = (1 << bits) - 1; //表示字节占位符。 向左移4位或8位,var a = (1 << 4 ) - 1 -> 10000 - 1, a.toString(2); // 1111。或者 8位的 1111 1111 + color = Number("0x" + color.substr(1)); //#ff0000 转变为16进制0xff0000; + if(isNaN(color)){ + return null; // Color + } + ["b", "g", "r"].forEach(function(x){ + const c = color & mask; + color >>= bits; + t[x] = bits == 4 ? 17 * c : c; // 0xfff , 一个f应该代表 255, 应该当[0-255],按15等份划分,每一等份间隔 17。 所以获得的值须要乘以17, 才干表示rgb中255的值 + }); + const rgba='rgba('+ t.r + ',' +t.g + ','+ t.b +',' + opacity+')' + return rgba; // Color +} + +// rgba转换16进制 +export const hexify = (color) => { + const values = color + .replace(/rgba?\(/, '') + .replace(/\)/, '') + .replace(/[\s+]/g, '') + .split(','); + const a = parseFloat(values[3] || 1), + r = Math.floor(a * parseInt(values[0]) + (1 - a) * 255), + g = Math.floor(a * parseInt(values[1]) + (1 - a) * 255), + b = Math.floor(a * parseInt(values[2]) + (1 - a) * 255); + return "#" + + ("0" + r.toString(16)).slice(-2) + + ("0" + g.toString(16)).slice(-2) + + ("0" + b.toString(16)).slice(-2); +} + + + + diff --git a/src/uni_modules/next-table/components/next-table/next-table.vue b/src/uni_modules/next-table/components/next-table/next-table.vue new file mode 100644 index 0000000..0a467d3 --- /dev/null +++ b/src/uni_modules/next-table/components/next-table/next-table.vue @@ -0,0 +1,1320 @@ + + + + + + \ No newline at end of file diff --git a/src/uni_modules/next-table/package.json b/src/uni_modules/next-table/package.json new file mode 100644 index 0000000..3fd764e --- /dev/null +++ b/src/uni_modules/next-table/package.json @@ -0,0 +1,98 @@ +{ + "id": "next-table", + "displayName": "next-table(多功能表格:多选checkbox、删除,编辑、合计,分页)", + "version": "1.2.3", + "description": "表格组件 支持固定表头和首列、上拉加载更多、及固定多列,表格自适应内容,排序,多选checkbox、可点击删除,编辑、合计功能,分页,自定义主题,兼容多端", + "keywords": [ + "table", + "固定表头、固定列、多选checkbox" + ], + "repository": "", + "engines": { + "uni-app": "^3.1.0", + "uni-app-x": "^3.1.0" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "", + "type": "component-vue", + "darkmode": "-", + "i18n": "-", + "widescreen": "-" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "√", + "aliyun": "√", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": "-", + "vue3": "-" + }, + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "vue": "-", + "nvue": "-", + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-", + "alipay": "-", + "toutiao": "-", + "baidu": "-", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "-", + "lark": "-", + "xhs": "-" + }, + "quickapp": { + "huawei": "-", + "union": "-" + } + }, + "uni-app-x": { + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/uni_modules/next-table/readme.md b/src/uni_modules/next-table/readme.md new file mode 100644 index 0000000..0f82d97 --- /dev/null +++ b/src/uni_modules/next-table/readme.md @@ -0,0 +1,509 @@ + +## 介绍 +基于uni-app开发的一个普通的表格组件,功能有固定首列和表头、排序、操作按钮、 +table 表格 固定表头、固定首列、多列 上拉加载更多、 排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计 多页功能 +已用于生产环境 + +> 遇到问题或有建议可以加入QQ群(455948571)反馈 +> 如果觉得组件不错,给五星鼓励鼓励咯! + + +## 参考依赖 +本组件居于github开源项目[zb-table](https://github.com/zouzhibin/zb-table#readme)进行二开,功能会有些差异和增强,如果有需要原版请参考源开源项目。感谢作者 + +## 注意本插件依赖于scss的编译,如果没有使用scss请手动改源码去掉scss的语法方可使用。如果有疑问请加入加入QQ群(455948571) + +## 注意 + +### 作者不介意你对组件源码进行改造使用,为了开源更加高效,谢谢你的配合;为了节省不必要的沟通浪费,以下情况请不要再反馈给作者,请自行解决; +### 在这感各位的理解,我支持开源,但是作者时间有限;谢谢各位的配合;在这里期望我写的小小插件能为你提供便捷; + + > 1.如果你对源码进行了修改使用,请不需要对作者做任何的反馈,作者确实没有空陪你做技术分析解答; + > 2.如果你引入插件,连插件是否有正常被uniapp框架识别解析都不清楚,请你换个插件使用; + > 3.如果你引入插件,针对自己项目进行功能改造的,请自行仔细阅读源码并了解其原理,自行改造;这里作者不愿意浪费过多时间进行技术解答; + +### 如果有使用问题请加群 + +注意:如果插件问题,请务必给一个完整的复现demo,谢谢配合; +[点击链接加入群聊前端开发(uniapp插件)】](https://qm.qq.com/q/S1bJzQfJAG) + +## 使用 + +>[从uniapp插件市场导入](https://ext.dcloud.net.cn/plugin?name=next-table) + +### 微信小程序在线体验 +![](https://lixueshiaa.github.io/webtest/www/static/img/ponder_next.png) + +### 预览 +### appDemo安装包下载地址:[android安装包](https://lixueshiaa.github.io/webtest/www/static/demo_next.apk); +*** + +| 功能预览 | +| :----------------------------------------------------------: | +| ![](https://lixueshiaa.github.io/webtest/www/static/next-table.gif) | + + +### 超集功能预览(增值功能-请下载next-x-table支持可固定表头,分组表头等强大的功能) +### 点击进入: [next-x-table](https://ext.dcloud.net.cn/plugin?id=19584) +### +| 小程序和app端随意设置容易宽高,冻结表头使得交互更加友好 | 动态分组表头/分页/排序/合计/fixed等功能 | +| :--------------------------------------------------------------------: | :-----------------------------------------------------------------------: | +| ![](https://lixueshiaa.github.io/webtest/www/static/next-table-aa.gif) | ![](https://lixueshiaa.github.io/webtest/www/static/next-x-table-app.gif) | + +| 分页功能演示 | 下拉加载更多等功能 | +| :-----------------------------------------------------------------------------: |:-----------------------------------------------------------------------: | +| ![](https://lixueshiaa.github.io/webtest/www/static/next-x-table-mapping-a.gif) |![](https://lixueshiaa.github.io/webtest/www/static/next-x-table-mapping.gif) | + +## 示例demo(vue3 + ts) +``` html + +``` + +```js + + +``` +## 多级表头示例demo,同样支持vue2(vue3 + ts) +``` html + + + +``` + +## 示例demo(vue2) +``` html + +``` + +```js + + +``` + +## table 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须| +| ------ | ------ | ------ | ------ | ------ |------ | +| data | 显示的数据 | array |-- | -- |必须 | +| column | 显示的列数据 | array |-- | -- |必须 | +| stripe | 是否为斑马纹 table| boolean | - |false | 否 | +| fit | 列的宽度是否自撑开 | boolean |true,false | false |否 | +| show-header | 是否显示表头 | boolean |true,false | true |否 | +| cell-style | 单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 | +| cell-header-style | 头部单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({ column, columnIndex})/Object |-- | -- |否 | +| formatter | colomn =》formatter 必须设置为true,才有作用,进行格式化数据,进行数据的转换 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 | +| border | 是否带有纵向边框 | boolean |true,false | true |否 | +| highlight | 是否要高亮当前行 | boolean |true,false | false |否 | +| show-summary | 是否在表尾显示合计行 | boolean |true,false | false |否 | +| sum-text | 合计行第一列的文本 | String |- | 合计 |否 | +| summary-method | 自定义的合计计算方法 | Function({ columns, data }) |- | - |否 | +| permissionBtn | 是否动态控制按钮的显示隐藏 | Function({ row, renders,index }) |- | - |否 | +| isShowLoadMore | 是否开启上拉加载 | boolean |true,false | false |否 | +| pullUpLoading | 开启上拉加载后的返回函数,接收参数done是函数,done(type),type为空代表还有数据,继续开启上拉加载,type='ok',代表结束上拉加载 | Function(done) |-- | -- |否 | +| showPaging | 是否开启分页器 | boolean |true,false | false |否 | +| pageIndex | 开启分页器后,当前页码 | Number |-- | 1 |否 | +| pageTotal | 开启分页器后,总页数 | Number |-- | 0 |否 | +| primaryColor | 主题颜色(注意:只支持16进制的颜色值如 #000000) | String |-- | 0 |#f0ad4e | + +``` +关闭上拉加载的方式1:pullUpLoading((done)=>{ + done(type) +}) +done 接收参数为 type ,type为空代表还有数据,可以继续加载,无数据的时候传入 'ok'代表结束 +``` + +## table 事件 +| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须| +| ------ | ------ | ------ |--------------------------| ------ |------ | +| 事件名自定义 | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (row,index)=>{} | -- |否 | +| sort-change | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (column,model,index)=>{} | -- |否 | +| currentChange | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight属性,this.$refs.table.resetHighlight()清除选中 | Function | (row,index)=>{} | -- |否 | +| toggleRowSelection | 用于多选表格,切换某一行的选中状态,第一个参数代表选中状态,参数二代表选中的对象 | Function | (selected ,array)=>{} | -- |否 | +| toggleAllSelection | 用于多选表格,切换所有行的选中状态 ,第一个参数代表选中状态,参数二代表选中的对象| Function | (selected ,array)=>{} | -- |否 | +| rowClick | 单击某行 ,第一个参数代表选中对象,参数二代表选中的index| Function | (row ,index)=>{} | -- |否 | +| cellClick | 单击单元格 ,当某个单元格被点击时会触发该事件| Function | (row ,index,column)=>{} | -- |否 | +| pullUpLoading | 开启上拉加载后的返回函数,无参数| Function | -- |-- |否 | +| pageChange | 开起分页paging时候,分页切换后的事件 返回切换后的页码 | Function | -- |-- |否 | + + +## data 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | ------ | ------ | ------ | ------ | +| checked | 是否被勾选 | boolean |true,false | 无 | + + +## column 属性 +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ------ | ------ | ------ | ------ | ------ | +| name | 属性值 | string |-- | 无 | +| label | 显示的标题 | string |-- | 无 | +| width | 列的宽度 | number |-- | 100 | +| disabled | 是否禁用 | boolean |true,false | false | +| fixed | 列是否固定在左侧,true 表示固定在左侧 | boolean |true,false | true | +| sorter | 排序,当设置为custom的时候代表自定义排序,不会再触发默认排序,会触发table事件@sort-change,可以通过接口来进行排序 | boolean |true,false,'custom' | false | +| emptyString | 当值为空的时候默认显示的值 | string | | -- | +| filters | 对象过滤的选项,对象格式,对象中的元素需要有 key 和 value 属性。 | Object | {key:value} | -- | +| align | 对齐方式 | String | left/center/right | left | +| type | 为 operation 的时候代表为操作按钮,img的时候代表图片地址,index代表序列号 | string | operation,img,index | -- | +| renders | type 为operation的时候 必传 | Array | {name:'名字',func:"父元素接收事件名",type:"按钮的类型",size:"大小"} | -- | +``` +type 为 operation 的时候代表为操作按钮 +renders 代表传入的按钮 Array =>[ + { + name:'编辑', + class:"", // 添加class + type:'primary',代表按钮的类型 type 为custom的时候自定义按钮 其他类型取决于uniapp buttom组件按钮 + size:'mini',代表按钮的大小 + func:'edit' // func 代表操作按钮点击的事件名字 父元素接收的事件 父元素 @edit + 例如:// + + } +] +``` + diff --git a/src/uni_modules/sl-table/package.json b/src/uni_modules/sl-table/package.json index f9e69fd..679b875 100644 --- a/src/uni_modules/sl-table/package.json +++ b/src/uni_modules/sl-table/package.json @@ -7,10 +7,12 @@ "合并单元格", "表格", "多级表头" -], + ], "repository": "", "engines": { - "HBuilderX": "^4.36" + "HBuilderX": "^4.36", + "uni-app": "^3.1.0", + "uni-app-x": "^3.1.0" }, "dcloudext": { "type": "component-vue", @@ -30,55 +32,67 @@ "data": "无", "permissions": "无" }, - "npmurl": "" + "npmurl": "", + "darkmode": "-", + "i18n": "-", + "widescreen": "-" }, "uni_modules": { "dependencies": [], "encrypt": [], "platforms": { "cloud": { - "tcb": "y", - "aliyun": "y", - "alipay": "y" + "tcb": "√", + "aliyun": "√", + "alipay": "√" }, "client": { - "Vue": { - "vue2": "y", - "vue3": "y" + "uni-app": { + "vue": { + "vue2": "-", + "vue3": "-" + }, + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "vue": "-", + "nvue": "-", + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-", + "alipay": "-", + "toutiao": "-", + "baidu": "-", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "-", + "lark": "-", + "xhs": "-" + }, + "quickapp": { + "huawei": "-", + "union": "-" + } }, - "App": { - "app-vue": "y", - "app-nvue": "u", - "app-uvue": "u", - "app-harmony": "u" - }, - "H5-mobile": { - "Safari": "y", - "Android Browser": "y", - "微信浏览器(Android)": "y", - "QQ浏览器(Android)": "y" - }, - "H5-pc": { - "Chrome": "y", - "IE": "u", - "Edge": "y", - "Firefox": "y", - "Safari": "y" - }, - "小程序": { - "微信": "y", - "阿里": "u", - "百度": "u", - "字节跳动": "u", - "QQ": "u", - "钉钉": "u", - "快手": "u", - "飞书": "u", - "京东": "u" - }, - "快应用": { - "华为": "u", - "联盟": "u" + "uni-app-x": { + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-" + } } } } diff --git a/src/uni_modules/uni-calendar/package.json b/src/uni_modules/uni-calendar/package.json index ec924e9..18d9347 100644 --- a/src/uni_modules/uni-calendar/package.json +++ b/src/uni_modules/uni-calendar/package.json @@ -10,15 +10,17 @@ "", "打卡", "日历选择" -], + ], "repository": "https://github.com/dcloudio/uni-ui", "engines": { - "HBuilderX": "" + "HBuilderX": "", + "uni-app": "^3.1.0", + "uni-app-x": "^3.1.0" }, "directories": { "example": "../../temps/example_temps" }, -"dcloudext": { + "dcloudext": { "sale": { "regular": { "price": "0.00" @@ -36,51 +38,69 @@ "permissions": "无" }, "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", - "type": "component-vue" + "type": "component-vue", + "darkmode": "-", + "i18n": "-", + "widescreen": "-" }, "uni_modules": { "dependencies": [], "encrypt": [], "platforms": { "cloud": { - "tcb": "y", - "aliyun": "y", - "alipay": "n" + "tcb": "√", + "aliyun": "√", + "alipay": "x" }, "client": { - "App": { - "app-vue": "y", - "app-nvue": "y" + "uni-app": { + "vue": { + "vue2": "-", + "vue3": "-" + }, + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "vue": "-", + "nvue": "-", + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-", + "alipay": "-", + "toutiao": "-", + "baidu": "-", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "-", + "lark": "-", + "xhs": "-" + }, + "quickapp": { + "huawei": "-", + "union": "-" + } }, - "H5-mobile": { - "Safari": "y", - "Android Browser": "y", - "微信浏览器(Android)": "y", - "QQ浏览器(Android)": "y" - }, - "H5-pc": { - "Chrome": "y", - "IE": "y", - "Edge": "y", - "Firefox": "y", - "Safari": "y" - }, - "小程序": { - "微信": "y", - "阿里": "y", - "百度": "y", - "字节跳动": "y", - "QQ": "y" - }, - "快应用": { - "华为": "u", - "联盟": "u" - }, - "Vue": { - "vue2": "y", - "vue3": "y" + "uni-app-x": { + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-" + } } } } } -} +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 46e36fe..97103cb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,6 @@ import { defineConfig } from "vite"; import uni from "@dcloudio/vite-plugin-uni"; -// https://vitejs.dev/config/ export default defineConfig({ plugins: [uni()], });