第一版
This commit is contained in:
303
AGENTS.md
303
AGENTS.md
@@ -1,143 +1,212 @@
|
|||||||
# AGENTS.md - Swimming uni-app Project
|
# 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
|
```bash
|
||||||
npm run dev:h5 # Run H5 development server
|
npm install
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build Commands
|
## Build And Dev Commands
|
||||||
|
|
||||||
|
### Common Development
|
||||||
```bash
|
```bash
|
||||||
npm run build:h5 # Build for H5
|
npm run dev:h5
|
||||||
npm run build:mp-weixin # Build for WeChat mini-program
|
npm run dev:h5:ssr
|
||||||
npm run build:app # Build for app
|
npm run dev:app
|
||||||
npm run build:app-android # Build for Android
|
npm run dev:app-android
|
||||||
npm run build:app-ios # Build for iOS
|
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
|
```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
|
### Validation
|
||||||
- **Note**: No test framework configured in this project yet.
|
```bash
|
||||||
|
npm run type-check
|
||||||
## 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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/
|
src/
|
||||||
├── pages/ # Page components (with sub-packages)
|
├── Service/ Service classes and base config
|
||||||
├── components/ # Reusable Vue components
|
├── common/ Shared utilities and domain models
|
||||||
├── Service/ # API services and utilities
|
├── components/ Reusable Vue SFC components
|
||||||
├── common/ # Common utilities and helpers
|
├── pages/ Routed uni-app pages
|
||||||
├── static/ # Static assets (images, etc.)
|
├── static/ Static assets
|
||||||
├── types/ # TypeScript type definitions
|
├── types/ Type declarations
|
||||||
├── uni_modules/ # Uni-app modules
|
├── uni_modules/ Third-party or packaged uni modules
|
||||||
└── colorui/ # ColorUI CSS framework
|
├── 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
|
## Imports And Module Usage
|
||||||
- **Files**: PascalCase for components (e.g., `ImageCropper.vue`), camelCase for utilities
|
- Use the `@/` alias for imports rooted in `src/`.
|
||||||
- **Components**: PascalCase for component names
|
- `tsconfig.json` maps `@/*` to `./src/*`.
|
||||||
- **Variables**: camelCase for local variables and functions
|
- Prefer alias imports for app code instead of long relative paths.
|
||||||
- **Constants**: UPPER_SNAKE_CASE for constants
|
- Import Vue composition helpers from `vue`.
|
||||||
- **Classes**: PascalCase for class names (e.g., `Service`)
|
- Import uni-app page lifecycle APIs from `@dcloudio/uni-app` when needed.
|
||||||
- **CSS Classes**: kebab-case (e.g., `home-container`, `timer-card`)
|
- 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
|
## Vue SFC Conventions
|
||||||
- Use `lang="ts"` in Vue SFC `<script>` tags
|
- Use Vue 3 `<script setup lang="ts">` for page and component SFCs.
|
||||||
- Use `<script setup>` syntax for Vue 3 composition API
|
- Keep file sections in the order: `<template>`, `<script setup lang="ts">`, `<style lang="scss" scoped>`.
|
||||||
- Type function parameters and return values
|
- Use `scoped` styles for page/component-local styling unless a global rule is required.
|
||||||
- Use `any` sparingly, prefer proper type definitions
|
- Prefer reactive state via `ref` and `reactive` from Vue.
|
||||||
- Path alias: `@/*` maps to `./src/*`
|
- Use uni-app page lifecycle hooks such as `onLoad` from `@dcloudio/uni-app`.
|
||||||
|
- Clean up timers or intervals in `onUnmounted()`.
|
||||||
|
|
||||||
### Vue Component Structure
|
## TypeScript Guidelines
|
||||||
```vue
|
- Use `lang="ts"` in Vue SFC scripts.
|
||||||
<template>
|
- Add explicit parameter types and return types for exported functions and service methods where practical.
|
||||||
<!-- Template with tabs for indentation -->
|
- Prefer concrete interfaces or domain models over `any`.
|
||||||
</template>
|
- Existing code uses `any` in several places; reduce new `any` usage instead of expanding it.
|
||||||
|
- Keep class names in PascalCase.
|
||||||
|
- Preserve existing static-service patterns unless there is a strong reason to refactor more broadly.
|
||||||
|
|
||||||
<script setup lang="ts">
|
## Service Layer Patterns
|
||||||
// Imports first
|
- Base configuration lives in `src/Service/BaseConfig.ts`.
|
||||||
// Composition API logic
|
- Shared service helpers live in `src/Service/Service.ts`.
|
||||||
// Functions
|
- Feature services under `src/Service/swimming/` usually:
|
||||||
// Lifecycle hooks
|
- define endpoint path constants as `private static` fields,
|
||||||
</script>
|
- 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`.
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
## Error Handling And User Feedback
|
||||||
/* SCSS styles with nested structure */
|
- Use `Service.Msg()` for toast-style user feedback.
|
||||||
</style>
|
- 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
|
## Naming Conventions
|
||||||
- **Indentation**: Use tabs (not spaces) for indentation
|
- Vue component filenames: PascalCase, for example `ImageCropper.vue`.
|
||||||
- **Quotes**: Single quotes for strings in TypeScript/JavaScript
|
- Utility and service helper filenames: usually camelCase or existing established names; match nearby files.
|
||||||
- **Semicolons**: Optional but consistent (project uses semicolons)
|
- Service classes: PascalCase for class names, for example `PlanService`.
|
||||||
- **Line endings**: CRLF (Windows)
|
- Some existing service class identifiers start lowercase, such as `studentService`; preserve existing public names unless a broader refactor is requested.
|
||||||
- **SCSS**: Use nested selectors, kebab-case class names
|
- 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
|
## Formatting Expectations
|
||||||
- Use `Service.Msg()` for toast messages
|
- Repository convention is tabs for indentation in source files.
|
||||||
- Use `Service.Alert()` for modal dialogs
|
- 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.
|
||||||
- API requests through `Service.Request()` handle 401 (token expired) automatically
|
- Semicolons are used frequently enough that new changes should stay consistent within the edited file.
|
||||||
- Return `ResultData` objects from service methods
|
- Keep line endings as CRLF on Windows-oriented files when possible.
|
||||||
- Clean up intervals in `onUnmounted()` lifecycle hook
|
- Use nested SCSS selectors where that is already the local pattern.
|
||||||
|
- Avoid introducing broad formatting-only diffs.
|
||||||
|
|
||||||
### UI Components
|
## Styling And UI
|
||||||
- Use **uview-plus** as primary UI component library
|
- Prefer `uview-plus` components where the project already uses them.
|
||||||
- Use **ColorUI** for CSS framework and icons
|
- ColorUI assets/components are also present; preserve established usage where relevant.
|
||||||
- Component prefix: `u-` for uview-plus components (e.g., `<u-icon>`, `<u-button>`)
|
- 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
|
## Platform And Runtime Notes
|
||||||
- Extend `BaseConfig` for service classes
|
- Use `uni.*` APIs for platform-specific behavior.
|
||||||
- Use static methods for utility functions
|
- Navigation typically goes through `Service.GoPage()`, `Service.GoPageTab()`, `Service.GoPageBack()`, or `Service.GoPageDelse()`.
|
||||||
- `Service` class provides:
|
- App state and auth token access are commonly wrapped by `Service.GetStorageCache()` and related helpers.
|
||||||
- API requests: `Service.Request()`
|
- Be careful when changing files inside `src/uni_modules/`; many are third-party modules and should only be edited when necessary.
|
||||||
- Navigation: `Service.GoPage()`, `Service.GoPageTab()`, `Service.GoPageBack()`
|
|
||||||
- Storage: `Service.SetStorageCache()`, `Service.GetStorageCache()`
|
|
||||||
- Messages: `Service.Msg()`, `Service.Alert()`
|
|
||||||
- Loading: `Service.LoadIng()`, `Service.LoadClose()`
|
|
||||||
|
|
||||||
### uni-app Specifics
|
## Agent Guidance
|
||||||
- Use `uni.` API for platform-specific operations
|
- Before changing code, inspect nearby files and follow their existing conventions.
|
||||||
- Pages registered in `src/pages.json`
|
- Keep edits focused and minimal; do not refactor unrelated areas opportunistically.
|
||||||
- Tab bar configured in `src/pages.json`
|
- Do not invent lint or test commands that are not actually configured.
|
||||||
- Use `rpx` units for responsive design
|
- When describing validation, use real commands from `package.json`.
|
||||||
- Global styles in `src/App.vue` and `src/uni.scss`
|
- 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
|
## Current Gaps To Remember
|
||||||
- `.gitignore` includes `node_modules/`, `dist/`, `unpackage/`
|
- No lint pipeline is configured.
|
||||||
- Commit messages should be descriptive (in Chinese or English)
|
- No automated test pipeline is configured.
|
||||||
|
- No single-test command exists yet.
|
||||||
### Key Dependencies
|
- No Cursor or Copilot repository instruction files exist.
|
||||||
- Vue 3.4.21
|
- Agents should rely on local code patterns plus this file when making changes.
|
||||||
- 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
|
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -26,7 +26,7 @@
|
|||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"uview-plus": "^3.3.54",
|
"uview-plus": "^3.7.32",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-i18n": "^9.1.9"
|
"vue-i18n": "^9.1.9"
|
||||||
},
|
},
|
||||||
@@ -12682,9 +12682,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uview-plus": {
|
"node_modules/uview-plus": {
|
||||||
"version": "3.7.13",
|
"version": "3.7.32",
|
||||||
"resolved": "https://registry.npmmirror.com/uview-plus/-/uview-plus-3.7.13.tgz",
|
"resolved": "https://registry.npmmirror.com/uview-plus/-/uview-plus-3.7.32.tgz",
|
||||||
"integrity": "sha512-vHByf0kxKReYxam6BuU6wn/80giCkMaMUHEblhkf4kAjP852b86V3ctkjfGtV17MEIORFo3Vkve+HFnHNXpwNg==",
|
"integrity": "sha512-sSUHHP0xgGkMoipRFB9XpiqTR/eaBn4WwF+rFQRcPLbr3CCey0hwH700HoqX6Uo6IsKensFIhv6Tw6Rs8XWv5w==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"HBuilderX": "^3.1.0",
|
"HBuilderX": "^3.1.0",
|
||||||
"uni-app": "^4.66",
|
"uni-app": "^4.66",
|
||||||
|
|||||||
@@ -41,6 +41,83 @@ class PlanService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 {
|
export {
|
||||||
|
|||||||
@@ -8,33 +8,49 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- ==================== 项目选择区域 ==================== -->
|
<!-- 项目选择区域 -->
|
||||||
<view class="project-select-section">
|
<view class="select-section">
|
||||||
<view class="section-header">
|
<view class="select-label">
|
||||||
<text class="section-title">选择项目</text>
|
<view class="label-text" style="margin-bottom: 20rpx;">选择项目</view>
|
||||||
</view>
|
<view class="picker-wrapper" @click="showProject=true">
|
||||||
<!-- 项目选择器 -->
|
<text class="picker-text">{{ selectProcet || '请选择项目' }}</text>
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
|
||||||
@change="handleProjectChange">
|
|
||||||
<view class="picker-wrapper">
|
|
||||||
<text class="picker-text">{{ selectedProjectName }}</text>
|
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</view>
|
||||||
|
</view>
|
||||||
|
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc"
|
||||||
|
:columns="projectOptions"></up-picker>
|
||||||
|
|
||||||
|
<!-- 日期选择 -->
|
||||||
|
<view class="date-filter" style="margin: 20rpx 20rpx 0;" v-if="selectProcet">
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">开始日期</text>
|
||||||
|
<text class="value">{{ begin || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">结束日期</text>
|
||||||
|
<text class="value">{{ end || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- ==================== 学生选择区域 ==================== -->
|
<!-- ==================== 学生选择区域 ==================== -->
|
||||||
<!-- 仅在选择项目后显示 -->
|
<!-- 仅在选择项目和时间后显示 -->
|
||||||
<view class="student-select-section" v-if="selectedProjectId && projectStudents.length > 0">
|
<view class="student-select-section" v-if="selectProcet && begin && projectStudents.length > 0">
|
||||||
<view class="section-header">
|
<view class="section-header">
|
||||||
<text class="section-title">选择学员(最多3人)</text>
|
<text class="section-title">选择学员(最多3人)</text>
|
||||||
<text class="select-count">已选:{{ selectedStudentIds.length }}/3</text>
|
<text class="select-count">已选:{{ selectedStudentIds.length }}/3</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 学生列表 -->
|
<!-- 学生列表 -->
|
||||||
<view class="student-list">
|
<view class="student-list">
|
||||||
<view v-for="student in projectStudents" :key="student.id"
|
<view v-for="student in projectStudents" :key="student.studentId"
|
||||||
:class="['student-item', { 'selected': selectedStudentIds.includes(student.id) }]"
|
:class="['student-item', { 'selected': selectedStudentIds.includes(student.studentId) }]"
|
||||||
@click="toggleStudent(student.id)">
|
@click="toggleStudent(student.studentId)">
|
||||||
<!-- 学生头像(使用首字母作为头像) -->
|
<!-- 学生头像(使用首字母作为头像) -->
|
||||||
<view class="student-avatar">
|
<view class="student-avatar">
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
||||||
@@ -42,7 +58,7 @@
|
|||||||
<!-- 学生姓名 -->
|
<!-- 学生姓名 -->
|
||||||
<text class="student-name">{{ student.name }}</text>
|
<text class="student-name">{{ student.name }}</text>
|
||||||
<!-- 选择状态图标 -->
|
<!-- 选择状态图标 -->
|
||||||
<view class="check-icon" v-if="selectedStudentIds.includes(student.id)">
|
<view class="check-icon" v-if="selectedStudentIds.includes(student.studentId)">
|
||||||
<u-icon name="checkmark" size="16" color="#fff"></u-icon>
|
<u-icon name="checkmark" size="16" color="#fff"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -57,18 +73,14 @@
|
|||||||
<view class="stats-card">
|
<view class="stats-card">
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-label">选中项目</text>
|
<text class="stat-label">选中项目</text>
|
||||||
<text class="stat-value">{{ selectedProjectName }}</text>
|
<text class="stat-value">{{ selectProcet }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
<view class="stat-divider"></view>
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-label">对比学员</text>
|
<text class="stat-label">对比学员</text>
|
||||||
<text class="stat-value">{{ selectedStudentIds.length }}人</text>
|
<text class="stat-value">{{ selectedStudentIds.length }}人</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
|
||||||
<view class="stat-item">
|
|
||||||
<text class="stat-label">训练次数</text>
|
|
||||||
<text class="stat-value">{{ maxTrainingCount }}次</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 折线图卡片 -->
|
<!-- 折线图卡片 -->
|
||||||
@@ -85,18 +97,12 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 图例说明 -->
|
|
||||||
<view class="legend-section">
|
|
||||||
<text class="legend-title">图例说明</text>
|
|
||||||
<view class="legend-list">
|
|
||||||
<view v-for="(student, index) in selectedStudents" :key="student.id" class="legend-item">
|
|
||||||
<view class="legend-color" :style="{ backgroundColor: chartColors[index] }"></view>
|
|
||||||
<text class="legend-name">{{ student.name }}</text>
|
|
||||||
<text class="legend-desc">({{ getStudentBestTime(student.id) }}s 最佳)</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历弹窗 -->
|
||||||
|
<up-calendar :show="showCalendar" mode="date" minDate='1776240407000' @confirm="calendarConfirm"
|
||||||
|
@close="calendarClose">
|
||||||
|
</up-calendar>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -104,6 +110,7 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
// ==================== 常量定义 ====================
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
@@ -115,30 +122,17 @@
|
|||||||
|
|
||||||
// ==================== 响应式数据 - 项目相关 ====================
|
// ==================== 响应式数据 - 项目相关 ====================
|
||||||
|
|
||||||
// 项目列表数据
|
// 项目选择
|
||||||
const projectList = ref([
|
let showProject = ref(false)
|
||||||
{ id: '1', name: '100米自由泳' },
|
let selectProcet = ref('')
|
||||||
{ id: '2', name: '50米自由泳' },
|
let selectId = ref('')
|
||||||
{ id: '3', name: '200米由泳' },
|
|
||||||
{ id: '4', name: '100米蛙泳' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 项目选择器选项(用于 picker 组件)
|
// 日期选择
|
||||||
const projectOptions = ref(projectList.value)
|
const begin = ref<string>('')
|
||||||
|
const end = ref<string>('')
|
||||||
|
const showCalendar = ref(false)
|
||||||
|
|
||||||
// 当前选中的项目索引(用于 picker 组件的显示)
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
const selectedProjectIndex = ref(-1)
|
|
||||||
|
|
||||||
// 当前选中的项目 ID
|
|
||||||
const selectedProjectId = ref('')
|
|
||||||
|
|
||||||
// 当前选中的项目名称(用于显示)
|
|
||||||
const selectedProjectName = computed(() => {
|
|
||||||
if (selectedProjectIndex.value === -1) {
|
|
||||||
return '请选择项目'
|
|
||||||
}
|
|
||||||
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== TypeScript 接口定义 ====================
|
// ==================== TypeScript 接口定义 ====================
|
||||||
|
|
||||||
@@ -147,7 +141,7 @@
|
|||||||
* 定义学生的基本信息
|
* 定义学生的基本信息
|
||||||
*/
|
*/
|
||||||
interface Student {
|
interface Student {
|
||||||
id : string
|
studentId : string
|
||||||
name : string
|
name : string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +169,7 @@
|
|||||||
// 已选中的学生详细信息列表((计算属性)
|
// 已选中的学生详细信息列表((计算属性)
|
||||||
const selectedStudents = computed(() => {
|
const selectedStudents = computed(() => {
|
||||||
return projectStudents.value.filter(student =>
|
return projectStudents.value.filter(student =>
|
||||||
selectedStudentIds.value.includes(student.id)
|
selectedStudentIds.value.includes(student.studentId)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -227,129 +221,59 @@
|
|||||||
series: [] as any[]
|
series: [] as any[]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 最大训练次数(计算属性)
|
|
||||||
const maxTrainingCount = computed(() => {
|
|
||||||
const allRecords = mockTrainingRecords[selectedProjectId.value] || []
|
|
||||||
const studentCounts = new Map<string, number>()
|
|
||||||
allRecords.forEach(record => {
|
|
||||||
studentCounts.set(record.studentId, (studentCounts.get(record.studentId) || 0) + 1)
|
|
||||||
})
|
|
||||||
return Math.max(0, ...Array.from(studentCounts.values()))
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== 模拟数据 ====================
|
|
||||||
|
|
||||||
// 模拟的学生数据(按项目分组)
|
|
||||||
const mockProjectStudents : Record<string, Student[]> = {
|
|
||||||
'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<string, StudentTrainingRecord[]> = {
|
|
||||||
'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(() => {
|
onLoad(() => {
|
||||||
loadProjectData()
|
getProjectData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
// TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 监听器 ====================
|
|
||||||
|
|
||||||
// 监听选中学生 ID 列表的变化
|
|
||||||
// 使用 getter 函数返回数组副本,这样可以确保监听器触发
|
|
||||||
watch(() => [...selectedStudentIds.value], (newIds, oldIds) => {
|
|
||||||
console.log('selectedStudentIds changed:', newIds)
|
const getProjectData = () => {
|
||||||
console.log('oldIds:', oldIds)
|
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
||||||
updateChartData()
|
if (res.code == 0) {
|
||||||
|
projectOptions.value[0] = res.data.list
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 业务逻辑方法 ====================
|
|
||||||
|
|
||||||
const loadProjectData = () => {
|
|
||||||
// TODO: 调用后端 API 获取项目列表
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectChange = (e : any) => {
|
const selectProcetFunc = (e : any) => {
|
||||||
const index = e.detail.value
|
selectProcet.value = e.value[0].name
|
||||||
selectedProjectIndex.value = index
|
selectId.value = e.value[0].planId
|
||||||
const selectedProject = projectList.value[index]
|
|
||||||
if (selectedProject) {
|
|
||||||
selectedProjectId.value = selectedProject.id
|
|
||||||
loadProjectStudents(selectedProject.id)
|
|
||||||
}
|
|
||||||
selectedStudentIds.value = []
|
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) => {
|
const loadProjectStudents = (projectId : string) => {
|
||||||
if (mockProjectStudents[projectId]) {
|
PlanService.GetPlanInfo(projectId).then(res => {
|
||||||
projectStudents.value = mockProjectStudents[projectId]
|
if (res.code == 0) {
|
||||||
|
projectStudents.value = res.data.plan.users
|
||||||
} else {
|
} else {
|
||||||
projectStudents.value = []
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudent = (studentId : string) => {
|
const toggleStudent = (studentId : string) => {
|
||||||
@@ -363,57 +287,44 @@
|
|||||||
}
|
}
|
||||||
selectedStudentIds.value.push(studentId)
|
selectedStudentIds.value.push(studentId)
|
||||||
}
|
}
|
||||||
|
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 getStudentBestTime = (studentId : string) : number => {
|
|
||||||
const records = mockTrainingRecords[selectedProjectId.value] || []
|
const xData = ref<DataItem[]>(
|
||||||
const studentRecords = records.filter(r => r.studentId === studentId)
|
Array.from({ length: studentIdList.length }, () => ({
|
||||||
if (studentRecords.length === 0) return 0
|
name: '',
|
||||||
return Math.min(...studentRecords.map(r => r.time))
|
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 updateChartData = () => {
|
|
||||||
if (selectedStudentIds.value.length === 0 || !selectedProjectId.value) {
|
|
||||||
lineChartData.value.series = []
|
|
||||||
lineChartData.value.categories = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectRecords = mockTrainingRecords[selectedProjectId.value] || []
|
|
||||||
const allDates = new Set<string>()
|
|
||||||
projectRecords.forEach(record => {
|
|
||||||
allDates.add(record.recordFullDate)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
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<string, number>()
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -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;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 28rpx;
|
padding: 28rpx;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
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 {
|
.picker-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -485,7 +439,8 @@
|
|||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
border-color: #52c41a;
|
border-color: #faad14;
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-text {
|
.picker-text {
|
||||||
|
|||||||
@@ -11,26 +11,36 @@
|
|||||||
<!-- 项目选择区域 -->
|
<!-- 项目选择区域 -->
|
||||||
<view class="select-section">
|
<view class="select-section">
|
||||||
<view class="select-label">
|
<view class="select-label">
|
||||||
<text class="label-text">选择项目</text>
|
<view class="label-text" style="margin-bottom: 20rpx;">选择项目</view>
|
||||||
</view>
|
<view class="picker-wrapper" @click="showProject=true">
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
<text class="picker-text">{{ selectProcet || '请选择项目' }}</text>
|
||||||
@change="handleProjectChange">
|
|
||||||
<view class="picker-wrapper">
|
|
||||||
<text class="picker-text">{{ selectedProjectName }}</text>
|
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc"
|
||||||
|
:columns="projectOptions"></up-picker>
|
||||||
|
|
||||||
<!-- 日历组件 -->
|
<!-- 日期选择 -->
|
||||||
<view class="calendar-wrapper" v-if="selectedProjectId">
|
<view class="date-filter" style="margin: 20rpx 20rpx 0;" v-if="selectProcet">
|
||||||
<uni-calendar :insert="true" :range='true' :lunar="false" :show-month="true" :selected="selectedDates"
|
<view class="date-picker" @click="openCalendar()">
|
||||||
@monthSwitch="handleMonthSwitch" @change="handleDateChange">
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
</uni-calendar>
|
<view class="date-text">
|
||||||
|
<text class="label">开始日期</text>
|
||||||
|
<text class="value">{{ begin || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">结束日期</text>
|
||||||
|
<text class="value">{{ end || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 学生选择区域 -->
|
<!-- 学生选择区域 -->
|
||||||
<view class="select-section" v-if="selectedProjectId && selectedDate">
|
<view class="select-section" v-if="selectProcet && begin ">
|
||||||
<view class="select-label">
|
<view class="select-label">
|
||||||
<text class="label-text">选择学生</text>
|
<text class="label-text">选择学生</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -51,7 +61,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="student-list" scroll-y>
|
<scroll-view class="student-list" scroll-y>
|
||||||
<view v-for="(student, index) in studentList" :key="student.id" class="student-item"
|
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item"
|
||||||
:class="{ 'selected': selectedStudentIndexes.includes(index) }"
|
:class="{ 'selected': selectedStudentIndexes.includes(index) }"
|
||||||
@click="toggleStudentSelection(index)">
|
@click="toggleStudentSelection(index)">
|
||||||
<view class="item-checkbox">
|
<view class="item-checkbox">
|
||||||
@@ -61,40 +71,29 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-avatar">
|
<view class="item-avatar">
|
||||||
<view class="avatar-circle"
|
<view class="avatar-circle male">
|
||||||
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
<text class="item-name">{{ student.name }}</text>
|
<text class="item-name">{{ student.name }}</text>
|
||||||
<view class="item-meta">
|
|
||||||
<text class="gender-badge"
|
|
||||||
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
|
||||||
{{ student.gender }}
|
|
||||||
</text>
|
|
||||||
<text class="age-text">{{ student.age }}岁</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<view class="table-section" v-if="selectedDate && selectedStudentIndexes.length > 0">
|
<view v-if="tableData.length>0" class="table-section">
|
||||||
<sl-table :columns="columns" :tableData="tableData" @cell-click="handleCellClick">
|
<next-table :show-header="true" :columns="columns" :stripe="true" :fit="false" :show-summary='false'
|
||||||
<template #empty>
|
:data="tableData" :showPaging='true' :pageIndex="page" @pageChange="pageChange"
|
||||||
<view class="empty-container">
|
:pageTotal="pageTotal"></next-table>
|
||||||
<view class="empty-icon">
|
|
||||||
<u-icon name="file-text" size="80" color="#ccc"></u-icon>
|
|
||||||
</view>
|
|
||||||
<text class="empty-text">该日期暂无训练数据</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</sl-table>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历弹窗 -->
|
||||||
|
<up-calendar :show="showCalendar" mode="date" minDate='1776240407000' @confirm="calendarConfirm"
|
||||||
|
@close="calendarClose">
|
||||||
|
</up-calendar>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -102,6 +101,7 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
interface Project {
|
interface Project {
|
||||||
id : string
|
id : string
|
||||||
@@ -134,65 +134,62 @@
|
|||||||
const currentMonth = ref(new Date().getMonth() + 1)
|
const currentMonth = ref(new Date().getMonth() + 1)
|
||||||
const selectedDate = ref('')
|
const selectedDate = ref('')
|
||||||
|
|
||||||
|
// 项目选择
|
||||||
|
let showProject = ref(false)
|
||||||
|
let selectProcet = ref('')
|
||||||
|
let selectId = ref('')
|
||||||
|
|
||||||
|
// 日期选择
|
||||||
|
const begin = ref<string>('')
|
||||||
|
const end = ref<string>('')
|
||||||
|
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([
|
const columns = ref([
|
||||||
{
|
{
|
||||||
label: '日期',
|
label: '训练日期',
|
||||||
prop: 'date',
|
name: 'addTime',
|
||||||
width: '100px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '姓名',
|
label: '姓名',
|
||||||
prop: 'name',
|
name: 'studentName',
|
||||||
width: '100px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '项目名称',
|
label: '项目名称',
|
||||||
prop: 'projectName',
|
name: 'name',
|
||||||
width: '120px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '包干计划',
|
label: '项目类型',
|
||||||
prop: 'plan',
|
name: 'planType',
|
||||||
width: '100px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '完成比例',
|
label: '完成比例',
|
||||||
prop: 'completion',
|
name: 'completion',
|
||||||
width: '100px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '详细数据',
|
label: '详细数据',
|
||||||
prop: 'detail',
|
name: 'detail',
|
||||||
width: '120px'
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const tableData = ref<TableDataItem[]>([])
|
const tableData = ref<TableDataItem[]>([])
|
||||||
|
|
||||||
const projectList = ref<Project[]>([
|
|
||||||
{ id: '1', name: '100米自由泳' },
|
|
||||||
{ id: '2', name: '200米自由泳' },
|
|
||||||
{ id: '3', name: '400米自由泳' },
|
|
||||||
{ id: '4', name: '100米蛙泳' }
|
|
||||||
])
|
|
||||||
|
|
||||||
const projectOptions = ref(projectList.value)
|
|
||||||
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
|
|
||||||
const selectedProjectIndex = ref(-1)
|
const selectedProjectIndex = ref(-1)
|
||||||
const selectedProjectId = ref('')
|
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<Student[]>([])
|
const studentList = ref<Student[]>([])
|
||||||
|
|
||||||
@@ -215,69 +212,12 @@
|
|||||||
return Math.round(total / tableData.value.length)
|
return Math.round(total / tableData.value.length)
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockData : Record<string, Record<string, TableDataItem[]>> = {
|
|
||||||
'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<string, Student[]> = {
|
|
||||||
'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(() => {
|
onLoad(() => {
|
||||||
loadData()
|
loadData()
|
||||||
|
getProjectData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
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 selectProcetFunc = (e : any) => {
|
||||||
const index = e.detail.value
|
selectProcet.value = e.value[0].name
|
||||||
selectedProjectIndex.value = index
|
selectId.value = e.value[0].planId
|
||||||
const selectedProject = projectList.value[index]
|
getProjectDetail()
|
||||||
|
|
||||||
if (selectedProject) {
|
|
||||||
selectedProjectId.value = selectedProject.id
|
|
||||||
selectedDate.value = ''
|
|
||||||
selectedStudentIndexes.value = []
|
|
||||||
selectedStudentIds.value = []
|
|
||||||
tableData.value = []
|
|
||||||
loadProjectStudents(selectedProject.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMonthSwitch = (e : any) => {
|
|
||||||
currentYear.value = e.year
|
const getProjectDetail = () => {
|
||||||
currentMonth.value = e.month
|
PlanService.GetPlanInfo(selectId.value).then(res => {
|
||||||
loadData()
|
if (res.code == 0) {
|
||||||
selectedDate.value = ''
|
studentList.value = res.data.plan.users
|
||||||
selectedStudentIndexes.value = []
|
} else {
|
||||||
selectedStudentIds.value = []
|
Service.Msg(res.msg)
|
||||||
tableData.value = []
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDateChange = (e : any) => {
|
|
||||||
console.log(e);
|
|
||||||
const date = e.fulldate
|
|
||||||
selectedDate.value = date
|
const openCalendar = () => {
|
||||||
selectedStudentIndexes.value = []
|
showCalendar.value = true
|
||||||
selectedStudentIds.value = []
|
|
||||||
tableData.value = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadProjectStudents = (projectId : string) => {
|
const calendarConfirm = (e : any) => {
|
||||||
const students = mockStudents[projectId] || []
|
begin.value = e[0]
|
||||||
studentList.value = students
|
end.value = e[e.length - 1]
|
||||||
console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`)
|
showCalendar.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calendarClose = () => {
|
||||||
|
showCalendar.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const selectAllStudents = () => {
|
const selectAllStudents = () => {
|
||||||
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
||||||
}
|
}
|
||||||
@@ -350,36 +293,50 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const confirmStudentSelection = () => {
|
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
|
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 = () => {
|
const closeStudentPicker = () => {
|
||||||
showStudentPicker.value = false
|
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))
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -393,7 +350,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-section {
|
.header-section {
|
||||||
background-color: #fff;
|
|
||||||
padding: 32rpx 28rpx 24rpx;
|
padding: 32rpx 28rpx 24rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
@@ -413,6 +370,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.select-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
@@ -455,14 +443,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-wrapper {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.student-picker-modal {
|
.student-picker-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -8,41 +8,34 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- ==================== 项目选择区域 ==================== -->
|
<!-- 项目选择区域 -->
|
||||||
<view class="project-select-section">
|
<view class="select-section">
|
||||||
<view class="section-header">
|
<view class="select-label">
|
||||||
<text class="section-title">选择项目</text>
|
<view class="label-text" style="margin-bottom: 20rpx;">选择项目</view>
|
||||||
</view>
|
<view class="picker-wrapper" @click="showProject=true">
|
||||||
<!-- 项目选择器 -->
|
<text class="picker-text">{{ selectProcet || '请选择项目' }}</text>
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
|
||||||
@change="handleProjectChange">
|
|
||||||
<view class="picker-wrapper">
|
|
||||||
<text class="picker-text">{{ selectedProjectName }}</text>
|
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc"
|
||||||
|
:columns="projectOptions"></up-picker>
|
||||||
|
|
||||||
|
|
||||||
<!-- ==================== 排名列表区域 ==================== -->
|
<!-- ==================== 排名列表区域 ==================== -->
|
||||||
<!-- 仅在选择了项目且有数据时显示 -->
|
<!-- 仅在选择了项目且有数据时显示 -->
|
||||||
<view class="ranking-section" v-if="selectedProjectId && gradeList.length > 0">
|
<view class="ranking-section" v-if="selectProcet && gradeList.length > 0">
|
||||||
<!-- 排名统计卡片 -->
|
<!-- 排名统计卡片 -->
|
||||||
<view class="stats-card">
|
<view class="stats-card">
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-label">对比项目</text>
|
<text class="stat-label">对比项目</text>
|
||||||
<text class="stat-value">{{ selectedProjectName }}</text>
|
<text class="stat-value">{{ selectProcet }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
<view class="stat-divider"></view>
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-label">参与人数</text>
|
<text class="stat-label">参与人数</text>
|
||||||
<text class="stat-value">{{ gradeList.length }}人</text>
|
<text class="stat-value">{{ gradeList.length }}人</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-divider"></view>
|
|
||||||
<view class="stat-item">
|
|
||||||
<text class="stat-label">最高速度</text>
|
|
||||||
<text class="stat-value">{{ maxSpeed }}m/s</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 排名列表 -->
|
<!-- 排名列表 -->
|
||||||
@@ -56,14 +49,13 @@
|
|||||||
|
|
||||||
<!-- 学生信息 -->
|
<!-- 学生信息 -->
|
||||||
<view class="student-info">
|
<view class="student-info">
|
||||||
<text class="student-name">{{ item.name }}</text>
|
<text class="student-name">{{ item.studentName }}</text>
|
||||||
<text class="student-speed">最快速度:{{ item.bestSpeed }}m/s</text>
|
<text class="student-speed">最快用时:{{ item.quicklyTime }}s</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 成绩详情 -->
|
<!-- 成绩详情 -->
|
||||||
<view class="speed-detail">
|
<view class="speed-detail">
|
||||||
<text class="detail-text">用时:{{ item.bestTime }}s</text>
|
<text class="detail-text">日期:{{ Service.formatDate(item.addTime,2)}}</text>
|
||||||
<text class="detail-text">日期:{{ item.recordDate }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -75,33 +67,16 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
// ==================== 响应式数据 - 项目相关 ====================
|
// ==================== 响应式数据 - 项目相关 ====================
|
||||||
|
|
||||||
// 项目列表数据
|
// 项目选择
|
||||||
const projectList = ref([
|
let showProject = ref(false)
|
||||||
{ id: '1', name: '100米自由泳' },
|
let selectProcet = ref('')
|
||||||
{ id: '2', name: '50米自由泳' },
|
let selectId = ref('')
|
||||||
{ id: '3', name: '200米自由泳' },
|
|
||||||
{ id: '4', name: '100米蛙泳' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 项目选择器选项(用于 picker 组件)
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
const projectOptions = ref(projectList.value)
|
|
||||||
|
|
||||||
// 当前选中的项目索引(用于 picker 组件的显示)
|
|
||||||
const selectedProjectIndex = ref(-1)
|
|
||||||
|
|
||||||
// 当前选中的项目 ID
|
|
||||||
const selectedProjectId = ref('')
|
|
||||||
|
|
||||||
// 当前选中的项目名称(用于显示)
|
|
||||||
const selectedProjectName = computed(() => {
|
|
||||||
if (selectedProjectIndex.value === -1) {
|
|
||||||
return '请选择项目'
|
|
||||||
}
|
|
||||||
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== TypeScript 接口定义 ====================
|
// ==================== TypeScript 接口定义 ====================
|
||||||
|
|
||||||
@@ -129,36 +104,7 @@
|
|||||||
return Math.max(...gradeList.value.map(item => item.bestSpeed)).toFixed(2)
|
return Math.max(...gradeList.value.map(item => item.bestSpeed)).toFixed(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 模拟数据 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟的学生成绩数据(按项目分组)
|
|
||||||
* 键为项目 ID,值为该项目的学生成绩列表
|
|
||||||
*/
|
|
||||||
const mockStudentGrades : Record<string, StudentGrade[]> = {
|
|
||||||
'1': [
|
|
||||||
{ id: 'g1', name: '王小明', bestSpeed: 3.89, bestTime: 25.72, recordDate: '03-24', studentId: 's3' },
|
|
||||||
{ id: 'g2', name: '张小明', bestSpeed: 3.97, bestTime: 25.18, recordDate: '03-24', studentId: 's1' },
|
|
||||||
{ id: 'g3', name: '赵小芳', bestSpeed: 3.03, bestTime: 32.96, recordDate: '03-14', studentId: 's4' },
|
|
||||||
{ id: 'g4', name: '李小红', bestSpeed: 3.38, bestTime: 29.58, recordDate: '03-24', studentId: 's2' },
|
|
||||||
{ id: 'g5', name: '陈小刚', bestSpeed: 2.82, bestTime: 35.46, recordDate: '03-19', studentId: 's5' }
|
|
||||||
],
|
|
||||||
'2': [
|
|
||||||
{ id: 'g6', name: '杨小龙', bestSpeed: 2.05, bestTime: 24.39, recordDate: '03-19', studentId: 's8' },
|
|
||||||
{ id: 'g7', name: '刘小华', bestSpeed: 2.11, bestTime: 23.70, recordDate: '03-19', studentId: 's6' },
|
|
||||||
{ id: 'g8', name: '张小丽', bestSpeed: 1.91, bestTime: 26.18, recordDate: '03-16', studentId: 's7' }
|
|
||||||
],
|
|
||||||
'3': [
|
|
||||||
{ id: 'g9', name: '吴小西', bestSpeed: 1.59, bestTime: 125.79, recordDate: '03-16', studentId: 's10' },
|
|
||||||
{ id: 'g10', name: '黄小东', bestSpeed: 1.61, bestTime: 124.23, recordDate: '03-14', studentId: 's9' },
|
|
||||||
{ id: 'g11', name: '周小南', bestSpeed: 0.00, bestTime: 0, recordDate: '-', studentId: 's11' },
|
|
||||||
{ id: 'g12', name: '徐小北', bestSpeed: 0.00, bestTime: 0, recordDate: '-', studentId: 's12' }
|
|
||||||
],
|
|
||||||
'4': [
|
|
||||||
{ id: 'g13', name: '马小兵', bestSpeed: 1.22, bestTime: 81.97, recordDate: '03-14', studentId: 's13' },
|
|
||||||
{ id: 'g14', name: '朱小红', bestSpeed: 1.13, bestTime: 88.47, recordDate: '03-12', studentId: 's14' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 生命周期钩子 ====================
|
// ==================== 生命周期钩子 ====================
|
||||||
|
|
||||||
@@ -168,56 +114,27 @@
|
|||||||
*/
|
*/
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
loadProjectData()
|
getProjectData()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 页面显示时触发
|
|
||||||
* 每次页面从后台切换到前台时执行
|
|
||||||
*/
|
|
||||||
onShow(() => {
|
|
||||||
// TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== 业务逻辑方法 - 项目相关 ====================
|
// ==================== 业务逻辑方法 - 项目相关 ====================
|
||||||
|
|
||||||
/**
|
const getProjectData = () => {
|
||||||
* 加载项目数据
|
PlanService.GetPlanListNoPage('计时项目').then(res => {
|
||||||
* 从后端 API 获取项目列表
|
if (res.code == 0) {
|
||||||
*/
|
projectOptions.value[0] = res.data.list
|
||||||
const loadProjectData = () => {
|
} else {
|
||||||
// TODO: 调用后端 API 获取项目列表
|
Service.Msg(res.msg)
|
||||||
// 示例代码:
|
}
|
||||||
// Service.Request('/api/projects', 'GET').then(res => {
|
})
|
||||||
// projectList.value = res.data
|
|
||||||
// projectOptions.value = res.data
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const selectProcetFunc = (e : any) => {
|
||||||
* 处理项目选择变化事件
|
selectProcet.value = e.value[0].name
|
||||||
* 当用户选择项目时触发
|
selectId.value = e.value[0].planId
|
||||||
* @param e picker 选择事件对象,包含 detail.value 属性表示选中的索引
|
loadProjectGrades(selectId.value)
|
||||||
*/
|
|
||||||
const handleProjectChange = (e : any) => {
|
|
||||||
// 获取选中的项目索引
|
|
||||||
const index = e.detail.value
|
|
||||||
|
|
||||||
// 更新选中项目的索引
|
|
||||||
selectedProjectIndex.value = index
|
|
||||||
|
|
||||||
// 获取选中的项目
|
|
||||||
const selectedProject = projectList.value[index]
|
|
||||||
|
|
||||||
if (selectedProject) {
|
|
||||||
// 更新选中项目的 ID
|
|
||||||
selectedProjectId.value = selectedProject.id
|
|
||||||
|
|
||||||
// 加载该项目的排名数据
|
|
||||||
loadProjectGrades(selectedProject.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 实际项目中应该调用 API 获取该项目的排名数据
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,13 +143,13 @@
|
|||||||
* @param projectId 项目 ID
|
* @param projectId 项目 ID
|
||||||
*/
|
*/
|
||||||
const loadProjectGrades = (projectId : string) => {
|
const loadProjectGrades = (projectId : string) => {
|
||||||
// 从模拟数据中获取排名列表
|
PlanService.GetRankData(projectId).then(res => {
|
||||||
if (mockStudentGrades[projectId]) {
|
if (res.code == 0) {
|
||||||
// 按最快速度从高到低排序
|
gradeList.value=res.data.list
|
||||||
gradeList.value = [...mockStudentGrades[projectId]].sort((a, b) => b.bestSpeed - a.bestSpeed)
|
|
||||||
} else {
|
} else {
|
||||||
gradeList.value = []
|
Service.Msg(res.msg)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 业务逻辑方法 - 排名相关 ====================
|
// ==================== 业务逻辑方法 - 排名相关 ====================
|
||||||
@@ -284,7 +201,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 项目选择区域 ==================== */
|
/* ==================== 项目选择区域 ==================== */
|
||||||
.project-select-section {
|
.select-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
@@ -292,17 +209,15 @@
|
|||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&::before {
|
.select-label {
|
||||||
content: '';
|
margin-bottom: 16rpx;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
.label-text {
|
||||||
top: 0;
|
font-size: 26rpx;
|
||||||
bottom: 0;
|
font-weight: 600;
|
||||||
width: 6rpx;
|
color: #666;
|
||||||
// background: linear-gradient(180deg, #1890ff 0%, #096dd9 100%);
|
}
|
||||||
border-radius: 20rpx 0 0 20rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-wrapper {
|
.picker-wrapper {
|
||||||
@@ -316,8 +231,8 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #fff0f5;
|
background-color: #f0f0f0;
|
||||||
border-color: #1890ff;
|
border-color: #faad14;
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,53 +7,62 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 项目选择区域 -->
|
||||||
<view class="select-section">
|
<view class="select-section">
|
||||||
<view class="select-label">
|
<view class="select-label">
|
||||||
<text class="label-text">选择项目</text>
|
<view class="label-text" style="margin-bottom: 20rpx;">选择项目</view>
|
||||||
</view>
|
<view class="picker-wrapper" @click="showProject=true">
|
||||||
<picker mode="selector" :range="projectOptions" range-key="name" :value="selectedProjectIndex"
|
<text class="picker-text">{{ selectProcet || '请选择项目' }}</text>
|
||||||
@change="handleProjectChange">
|
|
||||||
<view class="picker-wrapper">
|
|
||||||
<text class="picker-text">{{ selectedProjectName }}</text>
|
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</view>
|
||||||
|
</view>
|
||||||
|
<up-picker v-model:show="showProject" keyName="name" valueName="planId" @confirm="selectProcetFunc"
|
||||||
|
:columns="projectOptions"></up-picker>
|
||||||
|
|
||||||
|
<!-- 日期选择 -->
|
||||||
|
<view class="date-filter" style="margin: 20rpx 20rpx 0;" v-if="selectProcet">
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">开始日期</text>
|
||||||
|
<text class="value">{{ begin || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">结束日期</text>
|
||||||
|
<text class="value">{{ end || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="calendar-wrapper" v-if="selectedProjectId">
|
<!-- 学生选择区域 -->
|
||||||
<uni-calendar
|
<view class="select-section" v-if="selectProcet && begin ">
|
||||||
:insert="true"
|
|
||||||
:lunar="false"
|
|
||||||
:show-month="true"
|
|
||||||
:selected="selectedDates"
|
|
||||||
@monthSwitch="handleMonthSwitch"
|
|
||||||
@change="handleDateChange">
|
|
||||||
</uni-calendar>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="select-section" v-if="selectedProjectId && selectedDate">
|
|
||||||
<view class="select-label">
|
<view class="select-label">
|
||||||
<text class="label-text">选择学生</text>
|
<text class="label-text">选择学生</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="picker-wrapper" @click="showStudentSelect = true">
|
<view class="picker-wrapper" @click="showStudentPicker = true">
|
||||||
<text class="picker-text">{{ selectedStudentDisplay }}</text>
|
<text class="picker-text">{{ selectedStudentDisplay }}</text>
|
||||||
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
<u-icon name="arrow-down" size="18" color="#999"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="modal-overlay" v-if="showStudentSelect" @click="closeStudentSelect"></view>
|
<!-- 学生多选选择器 -->
|
||||||
<view class="student-picker-modal" v-if="showStudentSelect">
|
<view class="modal-overlay" v-if="showStudentPicker" @click="closeStudentPicker"></view>
|
||||||
|
<view class="student-picker-modal" v-if="showStudentPicker">
|
||||||
<view class="picker-header">
|
<view class="picker-header">
|
||||||
<text class="header-title">选择学生</text>
|
<text class="header-title">选择学生</text>
|
||||||
<view class="header-actions">
|
<view class="header-actions">
|
||||||
<text class="action-btn" @click="selectAllStudents">全选</text>
|
<text class="action-btn" @click="selectAllStudents">全选</text>
|
||||||
<text class="action-btn primary" @click="confirmStudentSelect">确定</text>
|
<text class="action-btn primary" @click="confirmStudentSelection">确定</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="student-list" scroll-y>
|
<scroll-view class="student-list" scroll-y>
|
||||||
<view v-for="(student, index) in studentList" :key="student.id" class="student-item"
|
<view v-for="(student, index) in studentList" :key="student.studentId" class="student-item"
|
||||||
:class="{ 'selected': selectedStudentIndexes.includes(index) }"
|
:class="{ 'selected': selectedStudentIndexes.includes(index) }"
|
||||||
@click="toggleStudentSelect(index)">
|
@click="toggleStudentSelection(index)">
|
||||||
<view class="item-checkbox">
|
<view class="item-checkbox">
|
||||||
<view class="checkbox-inner" :class="{ 'checked': selectedStudentIndexes.includes(index) }">
|
<view class="checkbox-inner" :class="{ 'checked': selectedStudentIndexes.includes(index) }">
|
||||||
<u-icon v-if="selectedStudentIndexes.includes(index)" name="checkmark" size="14"
|
<u-icon v-if="selectedStudentIndexes.includes(index)" name="checkmark" size="14"
|
||||||
@@ -61,58 +70,62 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-avatar">
|
<view class="item-avatar">
|
||||||
<view class="avatar-circle"
|
<view class="avatar-circle male">
|
||||||
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
|
||||||
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
<text class="avatar-text">{{ student.name.charAt(0) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
<text class="item-name">{{ student.name }}</text>
|
<text class="item-name">{{ student.name }}</text>
|
||||||
<view class="item-meta">
|
|
||||||
<text class="gender-badge"
|
|
||||||
:class="{ 'male': student.gender === '男', 'female': student.gender === '女' }">
|
|
||||||
{{ student.gender }}
|
|
||||||
</text>
|
|
||||||
<text class="age-text">{{ student.age }}岁</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="tableData.length>0" class="table-section">
|
||||||
<view class="table-section" v-if="selectedDate && selectedStudentIndexes.length > 0 && tableData.length > 0">
|
<next-table :show-header="true" :columns="columns" :stripe="true" :fit="false" :show-summary='false'
|
||||||
<view class="table-wrapper-scroll">
|
:data="tableData" :showPaging='true' :pageIndex="page" @pageChange="pageChange" @cellClick="cellClick"
|
||||||
<view class="table-header">
|
:pageTotal="pageTotal"></next-table>
|
||||||
<view class="header-row">
|
|
||||||
<view class="header-cell" style="width: 100px;">日期</view>
|
|
||||||
<view class="header-cell" style="width: 100px;">姓名</view>
|
|
||||||
<view class="header-cell" style="width: 120px;">项目</view>
|
|
||||||
<view class="header-cell" style="width: 100px;">20米</view>
|
|
||||||
<view class="header-cell" style="width: 100px;">70米</view>
|
|
||||||
<view class="header-cell" style="width: 100px;">120米</view>
|
|
||||||
<view class="header-cell" style="width: 100px;">170米</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="table-body">
|
|
||||||
<view v-for="(row, rowIndex) in tableData" :key="rowIndex" class="body-row" :class="{ 'even': rowIndex % 2 === 0, 'odd': rowIndex % 2 === 1 }">
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.date }}</view>
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.name }}</view>
|
|
||||||
<view class="body-cell" style="width: 120px;">{{ row.projectName }}</view>
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.segment20 }}</view>
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.segment70 }}</view>
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.segment120 }}</view>
|
|
||||||
<view class="body-cell" style="width: 100px;">{{ row.segment170 }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="empty-container" v-if="selectedDate && selectedStudentIndexes.length > 0 && tableData.length === 0">
|
|
||||||
|
<view class="empty-container" v-if="begin && selectedStudentIndexes.length > 0 && tableData.length === 0">
|
||||||
<view class="empty-icon">
|
<view class="empty-icon">
|
||||||
<u-icon name="file-text" size="80" color="#ccc"></u-icon>
|
<u-icon name="file-text" size="80" color="#ccc"></u-icon>
|
||||||
</view>
|
</view>
|
||||||
<text class="empty-text">该日期暂无分段数据</text>
|
<text class="empty-text">该日期暂无分段数据</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历弹窗 -->
|
||||||
|
<up-calendar :show="showCalendar" mode="date" minDate='1776240407000' @confirm="calendarConfirm"
|
||||||
|
@close="calendarClose">
|
||||||
|
</up-calendar>
|
||||||
|
|
||||||
|
<!-- 分段详情弹窗 -->
|
||||||
|
<up-popup :show="showSegmentPopup" @close="closeSegmentPopup" @open="openSegmentPopup" mode="center" round="16" bgColor="#fff">
|
||||||
|
<view class="segment-popup-content">
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">分段详情</text>
|
||||||
|
<u-icon name="close" size="22" color="#999" @click="closeSegmentPopup"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="popup-table">
|
||||||
|
<view class="popup-table-header">
|
||||||
|
<text class="popup-th">分段数</text>
|
||||||
|
<text class="popup-th">距离</text>
|
||||||
|
<text class="popup-th">时间</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="popup-scroll" scroll-y>
|
||||||
|
<view v-for="(item, index) in row.split('/')" :key="index" class="popup-table-row">
|
||||||
|
<text class="popup-td">{{ index+1 }}</text>
|
||||||
|
<text class="popup-td">{{ item.split('-')[0] }}</text>
|
||||||
|
<text class="popup-td">{{ item.split('-')[1] }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="row.split('/').length === 0" class="popup-empty">
|
||||||
|
<text class="popup-empty-text">暂无分段数据</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -120,14 +133,10 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
interface Project {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Student {
|
interface Student {
|
||||||
id: string
|
studentId : string
|
||||||
name : string
|
name : string
|
||||||
gender : string
|
gender : string
|
||||||
age : number
|
age : number
|
||||||
@@ -143,40 +152,24 @@
|
|||||||
segment170 : string
|
segment170 : string
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentYear = ref(new Date().getFullYear())
|
let row=ref('')
|
||||||
const currentMonth = ref(new Date().getMonth() + 1)
|
|
||||||
const selectedDate = ref('')
|
|
||||||
|
|
||||||
const projectList = ref<Project[]>([
|
// 项目选择
|
||||||
{ id: '1', name: '100米自由泳' },
|
let showProject = ref(false)
|
||||||
{ id: '2', name: '200米自由泳' },
|
let selectProcet = ref('')
|
||||||
{ id: '3', name: '50米蛙泳' },
|
let selectId = ref('')
|
||||||
{ id: '4', name: '100米蝶泳' }
|
|
||||||
])
|
|
||||||
|
|
||||||
const projectOptions = ref(projectList.value)
|
// 日期选择
|
||||||
const selectedProjectIndex = ref(-1)
|
const begin = ref<string>('')
|
||||||
const selectedProjectId = ref('')
|
const end = ref<string>('')
|
||||||
|
const showCalendar = ref(false)
|
||||||
|
|
||||||
const selectedProjectName = computed(() => {
|
const projectOptions = ref<Array<any>>([[]])
|
||||||
if (selectedProjectIndex.value === -1) {
|
|
||||||
return '请选择项目'
|
|
||||||
}
|
|
||||||
return projectList.value[selectedProjectIndex.value]?.name || '请选择项目'
|
|
||||||
})
|
|
||||||
|
|
||||||
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: '分段' }
|
|
||||||
])
|
|
||||||
|
|
||||||
const studentList = ref<Student[]>([])
|
const studentList = ref<Student[]>([])
|
||||||
const selectedStudentIndexes = ref<number[]>([])
|
const selectedStudentIndexes = ref<number[]>([])
|
||||||
const selectedStudentIds = ref<string[]>([])
|
const selectedStudentIds = ref<string[]>([])
|
||||||
const showStudentSelect = ref(false)
|
const showStudentPicker = ref(false)
|
||||||
|
|
||||||
const selectedStudentDisplay = computed(() => {
|
const selectedStudentDisplay = computed(() => {
|
||||||
if (selectedStudentIndexes.value.length === 0) {
|
if (selectedStudentIndexes.value.length === 0) {
|
||||||
@@ -187,110 +180,98 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let page = ref(1)
|
||||||
|
let status = ref('loadmore')
|
||||||
|
|
||||||
|
let pageTotal = ref(10)
|
||||||
|
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
label: '训练日期',
|
||||||
|
name: 'addTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '姓名',
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '总时长',
|
||||||
|
name: 'time',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '详细数据',
|
||||||
|
name: 'subsection',
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
const tableData = ref<TableDataItem[]>([])
|
const tableData = ref<TableDataItem[]>([])
|
||||||
|
|
||||||
const mockData: Record<string, Record<string, any[]>> = {
|
|
||||||
'1': {
|
|
||||||
'2026-03-12': [
|
|
||||||
{ name: '王小明', projectName: '100米自由泳', segment20: 0.037, segment70: 0.128, segment120: 0.219, segment170: 0.311 },
|
|
||||||
{ name: '张小红', projectName: '100米自由泳', segment20: 0.042, segment70: 0.145, segment120: 0.248, segment170: 0.352 },
|
|
||||||
{ name: '李小龙', projectName: '100米自由泳', segment20: 0.032, segment70: 0.112, segment120: 0.192, segment170: 0.272 }
|
|
||||||
],
|
|
||||||
'2026-03-14': [
|
|
||||||
{ name: '王小明', projectName: '100米自由泳', segment20: 0.035, segment70: 0.122, segment120: 0.209, segment170: 0.296 },
|
|
||||||
{ name: '张小红', projectName: '100米自由泳', segment20: 0.040, segment70: 0.138, segment120: 0.236, segment170: 0.334 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
'2026-03-16': [
|
|
||||||
{ name: '赵小芳', projectName: '200米自由泳', segment20: 0.038, segment70: 0.133, segment120: 0.228, segment170: 0.323 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
'2026-03-19': [
|
|
||||||
{ name: '陈小刚', projectName: '50米蛙泳', segment20: 0.044, segment70: 0.154, segment120: 0.264, segment170: 0.374 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
'2026-03-24': [
|
|
||||||
{ name: '周小丽', projectName: '100米蝶泳', segment20: 0.041, segment70: 0.143, segment120: 0.245, segment170: 0.347 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockStudents: Record<string, Student[]> = {
|
|
||||||
'1': [
|
|
||||||
{ id: 's1', name: '王小明', gender: '男', age: 12 },
|
|
||||||
{ id: 's2', name: '张小红', gender: '女', age: 11 },
|
|
||||||
{ id: 's3', name: '李小龙', gender: '男', age: 13 }
|
|
||||||
],
|
|
||||||
'2': [
|
|
||||||
{ id: 's4', name: '赵小芳', gender: '女', age: 10 }
|
|
||||||
],
|
|
||||||
'3': [
|
|
||||||
{ id: 's5', name: '陈小刚', gender: '男', age: 14 }
|
|
||||||
],
|
|
||||||
'4': [
|
|
||||||
{ id: 's6', name: '周小丽', gender: '女', age: 12 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
loadData()
|
getProjectData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadData = () => {
|
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 selectProcetFunc = (e : any) => {
|
||||||
const index = e.detail.value
|
selectProcet.value = e.value[0].name
|
||||||
selectedProjectIndex.value = index
|
selectId.value = e.value[0].planId
|
||||||
const selectedProject = projectList.value[index]
|
begin.value = ''
|
||||||
|
end.value = ''
|
||||||
if (selectedProject) {
|
|
||||||
selectedProjectId.value = selectedProject.id
|
|
||||||
selectedDate.value = ''
|
|
||||||
selectedStudentIndexes.value = []
|
selectedStudentIndexes.value = []
|
||||||
selectedStudentIds.value = []
|
selectedStudentIds.value = []
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
loadProjectStudents(selectedProject.id)
|
loadProjectStudents(selectId.value)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMonthSwitch = (e: any) => {
|
const openCalendar = () => {
|
||||||
currentYear.value = e.year
|
showCalendar.value = true
|
||||||
currentMonth.value = e.month
|
}
|
||||||
loadData()
|
|
||||||
selectedDate.value = ''
|
const calendarConfirm = (e : any) => {
|
||||||
|
begin.value = e[0]
|
||||||
|
end.value = e[e.length - 1]
|
||||||
|
showCalendar.value = false
|
||||||
selectedStudentIndexes.value = []
|
selectedStudentIndexes.value = []
|
||||||
selectedStudentIds.value = []
|
selectedStudentIds.value = []
|
||||||
tableData.value = []
|
tableData.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDateChange = (e: any) => {
|
const calendarClose = () => {
|
||||||
const date = e.fulldate
|
showCalendar.value = false
|
||||||
selectedDate.value = date
|
|
||||||
selectedStudentIndexes.value = []
|
|
||||||
selectedStudentIds.value = []
|
|
||||||
tableData.value = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadProjectStudents = (projectId : string) => {
|
const loadProjectStudents = (projectId : string) => {
|
||||||
const students = mockStudents[projectId] || []
|
PlanService.GetPlanInfo(projectId).then(res => {
|
||||||
studentList.value = students
|
if (res.code == 0) {
|
||||||
console.log(`项目 ${projectId} 的学生列表加载完成,共 ${students.length} 位学生`)
|
studentList.value = res.data.plan.users
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAllStudents = () => {
|
const selectAllStudents = () => {
|
||||||
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
selectedStudentIndexes.value = studentList.value.map((_, index) => index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleStudentSelect = (index: number) => {
|
const toggleStudentSelection = (index : number) => {
|
||||||
const idx = selectedStudentIndexes.value.indexOf(index)
|
const idx = selectedStudentIndexes.value.indexOf(index)
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
selectedStudentIndexes.value.splice(idx, 1)
|
selectedStudentIndexes.value.splice(idx, 1)
|
||||||
@@ -299,46 +280,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmStudentSelect = () => {
|
const confirmStudentSelection = () => {
|
||||||
selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].id)
|
selectedStudentIds.value = selectedStudentIndexes.value.map(index => studentList.value[index].studentId)
|
||||||
showStudentSelect.value = false
|
showStudentPicker.value = false
|
||||||
filterAndLoadData()
|
getRecord()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeStudentSelect = () => {
|
const closeStudentPicker = () => {
|
||||||
showStudentSelect.value = false
|
showStudentPicker.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterAndLoadData = () => {
|
const getRecord = () => {
|
||||||
if (!selectedDate.value || selectedStudentIndexes.value.length === 0) {
|
page.value = 1
|
||||||
tableData.value = []
|
getRecordList()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectData = mockData[selectedProjectId.value]
|
const getRecordList = () => {
|
||||||
if (!projectData) {
|
let studentIdList = selectedStudentIndexes.value.map(index => {
|
||||||
tableData.value = []
|
return studentList.value[index].studentId
|
||||||
return
|
})
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
planId: selectId.value,
|
||||||
|
studentId: JSON.stringify(studentIdList),
|
||||||
|
sTime: begin.value,
|
||||||
|
eTime: end.value,
|
||||||
|
page: page.value
|
||||||
|
}
|
||||||
|
PlanService.GetFenduanLog(data).then(res => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
pageTotal.value = res.data.pageTotal
|
||||||
|
tableData.value = res.data.list
|
||||||
|
} else {
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateData = projectData[selectedDate.value]
|
|
||||||
if (!dateData) {
|
|
||||||
tableData.value = []
|
const pageChange = (e) => {
|
||||||
return
|
page.value = e
|
||||||
|
getRecordList()
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedStudentNames = selectedStudentIndexes.value.map(index => studentList.value[index].name)
|
const showSegmentPopup = ref(false)
|
||||||
const filteredData = dateData.filter(item => selectedStudentNames.includes(item.name))
|
|
||||||
|
|
||||||
tableData.value = filteredData.map(item => ({
|
interface SegmentItem {
|
||||||
date: selectedDate.value,
|
index: number
|
||||||
name: item.name,
|
distance: string
|
||||||
projectName: item.projectName,
|
time: string
|
||||||
segment20: item.segment20.toFixed(3),
|
}
|
||||||
segment70: item.segment70.toFixed(3),
|
|
||||||
segment120: item.segment120.toFixed(3),
|
const segmentPopupData = ref<SegmentItem[]>([])
|
||||||
segment170: item.segment170.toFixed(3)
|
|
||||||
}))
|
const openSegmentPopup = () => {
|
||||||
|
showSegmentPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSegmentPopup = () => {
|
||||||
|
showSegmentPopup.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const cellClick = (rows: any) => {
|
||||||
|
row.value=rows.subsection
|
||||||
|
openSegmentPopup()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -374,6 +380,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.select-section {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@@ -407,7 +444,7 @@
|
|||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
border-color: #1890ff;
|
border-color: #faad14;
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,16 +455,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 日历组件包装 ==================== */
|
|
||||||
.calendar-wrapper {
|
|
||||||
background-color: #fff;
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== 学生选择器弹窗 ==================== */
|
|
||||||
.student-picker-modal {
|
.student-picker-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -455,7 +482,7 @@
|
|||||||
.header-title {
|
.header-title {
|
||||||
font-size: 36rpx;
|
font-size: 36rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -478,8 +505,8 @@
|
|||||||
|
|
||||||
&.primary {
|
&.primary {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
|
||||||
box-shadow: 0 4rpx 16rpx rgba(24, 144, 255, 0.3);
|
box-shadow: 0 4rpx 16rpx rgba(250, 173, 20, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -503,7 +530,7 @@
|
|||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
margin-top: 16rpx;
|
margin-top: 16rpx;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06), 0 0 0 1rpx rgba(0, 0, 0, 0.04) inset;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@@ -528,9 +555,9 @@
|
|||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
&.checked {
|
&.checked {
|
||||||
border-color: #1890ff;
|
border-color: #faad14;
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
box-shadow: 0 2rpx 8rpx rgba(250, 173, 20, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -553,7 +580,7 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
&.male {
|
&.male {
|
||||||
background: linear-gradient(135deg, #1890ffkt 0%, #40a9ff 50%, #096dd9 100%);
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.female {
|
&.female {
|
||||||
@@ -751,4 +778,79 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.segment-popup-content {
|
||||||
|
width: 620rpx;
|
||||||
|
max-height: 70vh;
|
||||||
|
padding: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table {
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.popup-table-header {
|
||||||
|
display: flex;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
padding: 24rpx 0;
|
||||||
|
|
||||||
|
.popup-th {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-scroll {
|
||||||
|
max-height: 400rpx;
|
||||||
|
|
||||||
|
.popup-table-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-td {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-empty {
|
||||||
|
padding: 60rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.popup-empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="timing-container">
|
<view class="baogan-container">
|
||||||
<!-- 页面标题区域 -->
|
<!-- 页面标题区域 -->
|
||||||
<view class="header-section">
|
<view class="header-section">
|
||||||
<view class="header-title">
|
<view class="header-title">
|
||||||
@@ -8,57 +8,38 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日历组件 -->
|
|
||||||
<view class="calendar-wrapper">
|
|
||||||
<uni-calendar
|
<!-- 日期选择 -->
|
||||||
:insert="true"
|
<view class="date-filter" style="margin: 20rpx 20rpx 0;">
|
||||||
:lunar="false"
|
<view class="date-picker" @click="openCalendar()">
|
||||||
:show-month="true"
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
:selected="selectedDates"
|
<view class="date-text">
|
||||||
@monthSwitch="handleMonthSwitch"
|
<text class="label">开始日期</text>
|
||||||
@change="handleDateChange">
|
<text class="value">{{ begin || '请选择' }}</text>
|
||||||
</uni-calendar>
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="date-picker" @click="openCalendar()">
|
||||||
|
<up-icon name="calendar" color="#3B82F6" size="25"></up-icon>
|
||||||
|
<view class="date-text">
|
||||||
|
<text class="label">结束日期</text>
|
||||||
|
<text class="value">{{ end || '请选择' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 选中日期数据统计 -->
|
|
||||||
<!-- 仅在选择了日期时显示统计信息 -->
|
|
||||||
<view class="date-summary" v-if="selectedDate">
|
|
||||||
<view class="summary-item">
|
|
||||||
<text class="summary-label">选中日期</text>
|
|
||||||
<text class="summary-value">{{ selectedDate }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="summary-divider"></view>
|
|
||||||
<view class="summary-item">
|
|
||||||
<text class="summary-label">训练人数</text>
|
|
||||||
<text class="summary-value">{{ tableData.length }}人</text>
|
|
||||||
</view>
|
|
||||||
<view class="summary-divider"></view>
|
|
||||||
<view class="summary-item">
|
|
||||||
<text class="summary-label">平均速度</text>
|
|
||||||
<text class="summary-value">{{ averageSpeed }}m/s</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<!-- 仅在选择了日期时显示表格 -->
|
<view v-if="tableData.length>0" class="table-section">
|
||||||
<view class="table-section" v-if="selectedDate">
|
<next-table :show-header="true" :columns="columns" :stripe="true" :fit="false" :show-summary='false'
|
||||||
<sl-table
|
:data="tableData" :showPaging='true' :pageIndex="page" @pageChange="pageChange"
|
||||||
:columns="columns"
|
:pageTotal="pageTotal"></next-table>
|
||||||
:tableData="tableData"
|
|
||||||
@cell-click="handleCellClick">
|
|
||||||
<!-- 空数据插槽 -->
|
|
||||||
<template #empty>
|
|
||||||
<view class="empty-container">
|
|
||||||
<view class="empty-icon">
|
|
||||||
<u-icon name="file-text" size="80" color="#ccc"></u-icon>
|
|
||||||
</view>
|
|
||||||
<text class="empty-text">该日期暂无训练数据</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</sl-table>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历弹窗 -->
|
||||||
|
<up-calendar :show="showCalendar" mode="date" minDate='1776240407000' @confirm="calendarConfirm"
|
||||||
|
@close="calendarClose">
|
||||||
|
</up-calendar>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -66,250 +47,143 @@
|
|||||||
import { onShow, onLoad } from "@dcloudio/uni-app"
|
import { onShow, onLoad } from "@dcloudio/uni-app"
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { ref, computed } from 'vue'
|
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())
|
const currentYear = ref(new Date().getFullYear())
|
||||||
|
|
||||||
// 当前月份(1-12),用于月份切换时记录当前月份
|
|
||||||
const currentMonth = ref(new Date().getMonth() + 1)
|
const currentMonth = ref(new Date().getMonth() + 1)
|
||||||
|
|
||||||
// 当前选中的日期,格式为 'YYYY-MM-DD'
|
|
||||||
// 空字符串表示未选择日期
|
|
||||||
const selectedDate = ref('')
|
const selectedDate = ref('')
|
||||||
|
|
||||||
// ==================== 表格配置 ====================
|
// 项目选择
|
||||||
|
let showProject = ref(false)
|
||||||
|
let selectProcet = ref('')
|
||||||
|
let selectId = ref('')
|
||||||
|
|
||||||
|
// 日期选择
|
||||||
|
const begin = ref<string>('')
|
||||||
|
const end = ref<string>('')
|
||||||
|
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([
|
const columns = ref([
|
||||||
{
|
{
|
||||||
label: '姓名',
|
label: '姓名',
|
||||||
prop: 'name',
|
name: 'studentName',
|
||||||
width: '100px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '项目名称',
|
label: '项目名称',
|
||||||
prop: 'projectName',
|
name: 'planName',
|
||||||
width: '150px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '最大速度',
|
label: '最快速度',
|
||||||
prop: 'maxSpeed',
|
name: 'quicklyTime',
|
||||||
width: '120px'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '计时数据',
|
label: '详细数据',
|
||||||
prop: 'timingData',
|
name: 'time',
|
||||||
width: '150px'
|
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<TableDataItem[]>([])
|
const tableData = ref<TableDataItem[]>([])
|
||||||
|
|
||||||
// ==================== 响应式数据 - 日历标记 ====================
|
|
||||||
|
|
||||||
// 日历打点数据
|
|
||||||
// 用于在日历上标记有训练记录的日期
|
|
||||||
// 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<string, TableDataItem[]> = {
|
|
||||||
'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(() => {
|
onLoad(() => {
|
||||||
// 加载当前月份的数据
|
|
||||||
loadData()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 页面显示时触发
|
|
||||||
// 每次页面从后台切换到前台时执行
|
|
||||||
// 可用于刷新页面显示的数据
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
// TODO: 如果需要在页面显示时刷新数据,可以在这里添加逻辑
|
|
||||||
// 例如:调用接口获取最新数据
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 业务逻辑方法 ====================
|
const openCalendar = () => {
|
||||||
|
showCalendar.value = true
|
||||||
/**
|
|
||||||
* 加载数据
|
|
||||||
* 调用后端 API 获取数据
|
|
||||||
* 当前为模拟数据,实际开发中应替换为真实 API 调用
|
|
||||||
*/
|
|
||||||
const loadData = () => {
|
|
||||||
// TODO: 调用后端 API 获取数据
|
|
||||||
// 示例代码:
|
|
||||||
// Service.Request('/api/timing/data', 'GET', {
|
|
||||||
// year: currentYear.value,
|
|
||||||
// month: currentMonth.value
|
|
||||||
// }).then(res => {
|
|
||||||
// // 处理返回的数据
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const calendarConfirm = (e : any) => {
|
||||||
* 处理表格单元格点击事件
|
begin.value = e[0]
|
||||||
* @param event 点击事件对象,包含行索引、列索引、单元格数据等信息
|
end.value = e[e.length - 1]
|
||||||
*/
|
showCalendar.value = false
|
||||||
const handleCellClick = (event: any) => {
|
getRecord()
|
||||||
// 在控制台输出点击事件信息,用于调试
|
|
||||||
console.log('表格单元格点击事件:', event)
|
|
||||||
|
|
||||||
// TODO: 根据业务需求处理单元格点击
|
|
||||||
// 例如:点击某行可以查看详细信息,点击某列可以进行排序等
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const calendarClose = () => {
|
||||||
* 处理日历月份切换事件
|
showCalendar.value = false
|
||||||
* 当用户切换日历的月份时触发
|
|
||||||
* @param e 切换事件对象,包含 year 和 month 属性
|
|
||||||
*/
|
|
||||||
const handleMonthSwitch = (e: any) => {
|
|
||||||
// 更新当前年份
|
|
||||||
currentYear.value = e.year
|
|
||||||
|
|
||||||
// 更新当前月份
|
|
||||||
currentMonth.value = e.month
|
|
||||||
|
|
||||||
// 重新加载数据
|
|
||||||
// 获取切换后月份的训练记录和日历标记
|
|
||||||
loadData()
|
|
||||||
|
|
||||||
// 清空选中的日期
|
|
||||||
// 因为切换了月份,之前选择的日期可能不再显示
|
|
||||||
selectedDate.value = ''
|
|
||||||
|
|
||||||
// 清空表格数据
|
|
||||||
tableData.value = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理日历日期选择事件
|
|
||||||
* 当用户点击日历上的某个日期时触发
|
|
||||||
* @param e 选择事件对象,fulldate 属性包含完整的日期字符串
|
|
||||||
*/
|
|
||||||
const handleDateChange = (e: any) => {
|
|
||||||
// 获取选择的完整日期,格式为 'YYYY-MM-DD'
|
|
||||||
const date = e.fulldate
|
|
||||||
|
|
||||||
// 更新选中的日期
|
|
||||||
selectedDate.value = date
|
|
||||||
|
|
||||||
// 根据选择的日期加载对应的训练数据
|
|
||||||
// 检查模拟数据中是否存在该日期的数据
|
const getRecord = () => {
|
||||||
if (mockData[date]) {
|
page.value = 1
|
||||||
// 如果存在,加载数据
|
getRecordList()
|
||||||
tableData.value = mockData[date]
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
// 如果不存在,清空数据(表格将显示空状态)
|
Service.Msg(res.msg)
|
||||||
tableData.value = []
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 实际项目中应该调用 API 获取该日期的计时数据
|
|
||||||
// 示例代码:
|
const pageChange = (e) => {
|
||||||
// Service.Request('/api/timing/detail', 'GET', {
|
page.value = e
|
||||||
// date: date
|
getRecordList()
|
||||||
// }).then(res => {
|
|
||||||
// // 处理返回的数据并更新表格
|
|
||||||
// tableData.value = res.data
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 页面背景色设置
|
|
||||||
page {
|
page {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面容器
|
.baogan-container {
|
||||||
.timing-container {
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 页面标题区域 ==================== */
|
|
||||||
.header-section {
|
.header-section {
|
||||||
background-color: #fff;
|
|
||||||
padding: 32rpx 28rpx 24rpx;
|
padding: 32rpx 28rpx 24rpx;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
@@ -329,29 +203,304 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 日历组件包装 ==================== */
|
.date-filter {
|
||||||
.calendar-wrapper {
|
display: flex;
|
||||||
|
gap: 24rpx;
|
||||||
|
|
||||||
|
.date-picker {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin-bottom: 20rpx;
|
border-radius: 16rpx;
|
||||||
padding: 20rpx 0;
|
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: 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;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
border: 1rpx solid #e8e8e8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #faad14;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-picker-modal {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 70vh;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
z-index: 999;
|
||||||
|
animation: slideUp 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.12);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 36rpx 32rpx 28rpx;
|
||||||
|
background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
||||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
padding: 14rpx 28rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #f5f5f5 0%, #f0f0f0 100%);
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 50%, #d48806 100%);
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(250, 173, 20, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.student-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 20rpx 40rpx;
|
||||||
|
|
||||||
|
.student-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
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;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-checkbox {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.checkbox-inner {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
border: 2rpx solid #d9d9d9;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
border-color: #faad14;
|
||||||
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(250, 173, 20, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.avatar-circle {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&.male {
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 50%, #096dd9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.female {
|
||||||
|
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 50%, #d46b08 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 8rpx;
|
||||||
|
right: 8rpx;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-text {
|
||||||
|
font-size: 34rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
|
||||||
|
.gender-badge {
|
||||||
|
font-size: 22rpx;
|
||||||
|
padding: 6rpx 14rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.male {
|
||||||
|
color: #1890ff;
|
||||||
|
background: linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(64, 169, 255, 0.05) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.female {
|
||||||
|
color: #fa8c16;
|
||||||
|
background: linear-gradient(135deg, rgba(250, 140, 22, 0.1) 0%, rgba(255, 169, 64, 0.05) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.4) 100%);
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
z-index: 998;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 日期数据统计卡片 ==================== */
|
|
||||||
.date-summary {
|
.date-summary {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin: 0 20rpx 20rpx;
|
margin: 0 20rpx 20rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
// 统计项
|
|
||||||
.summary-item {
|
.summary-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
// 标签文字
|
|
||||||
.summary-label {
|
.summary-label {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -359,15 +508,13 @@
|
|||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数值文字
|
|
||||||
.summary-value {
|
.summary-value {
|
||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1890ff;
|
color: #faad14;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分隔线
|
|
||||||
.summary-divider {
|
.summary-divider {
|
||||||
width: 1rpx;
|
width: 1rpx;
|
||||||
height: 50rpx;
|
height: 50rpx;
|
||||||
@@ -375,16 +522,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 表格区域 ==================== */
|
|
||||||
.table-section {
|
.table-section {
|
||||||
margin: 0 20rpx;
|
margin: 20rpx 20rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 空状态容器 ==================== */
|
|
||||||
.empty-container {
|
.empty-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -392,72 +537,79 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 80rpx 40rpx;
|
padding: 80rpx 40rpx;
|
||||||
|
|
||||||
// 空状态图标
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 空状态文字
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 提示状态容器 ==================== */
|
|
||||||
// 用于在用户未选择日期时显示提示
|
|
||||||
.hint-state {
|
.hint-state {
|
||||||
margin: 0 20rpx;
|
margin: 0 20rpx;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
padding: 80rpx 40rpx;
|
padding: 80rpx 40rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
// 提示图标
|
|
||||||
.hint-icon {
|
.hint-icon {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提示文字
|
|
||||||
.hint-text {
|
.hint-text {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== sl-table 样式覆盖 ==================== */
|
|
||||||
// 表格容器
|
|
||||||
::v-deep .sl-table {
|
::v-deep .sl-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表头背景色 - 使用蓝色渐变(计时功能的主色调)
|
|
||||||
::v-deep .sl-table__header {
|
::v-deep .sl-table__header {
|
||||||
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%) !important;
|
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表头单元格样式
|
|
||||||
::v-deep .sl-table__header__cell {
|
::v-deep .sl-table__header__cell {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格单元格样式
|
|
||||||
::v-deep .sl-table__body__cell {
|
::v-deep .sl-table__body__cell {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
padding: 24rpx 16rpx;
|
padding: 24rpx 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 偶数行背景色
|
|
||||||
::v-deep .sl-table__body__row:nth-child(even) {
|
::v-deep .sl-table__body__row:nth-child(even) {
|
||||||
background-color: #fafafa !important;
|
background-color: #fafafa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 奇数行背景色
|
|
||||||
::v-deep .sl-table__body__row:nth-child(odd) {
|
::v-deep .sl-table__body__row:nth-child(odd) {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<!-- 项目列表 -->
|
<!-- 项目列表 -->
|
||||||
<view class="project-list">
|
<view class="project-list">
|
||||||
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
<text class="list-title" v-if="projects.length > 0">项目列表</text>
|
||||||
<view v-if="projects.length > 0" class="list-container">
|
<scroll-view v-if="projects.length > 0" scroll-y="true" @scrolltolower='getList()' class="list-container">
|
||||||
<view v-for="project in projects" :key="project.planId" class="project-item"
|
<view v-for="project in projects" :key="project.planId" class="project-item"
|
||||||
@click="handleProjectClick(project)">
|
@click="handleProjectClick(project)">
|
||||||
<view class="item-icon">
|
<view class="item-icon">
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<text v-if="currentIndex!==3" class="item-count">{{ project.users.length }}人</text>
|
<text v-if="currentIndex!==3" class="item-count">{{ project.users.length }}人</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</scroll-view>
|
||||||
<view v-else class="empty-project">
|
<view v-else class="empty-project">
|
||||||
<text class="empty-text">暂无项目</text>
|
<text class="empty-text">暂无项目</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { Service } from '@/Service/Service'
|
import { Service } from '@/Service/Service'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||||
import { loginService } from '@/Service/swimming/loginService'
|
import { loginService } from '@/Service/swimming/loginService'
|
||||||
import { PlanService } from '@/Service/swimming/PlanService'
|
import { PlanService } from '@/Service/swimming/PlanService'
|
||||||
|
|
||||||
@@ -149,6 +149,10 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onShow(()=>{
|
||||||
|
uni.showTabBar()
|
||||||
|
})
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
uni.getProvider({
|
uni.getProvider({
|
||||||
service: 'oauth',
|
service: 'oauth',
|
||||||
@@ -185,6 +189,7 @@
|
|||||||
|
|
||||||
// 获取项目列表
|
// 获取项目列表
|
||||||
const getList = () => {
|
const getList = () => {
|
||||||
|
|
||||||
if (status.value == 'loading' || status.value == 'nomore') {
|
if (status.value == 'loading' || status.value == 'nomore') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -652,6 +657,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
|
max-height: 240rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-item {
|
.project-item {
|
||||||
@@ -660,7 +666,7 @@
|
|||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
padding: 28rpx 24rpx;
|
padding: 28rpx 24rpx;
|
||||||
background: linear-gradient(135deg, #fff 0%, #fafbfc 100%);
|
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);
|
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;
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06), 0 0 0 1rpx rgba(0, 0, 0, 0.04) inset;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -351,6 +351,13 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
Service.Msg(`${student.name} 第${student.segments.length}段已记录`)
|
||||||
|
|
||||||
|
// 所有学生分段记录完成后自动停止计时
|
||||||
|
const allCompleted = students.value.every(s => s.segments.length >= maxSeg)
|
||||||
|
if (allCompleted) {
|
||||||
|
stopTimer()
|
||||||
|
Service.Msg('所有学生记录完成')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置学生
|
// 重置学生
|
||||||
@@ -382,13 +389,41 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(students.value);
|
||||||
|
|
||||||
// 这里可以添加保存到后端或本地存储的逻辑
|
// 这里可以添加保存到后端或本地存储的逻辑
|
||||||
Service.Msg('保存成功', 'success')
|
Service.Msg('保存成功', 'success')
|
||||||
|
|
||||||
// 保存后可以选择返回上一页
|
let data = [
|
||||||
setTimeout(() => {
|
{
|
||||||
Service.GoPageBack()
|
studentId: "",
|
||||||
}, 1000)
|
studentName: "",
|
||||||
|
data: [{ circle: 0, time: "" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
data = []
|
||||||
|
students.value.map((item : any) => {
|
||||||
|
let record = item.segments.map((content : any, index : any) => {
|
||||||
|
return {
|
||||||
|
circle: (index + 1)*segmentDistance.value,
|
||||||
|
time: content.time
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
studentId: item.id,
|
||||||
|
studentName: item.name,
|
||||||
|
data: record
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
PlanService.AddPlanLog(planId.value,'分段项目','',JSON.stringify(data),'').then(res=>{
|
||||||
|
if(res.code==0){
|
||||||
|
Service.Msg('提交成功!')
|
||||||
|
}else{
|
||||||
|
Service.Msg(res.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示学生记录弹窗
|
// 显示学生记录弹窗
|
||||||
|
|||||||
@@ -41,7 +41,8 @@
|
|||||||
<text class="athlete-name">{{ athlete.name }}</text>
|
<text class="athlete-name">{{ athlete.name }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="time-wrapper">
|
<view class="time-wrapper">
|
||||||
<text class="athlete-time">{{ !athlete.time? ' 00:00:00 ':formatTime(athlete.time) }}</text>
|
<text
|
||||||
|
class="athlete-time">{{ !athlete.time? ' 00:00:00 ':formatTime(athlete.time) }}</text>
|
||||||
<text class="best-time">最快:
|
<text class="best-time">最快:
|
||||||
{{ athlete.bestTime !== null ? formatTime(athlete.quicklyTime) : '无' }}</text>
|
{{ athlete.bestTime !== null ? formatTime(athlete.quicklyTime) : '无' }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -58,6 +59,34 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="" style="width: 100%; height: 100rpx;"></view>
|
<view class="" style="width: 100%; height: 100rpx;"></view>
|
||||||
|
|
||||||
|
<!-- 右下角悬浮提交按钮 -->
|
||||||
|
<view class="fab-submit" @click="submitData">
|
||||||
|
<text class="fab-text">提交</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 记录提示弹窗 -->
|
||||||
|
<u-popup :show="showRecord" mode="center" :round="20" :closeable="true" @close="showRecord=false" closeOnClickOverlay>
|
||||||
|
<view class="record-notice-modal">
|
||||||
|
<view class="notice-header">
|
||||||
|
<text class="notice-title">恭喜以下学生打破记录</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-table">
|
||||||
|
<view class="table-header">
|
||||||
|
<text class="header-cell index-cell">序号</text>
|
||||||
|
<text class="header-cell name-cell">学生姓名</text>
|
||||||
|
<text class="header-cell time-cell">记录时间</text>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="table-body">
|
||||||
|
<view v-for="(item,index) in record" class="table-row">
|
||||||
|
<text class="row-cell index-cell">{{ index+1 }}</text>
|
||||||
|
<text class="row-cell name-cell">{{ item.name }}</text>
|
||||||
|
<text class="row-cell time-cell">{{ formatTime(item.quicklyTime) }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
|
||||||
<!-- 底部控制按钮 -->
|
<!-- 底部控制按钮 -->
|
||||||
<view class="control-buttons">
|
<view class="control-buttons">
|
||||||
<view class="button-group">
|
<view class="button-group">
|
||||||
@@ -81,7 +110,10 @@
|
|||||||
<text class="btn-text">暂停</text>
|
<text class="btn-text">暂停</text>
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -123,6 +155,8 @@
|
|||||||
const groupSize = ref('0')
|
const groupSize = ref('0')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 计时器状态
|
// 计时器状态
|
||||||
const isRunning = ref(false)
|
const isRunning = ref(false)
|
||||||
const currentTime = ref(0)
|
const currentTime = ref(0)
|
||||||
@@ -136,7 +170,8 @@
|
|||||||
const stopwatchMode = ref<'interval' | 'together'>('interval')
|
const stopwatchMode = ref<'interval' | 'together'>('interval')
|
||||||
// 间隔时间(秒)
|
// 间隔时间(秒)
|
||||||
const intervalTime = ref('0')
|
const intervalTime = ref('0')
|
||||||
|
let record=ref<Array<any>>([])
|
||||||
|
let showRecord=ref(false)
|
||||||
onLoad((data : any) => {
|
onLoad((data : any) => {
|
||||||
planId.value = data.id
|
planId.value = data.id
|
||||||
|
|
||||||
@@ -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 {
|
.athletes-section {
|
||||||
margin-top: 20rpx;
|
margin-top: 80rpx;
|
||||||
margin-bottom: 120rpx;
|
margin-bottom: 120rpx;
|
||||||
|
|
||||||
.section-header {
|
.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 {
|
.control-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -597,7 +706,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, #ffffff 100%);
|
||||||
padding: 32rpx 40rpx;
|
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);
|
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
|
||||||
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
@@ -611,7 +720,8 @@
|
|||||||
.start-btn,
|
.start-btn,
|
||||||
.pause-btn,
|
.pause-btn,
|
||||||
.reset-all-btn,
|
.reset-all-btn,
|
||||||
.record-btn {
|
.record-btn,
|
||||||
|
.submit-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100rpx;
|
height: 100rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
@@ -719,4 +829,84 @@
|
|||||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
48
src/uni_modules/next-table/changelog.md
Normal file
48
src/uni_modules/next-table/changelog.md
Normal file
@@ -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
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef VUE3 -->
|
||||||
|
<view class="uni-table-checkbox" @click.stop="selected" :style="`--primaryColor: ${primaryColor}`">
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef VUE2 -->
|
||||||
|
<view class="uni-table-checkbox" @click.stop="selected" :style="{'--primaryColor': primaryColor}">
|
||||||
|
<!-- #endif -->
|
||||||
|
<view v-if="!indeterminate" class="checkbox__inner" :class="{'is-checked':isChecked,'is-disable':isDisabled}">
|
||||||
|
<view class="checkbox__inner-icon"></view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="checkbox__inner checkbox--indeterminate">
|
||||||
|
<view class="checkbox__inner-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TableCheckbox',
|
||||||
|
emits:['checkboxSelected'],
|
||||||
|
props: {
|
||||||
|
indeterminate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
checked: {
|
||||||
|
type: [Boolean,String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
},
|
||||||
|
cellData: {
|
||||||
|
type: Object,
|
||||||
|
default () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
primaryColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#f0ad4e'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
checked(newVal){
|
||||||
|
if(typeof this.checked === 'boolean'){
|
||||||
|
this.isChecked = newVal
|
||||||
|
}else{
|
||||||
|
this.isChecked = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indeterminate(newVal){
|
||||||
|
this.isIndeterminate = newVal
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isChecked: false,
|
||||||
|
isDisabled: false,
|
||||||
|
isIndeterminate:false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if(typeof this.checked === 'boolean'){
|
||||||
|
this.isChecked = this.checked
|
||||||
|
}
|
||||||
|
this.isDisabled = this.disabled
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selected() {
|
||||||
|
if (this.isDisabled) return
|
||||||
|
this.isIndeterminate = false
|
||||||
|
this.isChecked = !this.isChecked
|
||||||
|
this.$emit('checkboxSelected', {
|
||||||
|
checked: this.isChecked,
|
||||||
|
data: this.cellData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$border-color: #DCDFE6;
|
||||||
|
$disable:0.4;
|
||||||
|
|
||||||
|
.uni-table-checkbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
margin: 5px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// 多选样式
|
||||||
|
.checkbox__inner {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
position: absolute;
|
||||||
|
/* #ifdef APP-NVUE */
|
||||||
|
top: 2px;
|
||||||
|
/* #endif */
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
top: 2px;
|
||||||
|
/* #endif */
|
||||||
|
left: 5px;
|
||||||
|
height: 7px;
|
||||||
|
width: 3px;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-left: 0;
|
||||||
|
border-top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checkbox--indeterminate {
|
||||||
|
border-color: var(--primaryColor);
|
||||||
|
background-color: var(--primaryColor);
|
||||||
|
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
height: 2px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0;
|
||||||
|
width: auto;
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
transform: scale(0.5);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover{
|
||||||
|
border-color: var(--primaryColor);
|
||||||
|
}
|
||||||
|
// 禁用
|
||||||
|
&.is-disable {
|
||||||
|
/* #ifdef H5 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* #endif */
|
||||||
|
background-color: #F2F6FC;
|
||||||
|
border-color: $border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
&.is-checked {
|
||||||
|
border-color: var(--primaryColor);
|
||||||
|
background-color: var(--primaryColor);
|
||||||
|
|
||||||
|
.checkbox__inner-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中禁用
|
||||||
|
&.is-disable {
|
||||||
|
opacity: $disable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="table-empty">
|
||||||
|
<text>{{text}}</text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.table-empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<view class="table-h5-footer top-header-uni" :style="{paddingRight:`${scrollbarSize}px`}">
|
||||||
|
<scroll-view
|
||||||
|
class="next-table-headers"
|
||||||
|
@scroll="handleFooterTableScrollLeft"
|
||||||
|
scroll-x="true"
|
||||||
|
scroll-y="false"
|
||||||
|
id="tableFooterHeaders"
|
||||||
|
scroll-anchoring="true"
|
||||||
|
:scroll-left="headerFooterTableLeft"
|
||||||
|
style="padding-bottom: 0px;
|
||||||
|
background: #fafafa;height: 100%">
|
||||||
|
<view class="next-table-fixed" >
|
||||||
|
<view class="next-table-thead" style="position: relative;" >
|
||||||
|
<view class="item-tr">
|
||||||
|
<view
|
||||||
|
class="item-th"
|
||||||
|
:style="{
|
||||||
|
width:`${item.width?item.width:'100'}px`,
|
||||||
|
flex:index===transColumns.length-1?1:'none',
|
||||||
|
minWidth:`${item.width?item.width:'100'}px`,
|
||||||
|
borderRight:`${border?'1px solid #e8e8e8':''}`,
|
||||||
|
borderTop:`${border?'1px solid #e8e8e8':''}`,
|
||||||
|
textAlign:item.align||'left'
|
||||||
|
}"
|
||||||
|
v-for="(item,index) in transColumns" :key="index">
|
||||||
|
{{ sums[index] }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import summary from '../js/summary.js'
|
||||||
|
export default {
|
||||||
|
name:'table-footer',
|
||||||
|
mixins:[summary],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.table-h5-footer {
|
||||||
|
background: #fafafa;
|
||||||
|
/*每个页面公共css */
|
||||||
|
scroll-view ::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
//第二种
|
||||||
|
::-webkit-scrollbar{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.item-tr{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.item-th{
|
||||||
|
padding-left: 8px;
|
||||||
|
line-height: 39px;
|
||||||
|
height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
padding-right: 20px;
|
||||||
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template >
|
||||||
|
<view class="table-load-more">
|
||||||
|
<image :src="base64Flower" style="" class="loading-custom-image"></image>
|
||||||
|
<text>正在加载中...</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
const base64Flower = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC';
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
base64Flower
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.table-load-more {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
height: 40px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.loading-custom-image{
|
||||||
|
color: #a4a4a4;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
animation: loading-circle 1s linear infinite;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
@keyframes loading-circle {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<!-- #ifdef VUE3 -->
|
||||||
|
<view class="table-paging" :style="`--primaryColor: ${primaryColor}`">
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef VUE2 -->
|
||||||
|
<view class="table-paging" :style="{'--primaryColor': primaryColor}">
|
||||||
|
<!-- #endif -->
|
||||||
|
<view
|
||||||
|
:class="{
|
||||||
|
'table-paging-item': true,
|
||||||
|
turner: true,
|
||||||
|
disabled: pageIndex <= 1,
|
||||||
|
}"
|
||||||
|
@click="prePage"
|
||||||
|
>
|
||||||
|
<text>上一页</text>
|
||||||
|
</view>
|
||||||
|
<view class="table-paging-item page">
|
||||||
|
<text class="table-paging-current">{{ pageIndex || "1" }}</text>
|
||||||
|
<text class="table-paging-gap">/</text>
|
||||||
|
<text>{{ pageTotal || "0" }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
:class="{
|
||||||
|
'table-paging-item': true,
|
||||||
|
turner: true,
|
||||||
|
disabled: pageIndex >= pageTotal,
|
||||||
|
}"
|
||||||
|
@click="nextPage"
|
||||||
|
>
|
||||||
|
|
||||||
|
<text>下一页</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
emits:['change'],
|
||||||
|
props: {
|
||||||
|
pageIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
pageTotal: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
primaryColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#f0ad4e'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 上一页
|
||||||
|
prePage() {
|
||||||
|
if (this.pageIndex <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit("change", this.pageIndex - 1);
|
||||||
|
},
|
||||||
|
// 下一页
|
||||||
|
nextPage() {
|
||||||
|
if (this.pageIndex >= this.pageTotal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit("change", this.pageIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$primary-color: var(--primaryColor);
|
||||||
|
|
||||||
|
.table-paging {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $primary-color;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.table-paging-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&.page {
|
||||||
|
color: #999999ff;
|
||||||
|
}
|
||||||
|
&.turner {
|
||||||
|
padding: 0 5px;
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-paging-current {
|
||||||
|
color: $primary-color;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.table-paging-gap {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<view class="next-table-header" style="display: flex;" >
|
||||||
|
<view class="item-tr" >
|
||||||
|
<view class='item-td'
|
||||||
|
:style="{
|
||||||
|
width:`${item.width?item.width:'100'}px`,
|
||||||
|
borderRight:`${border?'1px solid #e8e8e8':''}`,
|
||||||
|
textAlign:item.align||'left'
|
||||||
|
}"
|
||||||
|
:key="`15255966555${index}`"
|
||||||
|
v-for="(item,index) in fixedLeftColumns">
|
||||||
|
<template >
|
||||||
|
{{sums[index]}}
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import summary from '../js/summary.js'
|
||||||
|
export default {
|
||||||
|
mixins:[summary]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.next-table-header {
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fafafa;
|
||||||
|
.item-th{
|
||||||
|
padding-left: 8px;
|
||||||
|
line-height: 39px;
|
||||||
|
height: 40px;
|
||||||
|
//display: flex;
|
||||||
|
//align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-tr{
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.item-td{
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
padding-left: 8px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
padding-right: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
background: rgb(250, 250, 250);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<view class="next-table-footer" style="height: 40px;">
|
||||||
|
<view class="next-table-fixed" >
|
||||||
|
<view class="next-table-thead" style="position: relative;" >
|
||||||
|
<view class="item-tr">
|
||||||
|
<view
|
||||||
|
:class="['item-th',index <fixedLeftColumns.length&&'zb-stick-side']"
|
||||||
|
:style="{
|
||||||
|
left:`${item.left}px`,
|
||||||
|
width:`${item.width?item.width:'100'}px`,
|
||||||
|
flex:index===transColumns.length-1?1:'none',
|
||||||
|
minWidth:`${item.width?item.width:'100'}px`,
|
||||||
|
borderRight:`${border?'1px solid #e8e8e8':''}`,
|
||||||
|
borderTop:`${border?'1px solid #e8e8e8':''}`,
|
||||||
|
textAlign:item.align||'left'
|
||||||
|
}"
|
||||||
|
v-for="(item,index) in transColumns" :key="index">
|
||||||
|
<template>
|
||||||
|
{{ sums[index]||item.emptyString }}
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import summary from '../js/summary.js'
|
||||||
|
export default {
|
||||||
|
mixins:[summary]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.next-table-footer {
|
||||||
|
background: #fafafa;
|
||||||
|
width: fit-content;
|
||||||
|
min-width: 100%;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
.item-tr{
|
||||||
|
display: flex;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
.item-th{
|
||||||
|
padding-left: 8px;
|
||||||
|
line-height: 39px;
|
||||||
|
height: 40px;
|
||||||
|
//display: flex;
|
||||||
|
//align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
padding-right: 20px;
|
||||||
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
.next-table-fixed{
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
.zb-stick-side{
|
||||||
|
position: sticky;
|
||||||
|
bottom:0 ;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
//border-right: solid 1rpx #dbdbdb;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #fafafa;
|
||||||
|
//box-shadow: 6px 0 6px -4px #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
71
src/uni_modules/next-table/components/next-table/js/props.js
Normal file
71
src/uni_modules/next-table/components/next-table/js/props.js
Normal file
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/uni_modules/next-table/components/next-table/js/util.js
Normal file
91
src/uni_modules/next-table/components/next-table/js/util.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1320
src/uni_modules/next-table/components/next-table/next-table.vue
Normal file
1320
src/uni_modules/next-table/components/next-table/next-table.vue
Normal file
File diff suppressed because it is too large
Load Diff
98
src/uni_modules/next-table/package.json
Normal file
98
src/uni_modules/next-table/package.json
Normal file
@@ -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": "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
509
src/uni_modules/next-table/readme.md
Normal file
509
src/uni_modules/next-table/readme.md
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
|
||||||
|
## 介绍
|
||||||
|
基于uni-app开发的一个普通的表格组件,功能有固定首列和表头、排序、操作按钮、
|
||||||
|
table 表格 固定表头、固定首列、多列 上拉加载更多、 排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计 多页功能
|
||||||
|
已用于生产环境
|
||||||
|
|
||||||
|
> 遇到问题或有建议可以加入QQ群(<font color=#f00>455948571</font>)反馈
|
||||||
|
> 如果觉得组件不错,<font color=#f00>给五星鼓励鼓励</font>咯!
|
||||||
|
|
||||||
|
|
||||||
|
## 参考依赖
|
||||||
|
本组件居于github开源项目[zb-table](https://github.com/zouzhibin/zb-table#readme)进行二开,功能会有些差异和增强,如果有需要原版请参考源开源项目。感谢作者
|
||||||
|
|
||||||
|
## 注意本插件依赖于scss的编译,如果没有使用scss请手动改源码去掉scss的语法方可使用。如果有疑问请加入加入QQ群(<font color=#f00>455948571</font>)
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
### 作者不介意你对组件源码进行改造使用,为了开源更加高效,谢谢你的配合;为了节省不必要的沟通浪费,以下情况请不要再反馈给作者,请自行解决;
|
||||||
|
### 在这感各位的理解,我支持开源,但是作者时间有限;谢谢各位的配合;在这里期望我写的小小插件能为你提供便捷;
|
||||||
|
|
||||||
|
> 1.如果你对源码进行了修改使用,请不需要对作者做任何的反馈,作者确实没有空陪你做技术分析解答;
|
||||||
|
> 2.如果你引入插件,连插件是否有正常被uniapp框架识别解析都不清楚,请你换个插件使用;
|
||||||
|
> 3.如果你引入插件,针对自己项目进行功能改造的,请自行仔细阅读源码并了解其原理,自行改造;这里作者不愿意浪费过多时间进行技术解答;
|
||||||
|
|
||||||
|
### 如果有使用问题请加群
|
||||||
|
|
||||||
|
注意:如果插件问题,请务必给一个完整的复现demo,谢谢配合;
|
||||||
|
[点击链接加入群聊前端开发(uniapp插件)】](https://qm.qq.com/q/S1bJzQfJAG)
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
>[从uniapp插件市场导入](https://ext.dcloud.net.cn/plugin?name=next-table)
|
||||||
|
|
||||||
|
### 微信小程序在线体验
|
||||||
|

|
||||||
|
|
||||||
|
### 预览
|
||||||
|
### appDemo安装包下载地址:[android安装包](https://lixueshiaa.github.io/webtest/www/static/demo_next.apk);
|
||||||
|
***
|
||||||
|
|
||||||
|
| 功能预览 |
|
||||||
|
| :----------------------------------------------------------: |
|
||||||
|
|  |
|
||||||
|
|
||||||
|
|
||||||
|
### 超集功能预览(增值功能-请下载next-x-table支持可固定表头,分组表头等强大的功能)
|
||||||
|
### 点击进入: [next-x-table](https://ext.dcloud.net.cn/plugin?id=19584)
|
||||||
|
###
|
||||||
|
| 小程序和app端随意设置容易宽高,冻结表头使得交互更加友好 | 动态分组表头/分页/排序/合计/fixed等功能 |
|
||||||
|
| :--------------------------------------------------------------------: | :-----------------------------------------------------------------------: |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
| 分页功能演示 | 下拉加载更多等功能 |
|
||||||
|
| :-----------------------------------------------------------------------------: |:-----------------------------------------------------------------------: |
|
||||||
|
|  | |
|
||||||
|
|
||||||
|
## 示例demo(vue3 + ts)
|
||||||
|
``` html
|
||||||
|
<next-table
|
||||||
|
:show-header="true"
|
||||||
|
:columns="column"
|
||||||
|
:stripe="true"
|
||||||
|
:fit="false"
|
||||||
|
show-summary
|
||||||
|
sum-text="合计"
|
||||||
|
@rowClick="rowClick"
|
||||||
|
:summary-method="getSummaries"
|
||||||
|
@toggleRowSelection="toggleRowSelection"
|
||||||
|
@toggleAllSelection="toggleAllSelection"
|
||||||
|
:border="true"
|
||||||
|
@edit="buttonEdit"
|
||||||
|
@dele="dele"
|
||||||
|
:data="datalist"></next-table>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref, unref} from "vue"
|
||||||
|
const pageIndex = ref(1)
|
||||||
|
const pageTotal = ref(5)
|
||||||
|
const datalist = ref([])
|
||||||
|
const checkNameList = ref([])
|
||||||
|
|
||||||
|
function getdatalist(pageIndex) {
|
||||||
|
const pageSize = 10
|
||||||
|
const arr = []
|
||||||
|
for(let i = pageSize*(pageIndex-1) + 1; i < pageSize*pageIndex; i++) {
|
||||||
|
arr.push({
|
||||||
|
date: '2023-06-23',
|
||||||
|
name: `刘先生${i}`,
|
||||||
|
province: '上海',
|
||||||
|
sex: i%2 ? '0' : '1',
|
||||||
|
disabled: i%2 ? true : false,
|
||||||
|
checked: unref(checkNameList)[unref(pageIndex)] ? unref(checkNameList)[unref(pageIndex)].indexOf(`刘先生${i}`) !== -1 : false,
|
||||||
|
age: 20,
|
||||||
|
img: 'https://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D450%2C600/sign=e58ae9feb1003af34defd464001aea6a/8601a18b87d6277f4d763bcf2f381f30e824fce5.jpg',
|
||||||
|
city: '广州市',
|
||||||
|
address: '天河区东圃镇2002号',
|
||||||
|
zip: 200333
|
||||||
|
})
|
||||||
|
}
|
||||||
|
datalist.value = arr
|
||||||
|
}
|
||||||
|
function pageChange(index) {
|
||||||
|
pageIndex.value = index
|
||||||
|
getdatalist(unref(pageIndex))
|
||||||
|
}
|
||||||
|
function dele(item) {
|
||||||
|
const index = unref(datalist).findIndex(it => it.name == item.name)
|
||||||
|
if (index != -1) {
|
||||||
|
unref(datalist).splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function toggleAllSelection(_, list) {
|
||||||
|
unref(checkNameList)[unref(pageIndex)] = list.map(item => item.name)
|
||||||
|
}
|
||||||
|
function toggleRowSelection(bool, list) {
|
||||||
|
unref(checkNameList)[unref(pageIndex)] = list.map(item => item.name)
|
||||||
|
}
|
||||||
|
function buttonEdit(item) {
|
||||||
|
console.log(111111, item)
|
||||||
|
}
|
||||||
|
const column = ref([
|
||||||
|
{ type:'selection', fixed:true,width:60 },
|
||||||
|
{ name: 'name', label: '姓名',fixed:false,width:80,emptyString:'--' },
|
||||||
|
{ name: 'age', label: '年纪',sorter:false,align:'right', },
|
||||||
|
{ name: 'sex', label: '性别',filters:{'0':'男','1':'女'}},
|
||||||
|
{ name: 'img', label: '图片',type:"img" },
|
||||||
|
{ name: 'address', label: '地址' },
|
||||||
|
{ name: 'date', label: '日期',sorter:true },
|
||||||
|
{ name: 'province', label: '省份' },
|
||||||
|
{ name: 'city', label: '城市' },
|
||||||
|
{ name: 'zip', label: '邮编' },
|
||||||
|
{ name: 'operation', type:'operation',label: '操作',renders:[
|
||||||
|
{
|
||||||
|
name:'编辑',
|
||||||
|
func:'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name:'删除',
|
||||||
|
type:'warn',
|
||||||
|
func:"dele"
|
||||||
|
},
|
||||||
|
]},
|
||||||
|
])
|
||||||
|
getdatalist(unref(pageIndex))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
```
|
||||||
|
## 多级表头示例demo,同样支持vue2(vue3 + ts)
|
||||||
|
``` html
|
||||||
|
<template>
|
||||||
|
<view class="next-table-container">
|
||||||
|
<next-x-table
|
||||||
|
:show-header="true"
|
||||||
|
:columns="column"
|
||||||
|
:stripe="true"
|
||||||
|
:fit="true"
|
||||||
|
:show-summary="true"
|
||||||
|
sum-text="合计"
|
||||||
|
@rowClick="rowClick"
|
||||||
|
:summary-method="getSummaries"
|
||||||
|
@pageChange="pageChange"
|
||||||
|
@toggleRowSelection="toggleRowSelection"
|
||||||
|
@toggleAllSelection="toggleAllSelection"
|
||||||
|
:pageIndex="pageIndex"
|
||||||
|
:pageTotal="pageTotal"
|
||||||
|
align="center"
|
||||||
|
:showPaging="true"
|
||||||
|
:border="true"
|
||||||
|
:isShowLoadMore="false"
|
||||||
|
:highlight="true"
|
||||||
|
@edit="buttonEdit"
|
||||||
|
@dele="dele"
|
||||||
|
:pullUpLoading="pullUpLoading"
|
||||||
|
:data="datalist" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref, unref, onMounted, nextTick } from "vue"
|
||||||
|
const pageIndex = ref(1)
|
||||||
|
const pageTotal = ref(5)
|
||||||
|
const datalist = ref([])
|
||||||
|
const checkNameList = ref([])
|
||||||
|
const tableWidth = ref(0)
|
||||||
|
const tableHeight = ref(0)
|
||||||
|
const pageSize = 30
|
||||||
|
|
||||||
|
function getdatalist(pageIndex) {
|
||||||
|
const arr = []
|
||||||
|
for(let i = pageSize*(pageIndex-1) + 1; i < pageSize*pageIndex; i++) {
|
||||||
|
arr.push({
|
||||||
|
date: '2023-06-23',
|
||||||
|
name: `刘先生${i}`,
|
||||||
|
province: '上海',
|
||||||
|
sex: i%2 ? '0' : '1',
|
||||||
|
other: "其他类型",
|
||||||
|
remark: `备注-${i}`,
|
||||||
|
disabled: i%2 ? true : false,
|
||||||
|
checked: unref(checkNameList)[unref(pageIndex)] ? unref(checkNameList)[unref(pageIndex)].indexOf(`刘先生${i}`) !== -1 : false,
|
||||||
|
age: 20,
|
||||||
|
img: 'https://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D450%2C600/sign=e58ae9feb1003af34defd464001aea6a/8601a18b87d6277f4d763bcf2f381f30e824fce5.jpg',
|
||||||
|
city: '广州市',
|
||||||
|
address: '天河区东圃镇2002号发撒锻炼腹肌爱的色放上帝发誓地方',
|
||||||
|
zip: 200333
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(pageIndex === 1) {
|
||||||
|
datalist.value = arr
|
||||||
|
} else {
|
||||||
|
datalist.value = datalist.value.concat(arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function rowClick(e) {
|
||||||
|
console.log('------rowClick------', e)
|
||||||
|
}
|
||||||
|
function _getdatalist(pageIndex) {
|
||||||
|
const arr = []
|
||||||
|
for(let i = pageSize*(pageIndex-1) + 1; i < pageSize*pageIndex; i++) {
|
||||||
|
arr.push({
|
||||||
|
date: '2023-06-23',
|
||||||
|
name: `刘先生${i}`,
|
||||||
|
province: '上海',
|
||||||
|
sex: i%2 ? '0' : '1',
|
||||||
|
other: "其他类型",
|
||||||
|
remark: `备注-${i}`,
|
||||||
|
disabled: i%2 ? true : false,
|
||||||
|
checked: unref(checkNameList)[unref(pageIndex)] ? unref(checkNameList)[unref(pageIndex)].indexOf(`刘先生${i}`) !== -1 : false,
|
||||||
|
age: 20,
|
||||||
|
img: 'https://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D450%2C600/sign=e58ae9feb1003af34defd464001aea6a/8601a18b87d6277f4d763bcf2f381f30e824fce5.jpg',
|
||||||
|
city: '广州市',
|
||||||
|
address: '天河区东圃镇2002号发撒锻炼腹肌爱的色放上帝发誓地方',
|
||||||
|
zip: 200333
|
||||||
|
})
|
||||||
|
}
|
||||||
|
datalist.value = arr
|
||||||
|
}
|
||||||
|
function settingConf(type) {
|
||||||
|
if(type === 1) {
|
||||||
|
tableWidth.value = "50vw";
|
||||||
|
tableHeight.value = "50vh";
|
||||||
|
} else if(type === 2) {
|
||||||
|
tableWidth.value = "80vw";
|
||||||
|
tableHeight.value = "70vh";
|
||||||
|
} else if(type === 3) {
|
||||||
|
tableWidth.value = "100vw";
|
||||||
|
tableHeight.value = "80vh";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pageChange(index) {
|
||||||
|
console.log(1111111, index)
|
||||||
|
pageIndex.value = index
|
||||||
|
_getdatalist(unref(pageIndex))
|
||||||
|
}
|
||||||
|
function dele(item) {
|
||||||
|
const index = unref(datalist).findIndex(it => it.name == item.name)
|
||||||
|
if (index != -1) {
|
||||||
|
unref(datalist).splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function toggleAllSelection(_, list) {
|
||||||
|
unref(checkNameList)[unref(pageIndex)] = list.map(item => item.name)
|
||||||
|
}
|
||||||
|
function toggleRowSelection(bool, list) {
|
||||||
|
unref(checkNameList)[unref(pageIndex)] = list.map(item => item.name)
|
||||||
|
}
|
||||||
|
function buttonEdit(item) {
|
||||||
|
console.log(111111, item)
|
||||||
|
}
|
||||||
|
function pullUpLoading(callback) {
|
||||||
|
console.log(1111111111111, pageIndex.value)
|
||||||
|
pageIndex.value = pageIndex.value + 1
|
||||||
|
setTimeout(() => {
|
||||||
|
if(pageIndex.value > 3) {
|
||||||
|
callback('ok')
|
||||||
|
} else {
|
||||||
|
getdatalist(unref(pageIndex))
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
const column = ref([
|
||||||
|
{ type:'selection',width:60, fixed: true },
|
||||||
|
{ name: 'name', label: '姓名',align:'center',fixed: true, emptyString:'--' },
|
||||||
|
{ name: 'age', label: '年纪',sorter:false,align:'right',groupTitle: '分组二,分组一,总汇' },
|
||||||
|
{ name: 'sex', align:'center', label: '性别',filters:{'0':'男','1':'女'},groupTitle: '分组二,分组一,总汇'},
|
||||||
|
{ name: 'img', label: '图片',type:"img" },
|
||||||
|
{ name: 'address', label: '地址',align:'center',groupTitle: '分组三,分组一,总汇' },
|
||||||
|
{ name: 'date', label: '日期',sorter:true },
|
||||||
|
{ name: 'remark', label: '备注',groupTitle: '分组四,分组一,总汇' },
|
||||||
|
{ name: 'province', label: '省份',groupTitle: '分组四,分组一,总汇' },
|
||||||
|
{ name: 'other', label: '其他',groupTitle: '分组五,分组一,总汇' },
|
||||||
|
{ name: 'city', label: '城市',groupTitle: '总汇' },
|
||||||
|
{ name: 'zip', label: '邮编' },
|
||||||
|
{ name: 'operation', type:'operation',label: '操作',renders:[
|
||||||
|
{
|
||||||
|
name:'编辑',
|
||||||
|
func:'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name:'删除',
|
||||||
|
type:'warn',
|
||||||
|
func:"dele"
|
||||||
|
},
|
||||||
|
]},
|
||||||
|
])
|
||||||
|
onMounted(() => {
|
||||||
|
})
|
||||||
|
getdatalist(unref(pageIndex))
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
.next-table-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.flex-btns {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 示例demo(vue2)
|
||||||
|
``` html
|
||||||
|
<next-table
|
||||||
|
:show-header="true"
|
||||||
|
:columns="column"
|
||||||
|
:stripe="true"
|
||||||
|
:fit="false"
|
||||||
|
show-summary
|
||||||
|
sum-text="合计"
|
||||||
|
@rowClick="rowClick"
|
||||||
|
:summary-method="getSummaries"
|
||||||
|
@toggleRowSelection="toggleRowSelection"
|
||||||
|
@toggleAllSelection="toggleAllSelection"
|
||||||
|
:border="true"
|
||||||
|
@edit="buttonEdit"
|
||||||
|
@dele="dele"
|
||||||
|
:data="datalist"></next-table>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
pageIndex: 1,
|
||||||
|
pageTotal: 5,
|
||||||
|
datalist: [],
|
||||||
|
checkNameList: [],
|
||||||
|
column: [
|
||||||
|
{ type:'selection', fixed:true,width:60 },
|
||||||
|
{ name: 'name', label: '姓名',fixed:false,width:80,emptyString:'--' },
|
||||||
|
{ name: 'age', label: '年纪',sorter:false,align:'right', },
|
||||||
|
{ name: 'sex', label: '性别',filters:{'0':'男','1':'女'}},
|
||||||
|
{ name: 'img', label: '图片',type:"img" },
|
||||||
|
{ name: 'address', label: '地址' },
|
||||||
|
{ name: 'date', label: '日期',sorter:true },
|
||||||
|
{ name: 'province', label: '省份' },
|
||||||
|
{ name: 'city', label: '城市' },
|
||||||
|
{ name: 'zip', label: '邮编' },
|
||||||
|
{ name: 'operation', type:'operation',label: '操作',renders:[
|
||||||
|
{
|
||||||
|
name:'编辑',
|
||||||
|
func:'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name:'删除',
|
||||||
|
type:'warn',
|
||||||
|
func:"dele"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getdatalist(pageIndex) {
|
||||||
|
const pageSize = 10
|
||||||
|
const arr = []
|
||||||
|
for(let i = pageSize*(pageIndex-1) + 1; i < pageSize*pageIndex; i++) {
|
||||||
|
arr.push({
|
||||||
|
date: '2023-06-23',
|
||||||
|
name: `刘先生${i}`,
|
||||||
|
province: '上海',
|
||||||
|
sex: i%2 ? '0' : '1',
|
||||||
|
checked: this.checkNameList[this.pageIndex] ? this.checkNameList[this.pageIndex].indexOf(`刘先生${i}`) !== -1 : false,
|
||||||
|
age: 20,
|
||||||
|
img: 'https://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/wh%3D450%2C600/sign=e58ae9feb1003af34defd464001aea6a/8601a18b87d6277f4d763bcf2f381f30e824fce5.jpg',
|
||||||
|
city: '广州市',
|
||||||
|
address: '天河区东圃镇2002号',
|
||||||
|
zip: 200333
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.datalist = arr
|
||||||
|
},
|
||||||
|
pageChange(index) {
|
||||||
|
this.pageIndex = index
|
||||||
|
this.getdatalist(this.pageIndex)
|
||||||
|
},
|
||||||
|
dele(item) {
|
||||||
|
const index = this.datalist.findIndex(it => it.name == item.name)
|
||||||
|
if (index != -1) {
|
||||||
|
this.datalist.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleAllSelection(_, list) {
|
||||||
|
this.checkNameList[this.pageIndex] = list.map(item => item.name)
|
||||||
|
},
|
||||||
|
toggleRowSelection(bool, list) {
|
||||||
|
this.checkNameList[this.pageIndex] = list.map(item => item.name)
|
||||||
|
},
|
||||||
|
buttonEdit(item) {
|
||||||
|
console.log(111111, item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getdatalist(this.pageIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
例如:// <next-table @edit=""/>
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
],
|
],
|
||||||
"repository": "",
|
"repository": "",
|
||||||
"engines": {
|
"engines": {
|
||||||
"HBuilderX": "^4.36"
|
"HBuilderX": "^4.36",
|
||||||
|
"uni-app": "^3.1.0",
|
||||||
|
"uni-app-x": "^3.1.0"
|
||||||
},
|
},
|
||||||
"dcloudext": {
|
"dcloudext": {
|
||||||
"type": "component-vue",
|
"type": "component-vue",
|
||||||
@@ -30,55 +32,67 @@
|
|||||||
"data": "无",
|
"data": "无",
|
||||||
"permissions": "无"
|
"permissions": "无"
|
||||||
},
|
},
|
||||||
"npmurl": ""
|
"npmurl": "",
|
||||||
|
"darkmode": "-",
|
||||||
|
"i18n": "-",
|
||||||
|
"widescreen": "-"
|
||||||
},
|
},
|
||||||
"uni_modules": {
|
"uni_modules": {
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"encrypt": [],
|
"encrypt": [],
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"tcb": "y",
|
"tcb": "√",
|
||||||
"aliyun": "y",
|
"aliyun": "√",
|
||||||
"alipay": "y"
|
"alipay": "√"
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"Vue": {
|
"uni-app": {
|
||||||
"vue2": "y",
|
"vue": {
|
||||||
"vue3": "y"
|
"vue2": "-",
|
||||||
|
"vue3": "-"
|
||||||
},
|
},
|
||||||
"App": {
|
"web": {
|
||||||
"app-vue": "y",
|
"safari": "-",
|
||||||
"app-nvue": "u",
|
"chrome": "-"
|
||||||
"app-uvue": "u",
|
|
||||||
"app-harmony": "u"
|
|
||||||
},
|
},
|
||||||
"H5-mobile": {
|
"app": {
|
||||||
"Safari": "y",
|
"vue": "-",
|
||||||
"Android Browser": "y",
|
"nvue": "-",
|
||||||
"微信浏览器(Android)": "y",
|
"android": "-",
|
||||||
"QQ浏览器(Android)": "y"
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
},
|
},
|
||||||
"H5-pc": {
|
"mp": {
|
||||||
"Chrome": "y",
|
"weixin": "-",
|
||||||
"IE": "u",
|
"alipay": "-",
|
||||||
"Edge": "y",
|
"toutiao": "-",
|
||||||
"Firefox": "y",
|
"baidu": "-",
|
||||||
"Safari": "y"
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "-",
|
||||||
|
"lark": "-",
|
||||||
|
"xhs": "-"
|
||||||
},
|
},
|
||||||
"小程序": {
|
"quickapp": {
|
||||||
"微信": "y",
|
"huawei": "-",
|
||||||
"阿里": "u",
|
"union": "-"
|
||||||
"百度": "u",
|
}
|
||||||
"字节跳动": "u",
|
|
||||||
"QQ": "u",
|
|
||||||
"钉钉": "u",
|
|
||||||
"快手": "u",
|
|
||||||
"飞书": "u",
|
|
||||||
"京东": "u"
|
|
||||||
},
|
},
|
||||||
"快应用": {
|
"uni-app-x": {
|
||||||
"华为": "u",
|
"web": {
|
||||||
"联盟": "u"
|
"safari": "-",
|
||||||
|
"chrome": "-"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": "-",
|
||||||
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "-"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
],
|
],
|
||||||
"repository": "https://github.com/dcloudio/uni-ui",
|
"repository": "https://github.com/dcloudio/uni-ui",
|
||||||
"engines": {
|
"engines": {
|
||||||
"HBuilderX": ""
|
"HBuilderX": "",
|
||||||
|
"uni-app": "^3.1.0",
|
||||||
|
"uni-app-x": "^3.1.0"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"example": "../../temps/example_temps"
|
"example": "../../temps/example_temps"
|
||||||
@@ -36,49 +38,67 @@
|
|||||||
"permissions": "无"
|
"permissions": "无"
|
||||||
},
|
},
|
||||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||||
"type": "component-vue"
|
"type": "component-vue",
|
||||||
|
"darkmode": "-",
|
||||||
|
"i18n": "-",
|
||||||
|
"widescreen": "-"
|
||||||
},
|
},
|
||||||
"uni_modules": {
|
"uni_modules": {
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"encrypt": [],
|
"encrypt": [],
|
||||||
"platforms": {
|
"platforms": {
|
||||||
"cloud": {
|
"cloud": {
|
||||||
"tcb": "y",
|
"tcb": "√",
|
||||||
"aliyun": "y",
|
"aliyun": "√",
|
||||||
"alipay": "n"
|
"alipay": "x"
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"App": {
|
"uni-app": {
|
||||||
"app-vue": "y",
|
"vue": {
|
||||||
"app-nvue": "y"
|
"vue2": "-",
|
||||||
|
"vue3": "-"
|
||||||
},
|
},
|
||||||
"H5-mobile": {
|
"web": {
|
||||||
"Safari": "y",
|
"safari": "-",
|
||||||
"Android Browser": "y",
|
"chrome": "-"
|
||||||
"微信浏览器(Android)": "y",
|
|
||||||
"QQ浏览器(Android)": "y"
|
|
||||||
},
|
},
|
||||||
"H5-pc": {
|
"app": {
|
||||||
"Chrome": "y",
|
"vue": "-",
|
||||||
"IE": "y",
|
"nvue": "-",
|
||||||
"Edge": "y",
|
"android": "-",
|
||||||
"Firefox": "y",
|
"ios": "-",
|
||||||
"Safari": "y"
|
"harmony": "-"
|
||||||
},
|
},
|
||||||
"小程序": {
|
"mp": {
|
||||||
"微信": "y",
|
"weixin": "-",
|
||||||
"阿里": "y",
|
"alipay": "-",
|
||||||
"百度": "y",
|
"toutiao": "-",
|
||||||
"字节跳动": "y",
|
"baidu": "-",
|
||||||
"QQ": "y"
|
"kuaishou": "-",
|
||||||
|
"jd": "-",
|
||||||
|
"harmony": "-",
|
||||||
|
"qq": "-",
|
||||||
|
"lark": "-",
|
||||||
|
"xhs": "-"
|
||||||
},
|
},
|
||||||
"快应用": {
|
"quickapp": {
|
||||||
"华为": "u",
|
"huawei": "-",
|
||||||
"联盟": "u"
|
"union": "-"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Vue": {
|
"uni-app-x": {
|
||||||
"vue2": "y",
|
"web": {
|
||||||
"vue3": "y"
|
"safari": "-",
|
||||||
|
"chrome": "-"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"android": "-",
|
||||||
|
"ios": "-",
|
||||||
|
"harmony": "-"
|
||||||
|
},
|
||||||
|
"mp": {
|
||||||
|
"weixin": "-"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import uni from "@dcloudio/vite-plugin-uni";
|
import uni from "@dcloudio/vite-plugin-uni";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [uni()],
|
plugins: [uni()],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user