You've already forked template-MP
Compare commits
26 Commits
1e29e845e8
...
future
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8348d80e9 | ||
|
|
65656f1810 | ||
|
|
ca6bf7f211 | ||
|
|
5e1299db5c | ||
|
|
49c6854e8e | ||
|
|
d5a9bb95c3 | ||
|
|
c247cbe1c7 | ||
|
|
7030498a83 | ||
|
|
edba077718 | ||
|
|
cee0c70427 | ||
|
|
de533d2ef5 | ||
|
|
6f9c9e06f1 | ||
|
|
c7baff94e8 | ||
|
|
81a16966a4 | ||
|
|
1f42981fd4 | ||
|
|
ad0f64a48a | ||
|
|
726985d38f | ||
|
|
729d5af382 | ||
|
|
b5d4684289 | ||
| 838d5bdb22 | |||
| 101e3eb2a1 | |||
|
|
2280b5c3f4 | ||
|
|
e3823f7439 | ||
|
|
48125bd0ec | ||
| 5c01f10808 | |||
| 3f47967f8f |
24
.env
24
.env
@@ -1,4 +1,20 @@
|
|||||||
VITE_BASE_URL= #接口地址
|
# 接口地址
|
||||||
VITE_ASSETSURL=https://cdn.vrupup.com/s/1598/assets/ #资源地址
|
VITE_BASE_URL=
|
||||||
VITE_APPID=wx9cb717d8151d8486 #小程序APPID
|
|
||||||
VITE_UNI_APPID=_UNI_8842336 #UNI-APPID
|
# 资源地址
|
||||||
|
VITE_ASSETSURL=https://cdn.vrupup.com/s/1598/assets/
|
||||||
|
|
||||||
|
# 小程序APPID
|
||||||
|
VITE_APPID=wx9cb717d8151d8486
|
||||||
|
|
||||||
|
# UNI-APPID
|
||||||
|
VITE_UNI_APPID=_UNI_8842336
|
||||||
|
|
||||||
|
# 调试模式
|
||||||
|
VITE_DEBUG=false
|
||||||
|
|
||||||
|
# API超时时间(毫秒)
|
||||||
|
VITE_API_TIMEOUT=10000
|
||||||
|
|
||||||
|
# 版本号
|
||||||
|
VITE_APP_VERSION=1.0.0
|
||||||
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.hbuilderx
|
||||||
|
unpackage
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
56
.eslintrc.js
Normal file
56
.eslintrc.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'prettier',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@babel/eslint-parser',
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['vue', 'prettier'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'vue/no-unused-vars': 'error',
|
||||||
|
'vue/html-self-closing': 'off',
|
||||||
|
'vue/max-attributes-per-line': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
singleline: {
|
||||||
|
max: 3,
|
||||||
|
},
|
||||||
|
multiline: {
|
||||||
|
max: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-unused-vars': 'error',
|
||||||
|
'no-undef': 'error',
|
||||||
|
'no-var': 'error',
|
||||||
|
'prefer-const': 'error',
|
||||||
|
'object-shorthand': 'error',
|
||||||
|
'quote-props': ['error', 'as-needed'],
|
||||||
|
'array-callback-return': 'error',
|
||||||
|
'prefer-arrow-callback': 'error',
|
||||||
|
'arrow-parens': ['error', 'as-needed'],
|
||||||
|
'arrow-spacing': 'error',
|
||||||
|
'no-duplicate-imports': 'error',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
uni: 'readonly',
|
||||||
|
getApp: 'readonly',
|
||||||
|
getCurrentPages: 'readonly',
|
||||||
|
plus: 'readonly',
|
||||||
|
},
|
||||||
|
}
|
||||||
30
.iflow/agents/code-reviewer.md
Normal file
30
.iflow/agents/code-reviewer.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
agent-type: code-reviewer
|
||||||
|
name: code-reviewer
|
||||||
|
description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
|
||||||
|
when-to-use: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
|
||||||
|
allowed-tools:
|
||||||
|
model: gpt-4
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: blue
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a senior code reviewer with deep expertise in configuration security and production reliability. Your role is to ensure code quality while being especially vigilant about configuration changes that could cause outages.
|
||||||
|
|
||||||
|
When invoked:
|
||||||
|
1. Run git diff to see recent changes
|
||||||
|
2. Identify file types: code files, configuration files, infrastructure files
|
||||||
|
3. Apply appropriate review strategies for each type
|
||||||
|
4. Begin review immediately with heightened scrutiny for configuration changes
|
||||||
|
|
||||||
|
## Configuration Change Review (CRITICAL FOCUS)
|
||||||
|
|
||||||
|
### Magic Number Detection
|
||||||
|
For ANY numeric value change in configuration files:
|
||||||
|
- **ALWAYS QUESTION**: Why this specific value? What's the justification?
|
||||||
|
- **REQUIRE EVIDENCE**: Has this been tested under production-like load?
|
||||||
|
- **CHECK BOUNDS**: Is this within recommended ranges for your system?
|
||||||
|
- **ASSESS IMPACT**: What happens if this limit is reached?
|
||||||
|
|
||||||
|
Focus on fixing the underlying issue, not just symptoms. Always prioritize preventing production outages.
|
||||||
36
.iflow/agents/debugger.md
Normal file
36
.iflow/agents/debugger.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
agent-type: debugger
|
||||||
|
name: debugger
|
||||||
|
description: Debugging specialist for errors, test failures, and unexpected behavior. Use proactively when encountering any issues.
|
||||||
|
when-to-use: Debugging specialist for errors, test failures, and unexpected behavior. Use proactively when encountering any issues.
|
||||||
|
allowed-tools:
|
||||||
|
model: gpt-4
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: red
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an expert debugger specializing in root cause analysis.
|
||||||
|
|
||||||
|
When invoked:
|
||||||
|
1. Capture error message and stack trace
|
||||||
|
2. Identify reproduction steps
|
||||||
|
3. Isolate the failure location
|
||||||
|
4. Implement minimal fix
|
||||||
|
5. Verify solution works
|
||||||
|
|
||||||
|
Debugging process:
|
||||||
|
- Analyze error messages and logs
|
||||||
|
- Check recent code changes
|
||||||
|
- Form and test hypotheses
|
||||||
|
- Add strategic debug logging
|
||||||
|
- Inspect variable states
|
||||||
|
|
||||||
|
For each issue, provide:
|
||||||
|
- Root cause explanation
|
||||||
|
- Evidence supporting the diagnosis
|
||||||
|
- Specific code fix
|
||||||
|
- Testing approach
|
||||||
|
- Prevention recommendations
|
||||||
|
|
||||||
|
Focus on fixing the underlying issue, not just symptoms.
|
||||||
23
.iflow/agents/frontend-developer.md
Normal file
23
.iflow/agents/frontend-developer.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
agent-type: frontend-developer
|
||||||
|
name: frontend-developer
|
||||||
|
description: Build React components, implement responsive layouts, and handle client-side state management.
|
||||||
|
when-to-use: Build React components, implement responsive layouts, and handle client-side state management.
|
||||||
|
allowed-tools:
|
||||||
|
model: gpt-4
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: orange
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a frontend developer specializing in modern web development with React and TypeScript. Create responsive, accessible, and performant user interfaces.
|
||||||
|
|
||||||
|
Core skills:
|
||||||
|
- React components and hooks
|
||||||
|
- TypeScript for type safety
|
||||||
|
- Responsive CSS with Tailwind or styled-components
|
||||||
|
- State management (Redux, Zustand, Context API)
|
||||||
|
- Performance optimization
|
||||||
|
- Accessibility (WCAG compliance)
|
||||||
|
- Testing with React Testing Library
|
||||||
|
Implement modern patterns like server components, suspense, and progressive enhancement.
|
||||||
41
.iflow/agents/javascript-pro.md
Normal file
41
.iflow/agents/javascript-pro.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
agent-type: javascript-pro
|
||||||
|
name: javascript-pro
|
||||||
|
description: Master modern JavaScript with ES6+, async patterns, and Node.js APIs. Handles promises, event loops, and browser/Node compatibility. Use PROACTIVELY for JavaScript optimization, async debugging, or complex JS patterns.
|
||||||
|
when-to-use: Master modern JavaScript with ES6+, async patterns, and Node.js APIs. Handles promises, event loops, and browser/Node compatibility. Use PROACTIVELY for JavaScript optimization, async debugging, or complex JS patterns.
|
||||||
|
allowed-tools:
|
||||||
|
model: sonnet
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: orange
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a JavaScript expert specializing in modern JS and async programming.
|
||||||
|
|
||||||
|
## Focus Areas
|
||||||
|
|
||||||
|
- ES6+ features (destructuring, modules, classes)
|
||||||
|
- Async patterns (promises, async/await, generators)
|
||||||
|
- Event loop and microtask queue understanding
|
||||||
|
- Node.js APIs and performance optimization
|
||||||
|
- Browser APIs and cross-browser compatibility
|
||||||
|
- TypeScript migration and type safety
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
1. Prefer async/await over promise chains
|
||||||
|
2. Use functional patterns where appropriate
|
||||||
|
3. Handle errors at appropriate boundaries
|
||||||
|
4. Avoid callback hell with modern patterns
|
||||||
|
5. Consider bundle size for browser code
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- Modern JavaScript with proper error handling
|
||||||
|
- Async code with race condition prevention
|
||||||
|
- Module structure with clean exports
|
||||||
|
- Jest tests with async test patterns
|
||||||
|
- Performance profiling results
|
||||||
|
- Polyfill strategy for browser compatibility
|
||||||
|
|
||||||
|
Support both Node.js and browser environments. Include JSDoc comments.
|
||||||
38
.iflow/agents/test-automator.md
Normal file
38
.iflow/agents/test-automator.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
agent-type: test-automator
|
||||||
|
name: test-automator
|
||||||
|
description: Create comprehensive test suites with unit, integration, and e2e tests. Sets up CI pipelines, mocking strategies, and test data. Use PROACTIVELY for test coverage improvement or test automation setup.
|
||||||
|
when-to-use: Create comprehensive test suites with unit, integration, and e2e tests. Sets up CI pipelines, mocking strategies, and test data. Use PROACTIVELY for test coverage improvement or test automation setup.
|
||||||
|
allowed-tools:
|
||||||
|
model: sonnet
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: brown
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a test automation specialist focused on comprehensive testing strategies.
|
||||||
|
|
||||||
|
## Focus Areas
|
||||||
|
- Unit test design with mocking and fixtures
|
||||||
|
- Integration tests with test containers
|
||||||
|
- E2E tests with Playwright/Cypress
|
||||||
|
- CI/CD test pipeline configuration
|
||||||
|
- Test data management and factories
|
||||||
|
- Coverage analysis and reporting
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
1. Test pyramid - many unit, fewer integration, minimal E2E
|
||||||
|
2. Arrange-Act-Assert pattern
|
||||||
|
3. Test behavior, not implementation
|
||||||
|
4. Deterministic tests - no flakiness
|
||||||
|
5. Fast feedback - parallelize when possible
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Test suite with clear test names
|
||||||
|
- Mock/stub implementations for dependencies
|
||||||
|
- Test data factories or fixtures
|
||||||
|
- CI pipeline configuration for tests
|
||||||
|
- Coverage report setup
|
||||||
|
- E2E test scenarios for critical paths
|
||||||
|
|
||||||
|
Use appropriate testing frameworks (Jest, pytest, etc). Include both happy and edge cases.
|
||||||
39
.iflow/agents/typescript-pro.md
Normal file
39
.iflow/agents/typescript-pro.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
agent-type: typescript-pro
|
||||||
|
name: typescript-pro
|
||||||
|
description: Master TypeScript with advanced types, generics, and strict type safety. Handles complex type systems, decorators, and enterprise-grade patterns. Use PROACTIVELY for TypeScript architecture, type inference optimization, or advanced typing patterns.
|
||||||
|
when-to-use: Master TypeScript with advanced types, generics, and strict type safety. Handles complex type systems, decorators, and enterprise-grade patterns. Use PROACTIVELY for TypeScript architecture, type inference optimization, or advanced typing patterns.
|
||||||
|
allowed-tools:
|
||||||
|
model: sonnet
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: yellow
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a TypeScript expert specializing in advanced typing and enterprise-grade development.
|
||||||
|
|
||||||
|
## Focus Areas
|
||||||
|
- Advanced type systems (generics, conditional types, mapped types)
|
||||||
|
- Strict TypeScript configuration and compiler options
|
||||||
|
- Type inference optimization and utility types
|
||||||
|
- Decorators and metadata programming
|
||||||
|
- Module systems and namespace organization
|
||||||
|
- Integration with modern frameworks (React, Node.js, Express)
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
1. Leverage strict type checking with appropriate compiler flags
|
||||||
|
2. Use generics and utility types for maximum type safety
|
||||||
|
3. Prefer type inference over explicit annotations when clear
|
||||||
|
4. Design robust interfaces and abstract classes
|
||||||
|
5. Implement proper error boundaries with typed exceptions
|
||||||
|
6. Optimize build times with incremental compilation
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Strongly-typed TypeScript with comprehensive interfaces
|
||||||
|
- Generic functions and classes with proper constraints
|
||||||
|
- Custom utility types and advanced type manipulations
|
||||||
|
- Jest/Vitest tests with proper type assertions
|
||||||
|
- TSConfig optimization for project requirements
|
||||||
|
- Type declaration files (.d.ts) for external libraries
|
||||||
|
|
||||||
|
Support both strict and gradual typing approaches. Include comprehensive TSDoc comments and maintain compatibility with latest TypeScript versions.
|
||||||
41
.iflow/agents/ui-ux-designer.md
Normal file
41
.iflow/agents/ui-ux-designer.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
agent-type: ui-ux-designer
|
||||||
|
name: ui-ux-designer
|
||||||
|
description: Create interface designs, wireframes, and design systems. Masters user research, prototyping, and accessibility standards. Use PROACTIVELY for design systems, user flows, or interface optimization.
|
||||||
|
when-to-use: Create interface designs, wireframes, and design systems. Masters user research, prototyping, and accessibility standards. Use PROACTIVELY for design systems, user flows, or interface optimization.
|
||||||
|
allowed-tools:
|
||||||
|
model: sonnet
|
||||||
|
inherit-tools: true
|
||||||
|
inherit-mcps: true
|
||||||
|
color: purple
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a UI/UX designer specializing in user-centered design and interface systems.
|
||||||
|
|
||||||
|
## Focus Areas
|
||||||
|
|
||||||
|
- User research and persona development
|
||||||
|
- Wireframing and prototyping workflows
|
||||||
|
- Design system creation and maintenance
|
||||||
|
- Accessibility and inclusive design principles
|
||||||
|
- Information architecture and user flows
|
||||||
|
- Usability testing and iteration strategies
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
1. User needs first - design with empathy and data
|
||||||
|
2. Progressive disclosure for complex interfaces
|
||||||
|
3. Consistent design patterns and components
|
||||||
|
4. Mobile-first responsive design thinking
|
||||||
|
5. Accessibility built-in from the start
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- User journey maps and flow diagrams
|
||||||
|
- Low and high-fidelity wireframes
|
||||||
|
- Design system components and guidelines
|
||||||
|
- Prototype specifications for development
|
||||||
|
- Accessibility annotations and requirements
|
||||||
|
- Usability testing plans and metrics
|
||||||
|
|
||||||
|
Focus on solving user problems. Include design rationale and implementation notes.
|
||||||
11
.iflow/commands/cleanproject.toml
Normal file
11
.iflow/commands/cleanproject.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Command: cleanproject
|
||||||
|
# Description: Clean up development artifacts while preserving your working code
|
||||||
|
# Category: utility
|
||||||
|
# Version: 1
|
||||||
|
# Author: 10169
|
||||||
|
|
||||||
|
description = "Clean up development artifacts while preserving your working code"
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
I'll help clean up development artifacts while preserving your working code. I'll identify cleanup targets using native tools: Glob tool to find temporary and debug files, Grep tool to detect debug statements in code, Read tool to verify file contents before removal. Critical directories are automatically protected: .claude directory (commands and configurations), .git directory (version control), node_modules, vendor (dependency directories), Essential configuration files. When I find multiple items to clean, I'll create a todo list to process them systematically.
|
||||||
|
"""
|
||||||
11
.iflow/commands/commit.toml
Normal file
11
.iflow/commands/commit.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Command: commit
|
||||||
|
# Description: Analyze your changes and create a meaningful commit message
|
||||||
|
# Category: utility
|
||||||
|
# Version: 1
|
||||||
|
# Author: 10169
|
||||||
|
|
||||||
|
description = "分析您的更改并创建有意义的提交消息"
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
我会分析您的更改并创建一条有意义的提交消息并使用简单的git命令进行提交更改。我会分析更改,以确定修改了哪些文件、更改的性质(功能、修复、重构等)以及受影响的范围/组件。基于分析结果、以及遵循项目的IFLOW上下文中的提交规范,我会创建一条常规的中文提交消息,其中包含类型(新增|修复|文档|优化|其他)、主题(用现在时清晰描述)。
|
||||||
|
"""
|
||||||
11
.iflow/commands/docs.toml
Normal file
11
.iflow/commands/docs.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Command: docs
|
||||||
|
# Description: Smart documentation management and updates
|
||||||
|
# Category: documentation
|
||||||
|
# Version: 1
|
||||||
|
# Author: 10169
|
||||||
|
|
||||||
|
description = "智能文档管理和更新"
|
||||||
|
|
||||||
|
prompt = """
|
||||||
|
我将根据代码变更更新、整理和维护文档,帮助您智能地管理文档。我将同步文档与实现变更,识别过时的章节,提出改进建议,并确保文档之间的一致性。功能包括自动文档更新、交叉引用链接、版本跟踪和质量评估。
|
||||||
|
"""
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
{
|
{
|
||||||
|
"language": "zh-CN",
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"Framelink Figma MCP": {
|
"bing-cn-mcp": {
|
||||||
"description": "为AI编程工具提供Figma设计文件访问能力,支持获取设计数据和下载图像资源,帮助实现设计到代码的一键转换。",
|
"description": "MCP 服务器,提供中文必应搜索结果抓取和网页内容抓取功能,支持中文搜索",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@iflow-mcp/figma-developer-mcp@0.5.0", "--stdio"],
|
"args": ["-y", "@iflow-mcp/bing-cn-mcp"],
|
||||||
"env": {
|
"env": {
|
||||||
"FIGMA_API_KEY": ""
|
"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"playwright": {
|
"chrome-devtools": {
|
||||||
|
"description": "Chrome浏览器自动化工具,支持页面操作、性能分析、网络监控等功能",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["@playwright/mcp@latest"]
|
"args": ["-y", "@iflow-mcp/chrome-devtools-mcp"]
|
||||||
},
|
},
|
||||||
"context7": {
|
"context7": {
|
||||||
"description": "为开发者提供最新技术文档和代码示例的智能检索服务,支持库搜索和文档获取功能",
|
"description": "为开发者提供最新技术文档和代码示例的智能检索服务,支持库搜索和文档获取功能",
|
||||||
@@ -22,15 +24,6 @@
|
|||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@iflow-mcp/fetch@1.0.2"]
|
"args": ["-y", "@iflow-mcp/fetch@1.0.2"]
|
||||||
},
|
},
|
||||||
"server-puppeteer": {
|
|
||||||
"description": "基于Puppeteer的浏览器自动化工具,支持页面导航、截图、元素操作和JavaScript执行等功能",
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@iflow-mcp/server-puppeteer@0.6.2"],
|
|
||||||
"env": {
|
|
||||||
"PUPPETEER_LAUNCH_OPTIONS": "{\"executablePath\":\"C:\\Program Files\\Google\\Chrome\\Application\"}",
|
|
||||||
"ALLOW_DANGEROUS": "{\"executablePath\":\"C:\\Program Files\\Google\\Chrome\\Application\"}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"math-tools": {
|
"math-tools": {
|
||||||
"description": "提供34个数学运算工具,包含算术运算、统计计算、数据科学和表达式求值功能,支持高精度计算。",
|
"description": "提供34个数学运算工具,包含算术运算、统计计算、数据科学和表达式求值功能,支持高精度计算。",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
@@ -54,10 +47,13 @@
|
|||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@iflow-mcp/server-filesystem@0.6.2", "E:\\"]
|
"args": ["-y", "@iflow-mcp/server-filesystem@0.6.2", "E:\\"]
|
||||||
},
|
},
|
||||||
"mcp-server-code-runner": {
|
"Framelink Figma MCP": {
|
||||||
"description": "多语言代码执行器,支持35种编程语言的代码片段执行,包括JavaScript、Python、Go、Java等主流语言",
|
"description": "为AI编程工具提供Figma设计文件访问能力,支持获取设计数据和下载图像资源,帮助实现设计到代码的一键转换。",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "@iflow-mcp/mcp-server-code-runner"]
|
"args": ["-y", "@iflow-mcp/figma-developer-mcp@0.5.0", "--stdio"],
|
||||||
|
"env": {
|
||||||
|
"FIGMA_API_KEY": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"gitlab": {
|
"gitlab": {
|
||||||
"description": "提供GitLab项目管理、文件操作、Issue管理、合并请求等功能的MCP服务器工具集",
|
"description": "提供GitLab项目管理、文件操作、Issue管理、合并请求等功能的MCP服务器工具集",
|
||||||
|
|||||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.hbuilderx
|
||||||
|
unpackage
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
37
.prettierrc.js
Normal file
37
.prettierrc.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
module.exports = {
|
||||||
|
// 一行最多 100 字符
|
||||||
|
printWidth: 100,
|
||||||
|
// 使用 2 个空格缩进
|
||||||
|
tabWidth: 2,
|
||||||
|
// 不使用缩进符,而使用空格
|
||||||
|
useTabs: false,
|
||||||
|
// 行尾需要有分号
|
||||||
|
semi: false,
|
||||||
|
// 使用单引号
|
||||||
|
singleQuote: true,
|
||||||
|
// 对象的 key 仅在必要时用引号
|
||||||
|
quoteProps: 'as-needed',
|
||||||
|
// jsx 不使用单引号,而使用双引号
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
// 末尾不需要逗号
|
||||||
|
trailingComma: 'none',
|
||||||
|
// 大括号内的首尾需要空格
|
||||||
|
bracketSpacing: true,
|
||||||
|
// jsx 标签的反尖括号需要换行
|
||||||
|
bracketSameLine: false,
|
||||||
|
// 箭头函数,只有一个参数的时候,也需要括号
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
// 每个文件格式化的范围是文件的全部内容
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
// 不需要写文件开头的 @prettier
|
||||||
|
requirePragma: false,
|
||||||
|
// 不需要自动在文件开头插入 @prettier
|
||||||
|
insertPragma: false,
|
||||||
|
// 使用默认的折行标准
|
||||||
|
proseWrap: 'preserve',
|
||||||
|
// 根据显示样式决定 html 要不要折行
|
||||||
|
htmlWhitespaceSensitivity: 'css',
|
||||||
|
// 换行符使用 lf
|
||||||
|
endOfLine: 'lf',
|
||||||
|
}
|
||||||
2
App.vue
2
App.vue
@@ -9,7 +9,9 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "./uni.scss";
|
||||||
@import '@/common/styles/common.css';
|
@import '@/common/styles/common.css';
|
||||||
@import '@/common/styles/base.scss';
|
@import '@/common/styles/base.scss';
|
||||||
|
@import '@/common/styles/dark-mode.scss';
|
||||||
@import '@/uview-plus/index.scss';
|
@import '@/uview-plus/index.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
130
IFLOW.md
130
IFLOW.md
@@ -1,39 +1,52 @@
|
|||||||
# 项目概述
|
# 项目概述 - IFLOW 上下文
|
||||||
|
|
||||||
这是一个基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板。它提供了一个基础的项目结构和一些常用的工具函数,方便快速开发微信小程序。
|
这是一个基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板。它提供了一个基础的项目结构和一些常用的工具函数、自定义Hooks、组件等,方便快速开发微信小程序。
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
* **UniApp**: 跨平台开发框架,用于构建微信小程序。
|
* **UniApp**: 跨平台开发框架,用于构建微信小程序。
|
||||||
* **Vue3**: 渐进式 JavaScript 框架,用于构建用户界面。
|
* **Vue3**: 渐进式 JavaScript 框架,用于构建用户界面,采用 Composition API 和 setup 语法糖。
|
||||||
* **uView-Plus**: 基于 UniApp 的 UI 组件库。
|
* **uView-Plus**: 基于 UniApp 的 UI 组件库。
|
||||||
* **z-paging**: 一个用于处理分页加载的组件库。
|
* **z-paging**: 一个用于处理分页加载的组件库。
|
||||||
* **Vuex**: 状态管理库。
|
* **luch-request**: 网络请求库,用于封装 HTTP 请求。
|
||||||
|
* **ESLint & Prettier**: 代码规范和格式化工具。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── api/ # 接口相关
|
├── api/ # 接口相关
|
||||||
│ ├── modules/ # 业务接口
|
│ ├── modules/ # 业务接口模块
|
||||||
|
│ ├── index.js # API统一导出入口
|
||||||
│ └── request.js # 请求封装
|
│ └── request.js # 请求封装
|
||||||
├── common/ # 公共资源
|
├── common/ # 公共资源
|
||||||
|
│ ├── constants/ # 常量定义
|
||||||
│ ├── styles/ # 全局样式
|
│ ├── styles/ # 全局样式
|
||||||
│ │ ├── common.css # codefun原子类样式
|
│ │ ├── common.css # codefun原子类样式
|
||||||
│ │ └── base.scss # 全局样式变量
|
│ │ ├── base.scss # 全局样式变量和工具类
|
||||||
|
│ │ └── dark-mode.scss # 暗黑模式样式
|
||||||
│ └── utils/ # 工具函数
|
│ └── utils/ # 工具函数
|
||||||
│ └── tool.ts # 常用工具函数
|
│ ├── tool.js # 常用工具函数
|
||||||
|
│ └── env.js # 环境变量工具
|
||||||
├── components/ # 公共组件
|
├── components/ # 公共组件
|
||||||
|
│ └── common/ # 通用业务组件
|
||||||
|
├── directives/ # 自定义指令
|
||||||
|
├── hooks/ # Vue Composition API Hooks
|
||||||
|
│ ├── index.js # Hooks统一导出入口
|
||||||
|
│ ├── useRequest.js # 通用请求Hook
|
||||||
|
│ ├── useApi.js # API相关Hook
|
||||||
|
│ └── useState.js # 状态管理Hook
|
||||||
├── uni_modules/ # uni-app 组件
|
├── uni_modules/ # uni-app 组件
|
||||||
│ └── z-paging/ # 分页组件库
|
│ └── z-paging/ # 分页组件库
|
||||||
├── lib/ # 第三方库
|
├── lib/ # 第三方库
|
||||||
│ └── luch-request/ # luch-request 网络请求库
|
│ └── luch-request/ # luch-request 网络请求库
|
||||||
├── uview-plus/ # uView-Plus 组件库
|
├── uview-plus/ # uView-Plus 组件库
|
||||||
├── mixins/ # Vue 混入
|
├── mixins/ # Vue 混入
|
||||||
│ └── global.ts # 全局混入
|
│ └── global.js # 全局混入
|
||||||
├── store/ # 状态管理
|
|
||||||
│ └── index.ts # Vuex store
|
|
||||||
├── pages/ # 主包页面
|
├── pages/ # 主包页面
|
||||||
|
├── static/ # 静态资源文件
|
||||||
|
│ └── assets/ # 静态图片资源
|
||||||
|
├── store/ # 状态管理
|
||||||
├── subPages/ # 分包页面
|
├── subPages/ # 分包页面
|
||||||
├── App.vue # 应用入口
|
├── App.vue # 应用入口
|
||||||
├── main.js # 主入口文件
|
├── main.js # 主入口文件
|
||||||
@@ -42,7 +55,11 @@
|
|||||||
├── uni.scss # 全局样式变量
|
├── uni.scss # 全局样式变量
|
||||||
├── vite.config.js # Vite 编译配置
|
├── vite.config.js # Vite 编译配置
|
||||||
├── .nvmdrc # Node.js 版本要求
|
├── .nvmdrc # Node.js 版本要求
|
||||||
└── .env # 环境变量
|
├── .env # 环境变量
|
||||||
|
├── .eslintrc.js # ESLint 配置
|
||||||
|
├── .prettierrc.js # Prettier 配置
|
||||||
|
├── .eslintignore # ESLint 忽略文件
|
||||||
|
└── .prettierignore # Prettier 忽略文件
|
||||||
```
|
```
|
||||||
|
|
||||||
# 开发环境与运行
|
# 开发环境与运行
|
||||||
@@ -60,11 +77,30 @@ npm install
|
|||||||
|
|
||||||
## 运行项目
|
## 运行项目
|
||||||
|
|
||||||
由于这是一个 UniApp 项目,通常需要使用 HBuilderX 或其他支持 UniApp 的 IDE 来运行和调试。具体的运行命令取决于你的开发环境。
|
```bash
|
||||||
|
# 开发环境运行
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建生产环境
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 预览构建结果
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# 代码检查和修复
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# 代码格式化
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
|
|
||||||
## 构建项目
|
## 构建项目
|
||||||
|
|
||||||
同样,构建项目也需要使用 HBuilderX 或相应的 CLI 工具。
|
构建项目使用 Vite 工具链:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
# 代码规范与开发约定
|
# 代码规范与开发约定
|
||||||
|
|
||||||
@@ -73,29 +109,31 @@ npm install
|
|||||||
* 全局样式文件位于 `common/styles/` 目录下,包括 `common.css` 和 `base.scss`。
|
* 全局样式文件位于 `common/styles/` 目录下,包括 `common.css` 和 `base.scss`。
|
||||||
* `common.css` 提供了codefun原子类样式,用于快速布局。
|
* `common.css` 提供了codefun原子类样式,用于快速布局。
|
||||||
* `base.scss` 提供了SCSS变量和mixins,以及常用的样式类生成器。
|
* `base.scss` 提供了SCSS变量和mixins,以及常用的样式类生成器。
|
||||||
|
* 支持暗黑模式,通过 `dark-mode.scss` 实现。
|
||||||
* 样式规范应遵循项目中已有的风格。
|
* 样式规范应遵循项目中已有的风格。
|
||||||
|
|
||||||
## JavaScript
|
## JavaScript
|
||||||
|
|
||||||
* 严格遵循ES6规范。
|
* 严格遵循ES6+规范。
|
||||||
* 遵循JavaScript函数式编程范式。
|
* 遵循JavaScript函数式编程范式。
|
||||||
* 方法类函数应该使用 `function` 进行定义。
|
* 方法类函数应该使用 `function` 进行定义。
|
||||||
* 避免出现超过4个以上的 `ref`,超过4个则使用 `reactive`。
|
* 避免出现超过4个以上的 `ref`,超过4个则使用 `reactive`。
|
||||||
* 页面的生命周期按需进行导入,如(`import { onLoad } from '@dcloudio/uni-app'`)。
|
* 页面的生命周期需要通过 `@dcloudio/uni-app` 依赖进行按需导入,如(`import { onLoad } from '@dcloudio/uni-app'`)。
|
||||||
* 全局变量都集中放置于代码顶部。
|
* 全局变量都集中放置于代码顶部。
|
||||||
* 变量名使用小驼峰命名法。
|
* 变量名使用小驼峰命名法。
|
||||||
* 常量名使用全大写。
|
* 常量名使用全大写。
|
||||||
* 状态类变量命名参考 `isLogin`、`isOpen`。
|
* 状态类变量命名参考 `isLogin`、`isOpen`。
|
||||||
* 事件类方法命名参考 `onClick`、`onSelect`。
|
* 事件类方法命名参考 `onClick`、`onSelect`。
|
||||||
* 变量都应该写有注释说明、类型说明。
|
* 变量都应该写有注释说明、类型说明。
|
||||||
* `Promise` 方法使用 `async` `await` 写法,并进行容错处理。
|
* 所有 `Promise` 类方法使用 `async` `await` 写法,避免出现 `.then` 嵌套,并进行容错、错误抛出处理。
|
||||||
|
* 在需要页面跳转、提示、加载、本地存储、或其他功能的时候,优先使用工具函数 `common/utils/tool.js` 中存在的函数。
|
||||||
* 字符串拼接使用ES6的模板语法。
|
* 字符串拼接使用ES6的模板语法。
|
||||||
* JavaScript规范应遵循项目中已有的风格。
|
* 组件使用 Vue3 的 Composition API (setup语法糖) 编写。
|
||||||
|
|
||||||
## 静态资源
|
## 静态资源
|
||||||
|
|
||||||
* 静态资源变量 `ASSETSURL` 已进行全局混入,可以在 `<template></template>` 中直接使用。
|
* 静态资源变量 `ASSETSURL` 已进行全局混入,可以在 `<template></template>` 中直接使用。
|
||||||
* 所有静态资源URL应该使用 `ASSETSURL` 进行拼接,如:`${ASSETSURL}simple.png`。
|
* 所有静态资源URL应该使用 `ASSETSURL` 进行拼接,使用方式为:`${ASSETSURL}simple.png`、`background-image: url($ASSETSURL + 'b23bbf0c4c8e59f88f8fd883cb5d6b27.png')`。
|
||||||
|
|
||||||
## 工具函数 (tool.js)
|
## 工具函数 (tool.js)
|
||||||
|
|
||||||
@@ -105,26 +143,74 @@ npm install
|
|||||||
* **页面跳转**: `navigateTo`, `redirectTo`, `reLaunch`, `switchTab`, `navigateBack`
|
* **页面跳转**: `navigateTo`, `redirectTo`, `reLaunch`, `switchTab`, `navigateBack`
|
||||||
* **本地存储**: `storage`, `removeStorage`, `getStorageInfo`
|
* **本地存储**: `storage`, `removeStorage`, `getStorageInfo`
|
||||||
* **其他功能**: `copy` (复制文本), `saveImageToPhotos` (保存图片), `requestPayment` (微信支付), `upload` (文件上传), `loadFont` (加载字体)
|
* **其他功能**: `copy` (复制文本), `saveImageToPhotos` (保存图片), `requestPayment` (微信支付), `upload` (文件上传), `loadFont` (加载字体)
|
||||||
|
* **日期处理**: `formatDate` (日期格式化)
|
||||||
|
* **工具函数**: `deepClone` (深拷贝), `debounce/throttle` (防抖节流)
|
||||||
|
* **表单验证**: `getValidator` (表单验证工具)
|
||||||
|
* **主题切换**: `toggleDarkMode`, `getCurrentTheme` (暗黑模式切换)
|
||||||
|
* **权限管理**: `hasPermission`, `setPermissions`, `getUserPermissions` (权限检查和管理)
|
||||||
|
|
||||||
## 网络请求
|
## 网络请求
|
||||||
|
|
||||||
* 网络请求使用 `lib/luch-request` 库进行封装。
|
* 网络请求使用 `lib/luch-request` 库进行封装。
|
||||||
* 全局配置在 `api/request.js` 中定义,包括基础URL、请求头、SSL验证等。
|
* 全局配置在 `api/request.js` 中定义,包括基础URL、请求头、SSL验证等。
|
||||||
* 包含请求和响应拦截器,用于处理通用逻辑(如错误提示、鉴权等)。
|
* 包含请求和响应拦截器,用于处理通用逻辑(如错误提示、鉴权等)。
|
||||||
|
* 支持请求缓存机制和重试机制。
|
||||||
* 各业务板块的接口都应存放在 `api/modules` 下,并将单个接口进行导出以便页面按需导入。
|
* 各业务板块的接口都应存放在 `api/modules` 下,并将单个接口进行导出以便页面按需导入。
|
||||||
|
* 提供 `useGet` 和 `usePost` 等 Hooks 用于在组件中便捷地发起请求。
|
||||||
|
|
||||||
|
## 自定义Hooks
|
||||||
|
|
||||||
|
项目提供了多个可复用的 Vue Composition API Hooks,位于 `hooks/` 目录下:
|
||||||
|
|
||||||
|
* `useRequest`: 通用请求Hook,支持自动执行、手动执行、loading状态等
|
||||||
|
* `useGet`: GET请求Hook
|
||||||
|
* `usePost`: POST请求Hook
|
||||||
|
* `useState`: 状态管理Hook
|
||||||
|
* `useStorageState`: 本地存储状态Hook
|
||||||
|
* `useFormState`: 表单状态Hook
|
||||||
|
|
||||||
## 组件
|
## 组件
|
||||||
|
|
||||||
* 项目集成了 `uView-Plus` 和 `z-paging` 两个组件库,组件均已全局导入,可以直接使用。
|
* 项目集成了 `uView-Plus` 和 `z-paging` 两个组件库。
|
||||||
|
* 所有 `uni_modules` 目录中的组件无需导入直接可以进行使用。
|
||||||
* `uView-Plus` 组件已通过 `easycom` 自动导入,可以直接使用,如:`<u-button>`、`<u-icon>`。
|
* `uView-Plus` 组件已通过 `easycom` 自动导入,可以直接使用,如:`<u-button>`、`<u-icon>`。
|
||||||
* 全局组件放在 `components/` 目录下。
|
* 自定义公共组件放在 `components/common/` 目录下,采用 Vue3 的 setup 语法糖编写。
|
||||||
* 页面独立组件放在页面根目录下的 `components/`。
|
* 页面独立组件放在页面根目录下的 `components/`。
|
||||||
* 每个组件应该附带 `README.MD` 文档。
|
* 微信的原生组件放在页面根目录下的 `wxcomponents/`,并在使用了组件的对应页面路由配置中添加组件的引用属性 `"usingComponents": { "components": "/wxcomponents/components/components" }`。
|
||||||
* 组件编写应遵循项目中已有的风格。
|
* 组件编写应遵循项目中已有的风格。
|
||||||
|
|
||||||
|
## 自定义指令
|
||||||
|
|
||||||
|
* 项目支持自定义指令,位于 `directives/` 目录下
|
||||||
|
* 包括防重复点击、长按、权限控制、拖拽等指令
|
||||||
|
* 在 `main.js` 中已全局注册
|
||||||
|
|
||||||
|
## 常量管理
|
||||||
|
|
||||||
|
* 项目常量统一管理在 `common/constants/` 目录下
|
||||||
|
* 包括应用信息、页面路径、存储键名、事件常量等
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
* 环境变量通过 `.env` 文件配置
|
||||||
|
* 提供 `common/utils/env.js` 工具进行环境变量验证和获取
|
||||||
|
|
||||||
## 页面
|
## 页面
|
||||||
|
|
||||||
* 页面配置在 `pages.json` 中管理。
|
* 页面配置在 `pages.json` 中管理。
|
||||||
* 主包页面放在 `pages/` 目录下,分包页面放在 `subPages/` 目录下。
|
* 主包页面放在 `pages/` 目录下,分包页面放在 `subPages/` 目录下,如果页面不属于一级页面且没有包含在 `pages.json` 中的 `tabbar`,则应该放置在分包目录下。
|
||||||
* 页面使用 Composition API (setup语法糖) 编写。
|
* 页面使用 Composition API (setup语法糖) 编写。
|
||||||
* 注释、结构规范应遵循项目中已有的风格。
|
* 注释、结构规范应遵循项目中已有的风格。
|
||||||
|
|
||||||
|
## 代码提交规范
|
||||||
|
|
||||||
|
* 提交信息应清晰描述变更内容,如 `修复 搜索功能空值检查` 或 `新增 删除按钮功能`。
|
||||||
|
* 对于功能性的新增或修改,使用 `新增` 前缀。
|
||||||
|
* 对于错误修复,使用 `修复` 前缀。
|
||||||
|
* 对于性能优化、代码重构(既不修复错误也不添加功能),使用 `优化` 前缀。
|
||||||
|
* 对于文档更新,使用 `文档` 前缀。
|
||||||
|
* 提交信息应使用中文。
|
||||||
|
|
||||||
|
## 其他
|
||||||
|
|
||||||
|
* 页面中的分享功能应该使用原生的微信分享功能,通过 `button` 或 `<u-button>` 组件的 `open-type="share"` 属性实现。
|
||||||
205
README.md
205
README.md
@@ -1,80 +1,159 @@
|
|||||||
# <EFBFBD><EFBFBD>Ŀģ<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD>
|
# UniApp 微信小程序快速开发模板
|
||||||
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD>ǻ<EFBFBD><EFBFBD><EFBFBD> UniApp + Vue3 + uView-Plus <EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀģ<EFBFBD>壬<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һЩ<EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>á<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
本模板是基于 UniApp + Vue3 + uView-Plus 的微信小程序项目模板,提供了一些常用的工具函数、组件和最佳实践。
|
||||||
|
|
||||||
## Ŀ¼<EFBFBD>ṹ
|
## 目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> api/ # <EFBFBD>ӿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── api/ # 接口相关
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> modules/ # ҵ<EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD>
|
│ ├── modules/ # 业务接口
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> request.js # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ
|
│ └── request.js # 请求封装
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> common/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
|
├── common/ # 公共资源
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> styles/ # ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ
|
│ ├── styles/ # 全局样式
|
||||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> common.css # codefunԭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ
|
│ │ ├── common.css # codefun原子类样式
|
||||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> base.scss # ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
│ │ └── base.scss # 全局样式变量
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> utils/ # <EFBFBD><EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD>
|
│ └── utils/ # 工具函数
|
||||||
<EFBFBD><EFBFBD> <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> tool.ts # <EFBFBD><EFBFBD><EFBFBD>ù<EFBFBD><EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD>
|
│ └── tool.js # 常用工具函数
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> components/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── components/ # 公共组件
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uni_modules/ # uni-app <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── uni_modules/ # uni-app 组件
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> z-paging/ # <EFBFBD><EFBFBD>ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
│ └── z-paging/ # 分页组件库
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> lib/ # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── lib/ # 第三方库
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> luch-request/ # luch-request <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
│ └── luch-request/ # luch-request 网络请求库
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uview-plus/ # uView-Plus <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── uview-plus/ # uView-Plus 组件库
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> mixins/ # Vue <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── mixins/ # Vue 混入
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> global.ts # ȫ<EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD>
|
│ └── global.js # 全局混入
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> store/ # ״̬<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── store/ # 状态管理
|
||||||
<EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> index.ts # Vuex store
|
├── pages/ # 主包页面
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pages/ # <20><><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3>
|
├── subPages/ # 分包页面
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> subPages/ # <EFBFBD>ְ<EFBFBD>ҳ<EFBFBD><EFBFBD>
|
├── App.vue # 应用入口
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> App.vue # Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── main.js # 主入口文件
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> main.js # <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
├── pages.json # 页面配置
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> pages.json # ҳ<><D2B3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── manifest.json # 应用配置
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> manifest.json # Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── uni.scss # 全局样式变量
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> uni.scss # ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD>
|
├── vite.config.js # Vite 编译配置
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> vite.config.js # Vite <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
├── .nvmdrc # Node.js 版本要求
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .nvmdrc # Node.js <20>汾Ҫ<E6B1BE><D2AA>
|
└── .env # 环境变量
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> .env # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>
|
## 使用方法
|
||||||
|
|
||||||
1. <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD>Ŀ¼<EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĿĿ¼<EFBFBD><EFBFBD>
|
1. 将模板目录复制到你的项目目录中
|
||||||
2. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD> package.json <EFBFBD>е<EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><EFBFBD><EFBFBD>ƺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
2. 根据需要修改 package.json 中的项目名称和描述
|
||||||
3. ʹ<EFBFBD><EFBFBD> npm install <EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
3. 使用 npm install 安装依赖
|
||||||
4. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD> pages.json <EFBFBD>е<EFBFBD>ҳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
4. 根据需要修改 pages.json 中的页面配置
|
||||||
5. <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¹<EFBFBD><EFBFBD><EFBFBD>
|
5. 开始构建你的新项目
|
||||||
|
|
||||||
## <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
## 开发环境与运行
|
||||||
|
|
||||||
### <EFBFBD><EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
### 环境要求
|
||||||
- **dotenv** - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><D7A2>
|
|
||||||
|
|
||||||
### <20><>ʽ
|
* Node.js (版本信息在 `.nvmdrc` 文件中指定,当前为 20.0.0)
|
||||||
|
* npm 或 yarn
|
||||||
|
|
||||||
- common.css: ȫ<>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD><EFBFBD>ʽ
|
### 安装依赖
|
||||||
- base.scss: <20><><EFBFBD>õ<EFBFBD> SCSS <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
|
|
||||||
### <20><><EFBFBD>ߺ<EFBFBD><DFBA><EFBFBD> (tool.js)
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
- alert: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ
|
### 运行项目
|
||||||
- loading/hideLoading: <20><>ʾ/<2F><><EFBFBD>ؼ<EFBFBD><D8BC><EFBFBD><EFBFBD><EFBFBD>ʾ
|
|
||||||
- ҳ<><D2B3><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD>ط<EFBFBD><D8B7><EFBFBD>: navigateTo, redirectTo, reLaunch, switchTab, navigateBack
|
|
||||||
- <20><><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: storage, removeStorage, getStorageInfo
|
|
||||||
- copy: <20><><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
- saveImageToPhotos: <20><><EFBFBD><EFBFBD>ͼƬ<CDBC><C6AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
- requestPayment: <><CEA2>֧<EFBFBD><D6A7>
|
|
||||||
- upload: <20>ļ<EFBFBD><C4BC>ϴ<EFBFBD>
|
|
||||||
|
|
||||||
### <20><><EFBFBD><EFBFBD>
|
由于这是一个 UniApp 项目,通常需要使用 HBuilderX 或其他支持 UniApp 的 IDE 来运行和调试。具体的运行命令取决于你的开发环境。
|
||||||
|
|
||||||
- App.vue: ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD>ͻ<EFBFBD><CDBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
### 构建项目
|
||||||
- main.js: Vue Ӧ<>ó<EFBFBD>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ȫ<EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
- pages.json: ҳ<><D2B3>·<EFBFBD>ɺʹ<C9BA><CDB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
- uni.scss: ȫ<><C8AB><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD>
|
|
||||||
|
|
||||||
## ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
同样,构建项目也需要使用 HBuilderX 或相应的 CLI 工具。
|
||||||
|
|
||||||
1. <20><><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
## 项目特性
|
||||||
2. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD>Ļ<DEB8><C4BB><EFBFBD>չ<EFBFBD><D5B9><EFBFBD>ߺ<EFBFBD><DFBA><EFBFBD>
|
|
||||||
3. <20><><EFBFBD><EFBFBD><EFBFBD>ɸ<EFBFBD><C9B8><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD>Ļ<DEB8><C4BB>滻
|
### 核心依赖
|
||||||
4. <20><>ʽ<EFBFBD>ļ<EFBFBD><C4BC>ɸ<EFBFBD><C9B8><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD>ƹ淶<C6B9><E6B7B6><EFBFBD>е<EFBFBD><D0B5><EFBFBD>
|
- **dotenv** - 环境变量注入
|
||||||
|
|
||||||
|
### 样式系统
|
||||||
|
|
||||||
|
- common.css: 全局原子类样式
|
||||||
|
- base.scss: 实用的 SCSS 工具类
|
||||||
|
|
||||||
|
### 工具函数 (tool.js)
|
||||||
|
|
||||||
|
- alert: 文字轻提示
|
||||||
|
- loading/hideLoading: 显示/隐藏加载提示
|
||||||
|
- 页面跳转方法: navigateTo, redirectTo, reLaunch, switchTab, navigateBack
|
||||||
|
- 本地存储: storage, removeStorage, getStorageInfo
|
||||||
|
- copy: 复制文本到剪贴板
|
||||||
|
- saveImageToPhotos: 保存图片到相册
|
||||||
|
- requestPayment: 微信支付
|
||||||
|
- upload: 文件上传
|
||||||
|
- formatDate: 日期格式化
|
||||||
|
- deepClone: 深拷贝
|
||||||
|
- debounce/throttle: 防抖节流
|
||||||
|
- getValidator: 表单验证工具
|
||||||
|
- toggleDarkMode/getCurrentTheme: 暗黑模式切换
|
||||||
|
|
||||||
|
### 网络请求
|
||||||
|
|
||||||
|
- 基于 luch-request 的封装
|
||||||
|
- 自动添加 token
|
||||||
|
- 统一错误处理
|
||||||
|
- 请求缓存机制
|
||||||
|
- 请求重试机制
|
||||||
|
|
||||||
|
### 组件
|
||||||
|
|
||||||
|
- uView-Plus 组件库
|
||||||
|
- z-paging 分页组件
|
||||||
|
|
||||||
|
## 代码规范与开发约定
|
||||||
|
|
||||||
|
### 样式
|
||||||
|
|
||||||
|
- 全局样式文件位于 `common/styles/` 目录下
|
||||||
|
- 遵循项目中已有的风格
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
|
||||||
|
- 严格遵循ES6规范
|
||||||
|
- 遵循JavaScript函数式编程范式
|
||||||
|
- 方法类函数应该使用 `function` 进行定义
|
||||||
|
- 页面的生命周期需要通过 `@dcloudio/uni-app` 依赖进行按需导入
|
||||||
|
- 全局变量都集中放置于代码顶部
|
||||||
|
- 变量名使用小驼峰命名法
|
||||||
|
- 常量名使用全大写
|
||||||
|
- 所有 `Promise` 类方法使用 `async` `await` 写法
|
||||||
|
- 在需要页面跳转、提示、加载、本地存储等功能的时候,优先使用工具函数
|
||||||
|
- 字符串拼接使用ES6的模板语法
|
||||||
|
|
||||||
|
### 静态资源
|
||||||
|
|
||||||
|
- 静态资源变量 `ASSETSURL` 已进行全局混入,可以在 `<template></template>` 中直接使用
|
||||||
|
|
||||||
|
### 网络请求
|
||||||
|
|
||||||
|
- 网络请求使用 `lib/luch-request` 库进行封装
|
||||||
|
- 包含请求和响应拦截器,用于处理通用逻辑
|
||||||
|
- 各业务板块的接口都应存放在 `api/modules` 下
|
||||||
|
|
||||||
|
### 组件
|
||||||
|
|
||||||
|
- 所有 `uni_modules` 目录中的组件无需导入直接可以进行使用
|
||||||
|
- `uView-Plus` 组件已通过 `easycom` 自动导入
|
||||||
|
|
||||||
|
### 页面
|
||||||
|
|
||||||
|
- 页面配置在 `pages.json` 中管理
|
||||||
|
- 页面使用 Composition API (setup语法糖) 编写
|
||||||
|
|
||||||
|
## 代码提交规范
|
||||||
|
|
||||||
|
- 提交信息应清晰描述变更内容
|
||||||
|
- 对于功能性的新增或修改,使用 `新增` 前缀
|
||||||
|
- 对于错误修复,使用 `修复` 前缀
|
||||||
|
- 对于性能优化、代码重构,使用 `优化` 前缀
|
||||||
|
- 对于文档更新,使用 `文档` 前缀
|
||||||
|
- 提交信息应使用中文
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 请根据实际项目需求进行修改和扩展功能
|
||||||
|
2. 代码文件可按项目规范进行修改替换
|
||||||
|
3. 样式文件可按项目规范进行修改
|
||||||
15
api/index.js
Normal file
15
api/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* API统一导出入口
|
||||||
|
*/
|
||||||
|
// 导入网络请求实例
|
||||||
|
import http from './request.js'
|
||||||
|
// 导入各业务模块
|
||||||
|
import user from './modules/user.js'
|
||||||
|
// 导出网络请求实例
|
||||||
|
export { http }
|
||||||
|
// 导出各业务模块
|
||||||
|
export { user }
|
||||||
|
// 默认导出包含所有模块的对象
|
||||||
|
export default {
|
||||||
|
user,
|
||||||
|
}
|
||||||
85
api/modules/user.js
Normal file
85
api/modules/user.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import http from '@/api/request.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户相关API模块
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param {Object} data 登录数据
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
login(data) {
|
||||||
|
return http.post('/user/login', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
* @param {Object} data 注册数据
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
register(data) {
|
||||||
|
return http.post('/user/register', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
getUserInfo() {
|
||||||
|
return http.get('/user/info')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
* @param {Object} data 用户信息数据
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
updateUserInfo(data) {
|
||||||
|
return http.put('/user/info', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
* @param {Object} data 密码数据
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
changePassword(data) {
|
||||||
|
return http.post('/user/change-password', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
logout() {
|
||||||
|
return http.post('/user/logout')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户列表
|
||||||
|
* @param {Object} params 查询参数
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
getUserList(params) {
|
||||||
|
return http.get('/user/list', { params })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户详情
|
||||||
|
* @param {number} userId 用户ID
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
getUserDetail(userId) {
|
||||||
|
return http.get(`/user/${userId}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户
|
||||||
|
* @param {number} userId 用户ID
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
deleteUser(userId) {
|
||||||
|
return http.delete(`/user/${userId}`)
|
||||||
|
},
|
||||||
|
}
|
||||||
147
api/request.js
147
api/request.js
@@ -1,9 +1,46 @@
|
|||||||
import Request from '@/lib/luch-request/index.js'
|
import Request from '@/lib/luch-request/index.js'
|
||||||
|
|
||||||
|
import tool from '@/common/utils/tool.js'
|
||||||
|
|
||||||
const baseUrl = import.meta.env.VITE_BASE_URL
|
import EnvConfig from '@/common/utils/env.js'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const baseUrl = EnvConfig.BASE_URL
|
||||||
|
|
||||||
|
const http = new Request()
|
||||||
|
// 缓存存储
|
||||||
|
const cache = new Map()
|
||||||
|
// 缓存过期时间(毫秒),默认5分钟
|
||||||
|
const CACHE_EXPIRATION = 5 * 60 * 1000
|
||||||
|
// 缓存工具方法
|
||||||
|
const cacheUtils = {
|
||||||
|
// 生成缓存key
|
||||||
|
generateKey(config) {
|
||||||
|
return `${config.method}:${config.url}:${JSON.stringify(config.data || {})}:${JSON.stringify(config.params || {})}`
|
||||||
|
},
|
||||||
|
// 获取缓存
|
||||||
|
get(key) {
|
||||||
|
const cached = cache.get(key)
|
||||||
|
if (!cached) return null
|
||||||
|
// 检查是否过期
|
||||||
|
if (Date.now() - cached.timestamp > CACHE_EXPIRATION) {
|
||||||
|
cache.delete(key)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return cached.data
|
||||||
|
},
|
||||||
|
// 设置缓存
|
||||||
|
set(key, data) {
|
||||||
|
cache.set(key, {
|
||||||
|
data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 清除缓存
|
||||||
|
clear() {
|
||||||
|
cache.clear()
|
||||||
|
},
|
||||||
// 清除指定缓存
|
// 清除指定缓存
|
||||||
delete(key) {
|
delete(key) {
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
@@ -11,31 +48,115 @@ http.setConfig(config => {
|
|||||||
}
|
}
|
||||||
/* 设置全局配置 */
|
/* 设置全局配置 */
|
||||||
http.setConfig(config => {
|
http.setConfig(config => {
|
||||||
|
|
||||||
config.header = { ...config.header }
|
config.header = { ...config.header }
|
||||||
config.sslVerify = false
|
config.sslVerify = false
|
||||||
config.baseURL = baseUrl
|
config.baseURL = baseUrl
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
http.interceptors.request.use(
|
||||||
|
async config => {
|
||||||
|
config.header = { ...config.header }
|
||||||
|
// 自动添加token
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
if (token) {
|
||||||
|
config.header.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
// 添加通用请求头
|
||||||
|
config.header['Content-Type'] = 'application/json'
|
||||||
|
// 检查缓存
|
||||||
|
if (config.method === 'GET' && config.cache !== false) {
|
||||||
|
const cacheKey = cacheUtils.generateKey(config)
|
||||||
|
const cachedData = cacheUtils.get(cacheKey)
|
||||||
|
if (cachedData) {
|
||||||
|
// 如果有缓存,直接返回缓存数据
|
||||||
|
return Promise.resolve({
|
||||||
data: cachedData,
|
data: cachedData,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
|
},
|
||||||
http.interceptors.response.use(response => {
|
config => {
|
||||||
if (response.statusCode == 500 || response.statusCode == 404 || response.statusCode == 403) {
|
return Promise.reject(config)
|
||||||
console.error(response)
|
}
|
||||||
return tool.alert('网络错误,请稍后重试')
|
)
|
||||||
|
http.interceptors.response.use(
|
||||||
|
response => {
|
||||||
// 网络错误处理
|
// 网络错误处理
|
||||||
|
if (response.statusCode >= 500) {
|
||||||
if (response.statusCode == 401 || response.data.code == 401) {
|
console.error('服务器错误:', response)
|
||||||
|
tool.alert('服务器错误,请稍后重试')
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
if (response.statusCode === 404) {
|
||||||
|
console.error('接口不存在:', response)
|
||||||
|
tool.alert('接口不存在')
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
if (response.statusCode === 403) {
|
||||||
|
console.error('无权限访问:', response)
|
||||||
|
tool.alert('无权限访问')
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
// 未授权处理
|
||||||
|
if (response.statusCode === 401 || response.data?.code === 401) {
|
||||||
|
console.error('未授权或登录过期:', response)
|
||||||
|
tool.alert('登录已过期,请重新登录')
|
||||||
|
// 清除本地token
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
// 跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({ url: '/pages/login/login' }) // 根据实际登录页路径调整
|
||||||
|
}, 1500)
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
// 业务错误处理
|
||||||
|
if (response.data && response.data.code !== undefined && response.data.code !== 200) {
|
||||||
|
const message = response.data.message || response.data.msg || '业务错误'
|
||||||
|
console.error('业务错误:', message)
|
||||||
|
tool.alert(message)
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
// 成功响应
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// 缓存GET请求的响应数据
|
// 缓存GET请求的响应数据
|
||||||
if (response.config && response.config.method === 'GET' && response.config.cache !== false) {
|
if (response.config && response.config.method === 'GET' && response.config.cache !== false) {
|
||||||
const cacheKey = cacheUtils.generateKey(response.config)
|
const cacheKey = cacheUtils.generateKey(response.config)
|
||||||
cacheUtils.set(cacheKey, response.data)
|
cacheUtils.set(cacheKey, response.data)
|
||||||
|
}
|
||||||
|
return Promise.resolve(response)
|
||||||
|
} else {
|
||||||
|
return Promise.reject(response)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('请求失败:', error)
|
||||||
|
if (error.errMsg) {
|
||||||
|
// 网络错误
|
||||||
|
tool.alert('网络连接失败,请检查网络')
|
||||||
|
} else {
|
||||||
|
tool.alert('请求失败,请稍后重试')
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 添加缓存功能到http实例
|
||||||
|
http.cache = cacheUtils
|
||||||
|
// 添加重试方法
|
||||||
|
http.retry = async function (config, retries = 3, delay = 1000) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const attempt = async retryCount => {
|
||||||
|
try {
|
||||||
|
const response = await this.request(config)
|
||||||
|
resolve(response)
|
||||||
|
} catch (error) {
|
||||||
|
if (retryCount <= 0) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
// 延迟后重试
|
||||||
|
setTimeout(() => {
|
||||||
|
attempt(retryCount - 1)
|
||||||
}, delay)
|
}, delay)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
common/constants/app.js
Normal file
30
common/constants/app.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 应用相关常量
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 应用信息
|
||||||
|
export const APP_NAME = '小程序模板'
|
||||||
|
export const APP_VERSION = '1.0.0'
|
||||||
|
|
||||||
|
// 页面路径常量
|
||||||
|
export const PAGE_PATHS = {
|
||||||
|
INDEX: '/pages/index/index',
|
||||||
|
WELCOME: '/subPages/welcome/index',
|
||||||
|
LOGIN: '/pages/login/login', // 示例路径
|
||||||
|
REGISTER: '/pages/register/register' // 示例路径
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储键名常量
|
||||||
|
export const STORAGE_KEYS = {
|
||||||
|
TOKEN: 'token',
|
||||||
|
USER_INFO: 'userInfo',
|
||||||
|
THEME: 'theme',
|
||||||
|
LANG: 'language'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件常量
|
||||||
|
export const EVENTS = {
|
||||||
|
USER_LOGIN: 'userLogin',
|
||||||
|
USER_LOGOUT: 'userLogout',
|
||||||
|
THEME_CHANGE: 'themeChange'
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// 定义内外边距,历遍1-40
|
// 定义内外边距,只生成常用的数值
|
||||||
@for $i from 0 through 40 {
|
$spacing-sizes: (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100);
|
||||||
// 只要双数和能被5除尽的数
|
@each $i in $spacing-sizes {
|
||||||
@if $i % 2==0 or $i % 5==0 {
|
|
||||||
// 得出:u-margin-30或者u-m-30
|
|
||||||
.w-#{$i} {
|
.w-#{$i} {
|
||||||
width: calc($i * 1%) !important;
|
width: calc($i * 1%) !important;
|
||||||
}
|
}
|
||||||
@@ -10,76 +8,60 @@
|
|||||||
padding-left: $i + rpx !important;
|
padding-left: $i + rpx !important;
|
||||||
padding-right: $i + rpx !important;
|
padding-right: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-y-#{$i} {
|
.p-y-#{$i} {
|
||||||
padding-top: $i + rpx !important;
|
padding-top: $i + rpx !important;
|
||||||
padding-bottom: $i + rpx !important;
|
padding-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-x-#{$i} {
|
.m-x-#{$i} {
|
||||||
margin-left: $i + rpx !important;
|
margin-left: $i + rpx !important;
|
||||||
margin-right: $i + rpx !important;
|
margin-right: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-y-#{$i} {
|
.m-y-#{$i} {
|
||||||
margin-top: $i + rpx !important;
|
margin-top: $i + rpx !important;
|
||||||
margin-bottom: $i + rpx !important;
|
margin-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-#{$i} {
|
.m-#{$i} {
|
||||||
margin-left: $i + rpx !important;
|
margin-left: $i + rpx !important;
|
||||||
margin-right: $i + rpx !important;
|
margin-right: $i + rpx !important;
|
||||||
margin-top: $i + rpx !important;
|
margin-top: $i + rpx !important;
|
||||||
margin-bottom: $i + rpx !important;
|
margin-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-#{$i} {
|
.p-#{$i} {
|
||||||
padding-left: $i + rpx !important;
|
padding-left: $i + rpx !important;
|
||||||
padding-right: $i + rpx !important;
|
padding-right: $i + rpx !important;
|
||||||
padding-top: $i + rpx !important;
|
padding-top: $i + rpx !important;
|
||||||
padding-bottom: $i + rpx !important;
|
padding-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-l-#{$i} {
|
.m-l-#{$i} {
|
||||||
margin-left: $i + rpx !important;
|
margin-left: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-t-#{$i} {
|
.m-t-#{$i} {
|
||||||
margin-top: $i + rpx !important;
|
margin-top: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-r-#{$i} {
|
.m-r-#{$i} {
|
||||||
margin-right: $i + rpx !important;
|
margin-right: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-b-#{$i} {
|
.m-b-#{$i} {
|
||||||
margin-bottom: $i + rpx !important;
|
margin-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-l-#{$i} {
|
.p-l-#{$i} {
|
||||||
padding-left: $i + rpx !important;
|
padding-left: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-t-#{$i} {
|
.p-t-#{$i} {
|
||||||
padding-top: $i + rpx !important;
|
padding-top: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-r-#{$i} {
|
.p-r-#{$i} {
|
||||||
padding-right: $i + rpx !important;
|
padding-right: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-b-#{$i} {
|
.p-b-#{$i} {
|
||||||
padding-bottom: $i + rpx !important;
|
padding-bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-p-#{$i} {
|
.l-p-#{$i} {
|
||||||
letter-spacing: $i + rpx !important;
|
letter-spacing: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.z-i-#{$i} {
|
.z-i-#{$i} {
|
||||||
z-index: $i;
|
z-index: $i;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-h-#{$i} {
|
.l-h-#{$i} {
|
||||||
line-height: $i + rpx !important;
|
line-height: $i + rpx !important;
|
||||||
}
|
}
|
||||||
@@ -89,7 +71,6 @@
|
|||||||
.l-#{$i} {
|
.l-#{$i} {
|
||||||
left: $i + rpx !important;
|
left: $i + rpx !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.t-#{$i} {
|
.t-#{$i} {
|
||||||
top: $i + rpx !important;
|
top: $i + rpx !important;
|
||||||
}
|
}
|
||||||
@@ -97,22 +78,20 @@
|
|||||||
bottom: $i + rpx !important;
|
bottom: $i + rpx !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// 定义字体(rpx)单位,只生成常用的字体大小
|
||||||
|
$font-sizes: (9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 52, 56, 60);
|
||||||
// 定义字体(rpx)单位,大于或等于20的都为rpx单位字体
|
@each $i in $font-sizes {
|
||||||
@for $i from 9 through 60 {
|
|
||||||
.font-#{$i} {
|
.font-#{$i} {
|
||||||
font-size: $i + rpx !important;
|
font-size: $i + rpx !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 圆角
|
// 圆角
|
||||||
@for $i from 4 through 60 {
|
$rounded-sizes: (4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60);
|
||||||
|
@each $i in $rounded-sizes {
|
||||||
.rounded-#{$i} {
|
.rounded-#{$i} {
|
||||||
border-radius: $i + rpx;
|
border-radius: $i + rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多行文本溢出
|
// 多行文本溢出
|
||||||
@for $i from 1 through 5 {
|
@for $i from 1 through 5 {
|
||||||
.over-line-#{$i} {
|
.over-line-#{$i} {
|
||||||
@@ -125,3 +104,120 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 显示相关工具类
|
||||||
|
.display-none {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.display-block {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.display-inline {
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
.display-inline-block {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
.display-flex {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
// 浮动相关
|
||||||
|
.float-left {
|
||||||
|
float: left !important;
|
||||||
|
}
|
||||||
|
.float-right {
|
||||||
|
float: right !important;
|
||||||
|
}
|
||||||
|
.float-none {
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
.clearfix::after {
|
||||||
|
content: '';
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
// 文本对齐
|
||||||
|
.text-left {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
.text-right {
|
||||||
|
text-align: right !important;
|
||||||
|
}
|
||||||
|
.text-justify {
|
||||||
|
text-align: justify !important;
|
||||||
|
}
|
||||||
|
// 文本装饰
|
||||||
|
.text-underline {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
.text-line-through {
|
||||||
|
text-decoration: line-through !important;
|
||||||
|
}
|
||||||
|
.text-no-underline {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
// 字体粗细
|
||||||
|
.font-normal {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
.font-bold {
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
// 文本大小写
|
||||||
|
.text-uppercase {
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
}
|
||||||
|
.text-lowercase {
|
||||||
|
text-transform: lowercase !important;
|
||||||
|
}
|
||||||
|
.text-capitalize {
|
||||||
|
text-transform: capitalize !important;
|
||||||
|
}
|
||||||
|
// 垂直对齐
|
||||||
|
.align-baseline {
|
||||||
|
vertical-align: baseline !important;
|
||||||
|
}
|
||||||
|
.align-top {
|
||||||
|
vertical-align: top !important;
|
||||||
|
}
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
.align-bottom {
|
||||||
|
vertical-align: bottom !important;
|
||||||
|
}
|
||||||
|
// 位置相关
|
||||||
|
.position-relative {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
.position-absolute {
|
||||||
|
position: absolute !important;
|
||||||
|
}
|
||||||
|
.position-fixed {
|
||||||
|
position: fixed !important;
|
||||||
|
}
|
||||||
|
.position-static {
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
// 溢出处理
|
||||||
|
.overflow-auto {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
|
.overflow-hidden {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
.overflow-visible {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
.overflow-scroll {
|
||||||
|
overflow: scroll !important;
|
||||||
|
}
|
||||||
|
// 可见性
|
||||||
|
.visible {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
|||||||
88
common/styles/dark-mode.scss
Normal file
88
common/styles/dark-mode.scss
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/* 暗黑模式样式 */
|
||||||
|
:root {
|
||||||
|
// 默认使用亮色模式变量
|
||||||
|
--uni-color-primary: #007aff;
|
||||||
|
--uni-color-success: #4cd964;
|
||||||
|
--uni-color-warning: #f0ad4e;
|
||||||
|
--uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
--uni-text-color: #333;
|
||||||
|
--uni-text-color-inverse: #fff;
|
||||||
|
--uni-text-color-grey: #999;
|
||||||
|
--uni-text-color-placeholder: #808080;
|
||||||
|
--uni-text-color-disable: #c0c0c0;
|
||||||
|
|
||||||
|
--uni-bg-color: #ffffff;
|
||||||
|
--uni-bg-color-grey: #f8f8f8;
|
||||||
|
--uni-bg-color-hover: #f1f1f1;
|
||||||
|
--uni-bg-color-mask: rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
--uni-border-color: #c8c7cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗黑模式 */
|
||||||
|
[data-theme='dark'] {
|
||||||
|
--uni-color-primary: #0a84ff;
|
||||||
|
--uni-color-success: #34c759;
|
||||||
|
--uni-color-warning: #ff9500;
|
||||||
|
--uni-color-error: #ff3b30;
|
||||||
|
|
||||||
|
--uni-text-color: #f2f2f7;
|
||||||
|
--uni-text-color-inverse: #000000;
|
||||||
|
--uni-text-color-grey: #8e8e93;
|
||||||
|
--uni-text-color-placeholder: #4c4c4c;
|
||||||
|
--uni-text-color-disable: #636366;
|
||||||
|
|
||||||
|
--uni-bg-color: #1c1c1e;
|
||||||
|
--uni-bg-color-grey: #2c2c2e;
|
||||||
|
--uni-bg-color-hover: #3a3a3c;
|
||||||
|
--uni-bg-color-mask: rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
--uni-border-color: #434346;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 系统级暗黑模式 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme='light']) {
|
||||||
|
--uni-color-primary: #0a84ff;
|
||||||
|
--uni-color-success: #34c759;
|
||||||
|
--uni-color-warning: #ff9500;
|
||||||
|
--uni-color-error: #ff3b30;
|
||||||
|
|
||||||
|
--uni-text-color: #f2f2f7;
|
||||||
|
--uni-text-color-inverse: #000000;
|
||||||
|
--uni-text-color-grey: #8e8e93;
|
||||||
|
--uni-text-color-placeholder: #4c4c4c;
|
||||||
|
--uni-text-color-disable: #636366;
|
||||||
|
|
||||||
|
--uni-bg-color: #1c1c1e;
|
||||||
|
--uni-bg-color-grey: #2c2c2e;
|
||||||
|
--uni-bg-color-hover: #3a3a3c;
|
||||||
|
--uni-bg-color-mask: rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
--uni-border-color: #434346;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 应用暗黑模式的通用类 */
|
||||||
|
.dark-mode {
|
||||||
|
background-color: var(--uni-bg-color);
|
||||||
|
color: var(--uni-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .dark-invert {
|
||||||
|
background-color: var(--uni-bg-color-inverse);
|
||||||
|
color: var(--uni-text-color-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用暗黑模式切换类 */
|
||||||
|
.light-mode {
|
||||||
|
background-color: var(--uni-bg-color);
|
||||||
|
color: var(--uni-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用暗黑模式切换类 */
|
||||||
|
.dark-mode {
|
||||||
|
background-color: var(--uni-bg-color);
|
||||||
|
color: var(--uni-text-color);
|
||||||
|
}
|
||||||
80
common/utils/env.js
Normal file
80
common/utils/env.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 环境变量验证工具
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 验证必需的环境变量
|
||||||
|
* @param {Array<string>} requiredVars 必需的环境变量键名数组
|
||||||
|
* @throws {Error} 如果有任何必需的环境变量缺失则抛出错误
|
||||||
|
*/
|
||||||
|
export function validateEnv(requiredVars = []) {
|
||||||
|
const missingVars = []
|
||||||
|
for (const key of requiredVars) {
|
||||||
|
if (!import.meta.env[key]) {
|
||||||
|
missingVars.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missingVars.length > 0) {
|
||||||
|
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取环境变量,带默认值和类型转换
|
||||||
|
* @param {string} key 环境变量键名
|
||||||
|
* @param {any} defaultValue 默认值
|
||||||
|
* @param {string} type 类型 ('string' | 'number' | 'boolean' | 'array')
|
||||||
|
* @returns {any} 环境变量值
|
||||||
|
*/
|
||||||
|
export function getEnv(key, defaultValue = null, type = 'string') {
|
||||||
|
const value = import.meta.env[key]
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'number':
|
||||||
|
const numValue = Number(value)
|
||||||
|
return isNaN(numValue) ? defaultValue : numValue
|
||||||
|
case 'boolean':
|
||||||
|
return value === 'true' || value === '1'
|
||||||
|
case 'array':
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)
|
||||||
|
} catch (e) {
|
||||||
|
return Array.isArray(defaultValue) ? defaultValue : []
|
||||||
|
}
|
||||||
|
case 'string':
|
||||||
|
default:
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 环境变量配置
|
||||||
|
*/
|
||||||
|
export const EnvConfig = {
|
||||||
|
// API基础URL
|
||||||
|
BASE_URL: getEnv('VITE_BASE_URL', '', 'string'),
|
||||||
|
// 静态资源URL
|
||||||
|
ASSETS_URL: getEnv('VITE_ASSETSURL', '', 'string'),
|
||||||
|
// 小程序APPID
|
||||||
|
APP_ID: getEnv('VITE_APPID', '', 'string'),
|
||||||
|
// UNI-APPID
|
||||||
|
UNI_APP_ID: getEnv('VITE_UNI_APPID', '', 'string'),
|
||||||
|
// 是否为开发环境
|
||||||
|
IS_DEV: getEnv('NODE_ENV', 'development', 'string') === 'development',
|
||||||
|
// 是否为生产环境
|
||||||
|
IS_PROD: getEnv('NODE_ENV', 'development', 'string') === 'production',
|
||||||
|
// 调试模式
|
||||||
|
DEBUG: getEnv('VITE_DEBUG', false, 'boolean'),
|
||||||
|
// API超时时间(毫秒)
|
||||||
|
API_TIMEOUT: getEnv('VITE_API_TIMEOUT', 10000, 'number'),
|
||||||
|
}
|
||||||
|
// 验证必需的环境变量
|
||||||
|
try {
|
||||||
|
validateEnv(['VITE_BASE_URL', 'VITE_APPID'])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('环境变量验证失败:', error.message)
|
||||||
|
// 在开发环境中抛出错误
|
||||||
|
if (EnvConfig.IS_DEV) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default EnvConfig
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
const baseUrl = import.meta.env.VITE_BASE_URL
|
const baseUrl = import.meta.env.VITE_BASE_URL
|
||||||
const assetsUrl = import.meta.env.VITE_ASSETSURL
|
const assetsUrl = import.meta.env.VITE_ASSETSURL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具类 - 提供常用的工具方法
|
* 工具类 - 提供常用的工具方法
|
||||||
* @class Tool
|
* @class Tool
|
||||||
@@ -13,11 +12,9 @@ class Tool {
|
|||||||
SUCCESS: 1,
|
SUCCESS: 1,
|
||||||
LOADING: 2,
|
LOADING: 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 字体加载状态缓存
|
// 字体加载状态缓存
|
||||||
this.loadedFonts = new Set()
|
this.loadedFonts = new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文字轻提示
|
* 文字轻提示
|
||||||
* @param {string} str 提示文字
|
* @param {string} str 提示文字
|
||||||
@@ -30,13 +27,11 @@ class Tool {
|
|||||||
console.warn('alert方法需要提供提示文字')
|
console.warn('alert方法需要提供提示文字')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
[this.ICON_TYPES.NONE]: 'none',
|
[this.ICON_TYPES.NONE]: 'none',
|
||||||
[this.ICON_TYPES.SUCCESS]: 'success',
|
[this.ICON_TYPES.SUCCESS]: 'success',
|
||||||
[this.ICON_TYPES.LOADING]: 'loading',
|
[this.ICON_TYPES.LOADING]: 'loading',
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: String(str),
|
title: String(str),
|
||||||
icon: iconMap[icon] || 'none',
|
icon: iconMap[icon] || 'none',
|
||||||
@@ -49,7 +44,6 @@ class Tool {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示loading加载
|
* 显示loading加载
|
||||||
* @param {string} [title=' '] 加载文案
|
* @param {string} [title=' '] 加载文案
|
||||||
@@ -58,14 +52,12 @@ class Tool {
|
|||||||
loading(title = ' ', mask = true) {
|
loading(title = ' ', mask = true) {
|
||||||
uni.showLoading({ title, mask })
|
uni.showLoading({ title, mask })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭loading提示框
|
* 关闭loading提示框
|
||||||
*/
|
*/
|
||||||
hideLoading() {
|
hideLoading() {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一处理URL格式,确保以/开头
|
* 统一处理URL格式,确保以/开头
|
||||||
* @param {string} url 页面地址
|
* @param {string} url 页面地址
|
||||||
@@ -76,17 +68,14 @@ class Tool {
|
|||||||
if (!url || typeof url !== 'string') {
|
if (!url || typeof url !== 'string') {
|
||||||
throw new Error('URL必须是字符串')
|
throw new Error('URL必须是字符串')
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.startsWith('/') ? url : `/${url}`
|
return url.startsWith('/') ? url : `/${url}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可返回跳转(导航到新页面)
|
* 可返回跳转(导航到新页面)
|
||||||
* @param {string} url 页面地址
|
* @param {string} url 页面地址
|
||||||
*/
|
*/
|
||||||
navigateTo(url) {
|
navigateTo(url) {
|
||||||
const formattedUrl = this._formatUrl(url)
|
const formattedUrl = this._formatUrl(url)
|
||||||
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: formattedUrl,
|
url: formattedUrl,
|
||||||
fail: err => {
|
fail: err => {
|
||||||
@@ -95,7 +84,6 @@ class Tool {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不可返回跳转(重定向到新页面)
|
* 不可返回跳转(重定向到新页面)
|
||||||
* @param {string} url 页面地址
|
* @param {string} url 页面地址
|
||||||
@@ -103,7 +91,6 @@ class Tool {
|
|||||||
redirectTo(url) {
|
redirectTo(url) {
|
||||||
uni.redirectTo({ url: this._formatUrl(url) })
|
uni.redirectTo({ url: this._formatUrl(url) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除页面栈跳转(重新启动到新页面)
|
* 清除页面栈跳转(重新启动到新页面)
|
||||||
* @param {string} url 页面地址
|
* @param {string} url 页面地址
|
||||||
@@ -111,7 +98,6 @@ class Tool {
|
|||||||
reLaunch(url) {
|
reLaunch(url) {
|
||||||
uni.reLaunch({ url: this._formatUrl(url) })
|
uni.reLaunch({ url: this._formatUrl(url) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跳转tabBar页
|
* 跳转tabBar页
|
||||||
* @param {string} url 页面地址
|
* @param {string} url 页面地址
|
||||||
@@ -119,7 +105,6 @@ class Tool {
|
|||||||
switchTab(url) {
|
switchTab(url) {
|
||||||
uni.switchTab({ url: this._formatUrl(url) })
|
uni.switchTab({ url: this._formatUrl(url) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回上一页面或指定页面
|
* 返回上一页面或指定页面
|
||||||
* @param {number} [delta=1] 返回的页面数
|
* @param {number} [delta=1] 返回的页面数
|
||||||
@@ -127,7 +112,6 @@ class Tool {
|
|||||||
*/
|
*/
|
||||||
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
|
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
|
|
||||||
if (pages.length <= 1) {
|
if (pages.length <= 1) {
|
||||||
console.warn('无上一页,使用回退地址')
|
console.warn('无上一页,使用回退地址')
|
||||||
uni.reLaunch({ url: fallbackUrl })
|
uni.reLaunch({ url: fallbackUrl })
|
||||||
@@ -135,7 +119,6 @@ class Tool {
|
|||||||
uni.navigateBack({ delta })
|
uni.navigateBack({ delta })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作本地缓存
|
* 操作本地缓存
|
||||||
* @param {string} key 缓存键值
|
* @param {string} key 缓存键值
|
||||||
@@ -146,24 +129,20 @@ class Tool {
|
|||||||
if (typeof key !== 'string') {
|
if (typeof key !== 'string') {
|
||||||
throw new Error('key必须是字符串')
|
throw new Error('key必须是字符串')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置操作
|
// 设置操作
|
||||||
if (value !== undefined && value !== null) {
|
if (value !== undefined && value !== null) {
|
||||||
uni.setStorageSync(key, value)
|
uni.setStorageSync(key, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取操作
|
// 读取操作
|
||||||
if (key !== '#') {
|
if (key !== '#') {
|
||||||
return uni.getStorageSync(key)
|
return uni.getStorageSync(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 特殊操作
|
// 特殊操作
|
||||||
if (key === '#') {
|
if (key === '#') {
|
||||||
uni.clearStorageSync()
|
uni.clearStorageSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除指定缓存
|
* 删除指定缓存
|
||||||
* @param {string} key 要删除的缓存键
|
* @param {string} key 要删除的缓存键
|
||||||
@@ -172,10 +151,8 @@ class Tool {
|
|||||||
if (typeof key !== 'string') {
|
if (typeof key !== 'string') {
|
||||||
throw new Error('key必须是字符串')
|
throw new Error('key必须是字符串')
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.removeStorageSync(key)
|
uni.removeStorageSync(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取缓存信息
|
* 获取缓存信息
|
||||||
* @returns {Object} 缓存信息
|
* @returns {Object} 缓存信息
|
||||||
@@ -183,7 +160,6 @@ class Tool {
|
|||||||
getStorageInfo() {
|
getStorageInfo() {
|
||||||
return uni.getStorageInfoSync()
|
return uni.getStorageInfoSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 复制文本到剪贴板
|
* 复制文本到剪贴板
|
||||||
* @param {string} data 要复制的文本
|
* @param {string} data 要复制的文本
|
||||||
@@ -194,7 +170,6 @@ class Tool {
|
|||||||
this.alert('暂无内容')
|
this.alert('暂无内容')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
@@ -203,7 +178,6 @@ class Tool {
|
|||||||
fail: reject,
|
fail: reject,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.alert('复制成功')
|
this.alert('复制成功')
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -212,7 +186,6 @@ class Tool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入外部字体
|
* 导入外部字体
|
||||||
* @param {string} fontName 字体文件名(不含路径)
|
* @param {string} fontName 字体文件名(不含路径)
|
||||||
@@ -222,15 +195,12 @@ class Tool {
|
|||||||
if (!fontName || typeof fontName !== 'string') {
|
if (!fontName || typeof fontName !== 'string') {
|
||||||
throw new Error('字体名称必须是字符串')
|
throw new Error('字体名称必须是字符串')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已加载过
|
// 检查是否已加载过
|
||||||
if (this.loadedFonts.has(fontName)) {
|
if (this.loadedFonts.has(fontName)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fontFamily = fontName.replace(/\.[^/.]+$/, '') // 移除文件扩展名
|
const fontFamily = fontName.replace(/\.[^/.]+$/, '') // 移除文件扩展名
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
uni.loadFontFace({
|
uni.loadFontFace({
|
||||||
family: fontFamily,
|
family: fontFamily,
|
||||||
@@ -240,7 +210,6 @@ class Tool {
|
|||||||
fail: reject,
|
fail: reject,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.loadedFonts.add(fontName)
|
this.loadedFonts.add(fontName)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -248,7 +217,6 @@ class Tool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存图片到相册
|
* 保存图片到相册
|
||||||
* @param {string} url 图片URL
|
* @param {string} url 图片URL
|
||||||
@@ -259,7 +227,6 @@ class Tool {
|
|||||||
this.alert('图片地址不能为空')
|
this.alert('图片地址不能为空')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 检查权限
|
// 检查权限
|
||||||
const { authSetting } = await new Promise((resolve, reject) => {
|
const { authSetting } = await new Promise((resolve, reject) => {
|
||||||
@@ -268,7 +235,6 @@ class Tool {
|
|||||||
fail: reject,
|
fail: reject,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!authSetting['scope.writePhotosAlbum']) {
|
if (!authSetting['scope.writePhotosAlbum']) {
|
||||||
// 请求权限
|
// 请求权限
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
@@ -279,7 +245,6 @@ class Tool {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取图片信息
|
// 获取图片信息
|
||||||
const { path } = await new Promise((resolve, reject) => {
|
const { path } = await new Promise((resolve, reject) => {
|
||||||
uni.getImageInfo({
|
uni.getImageInfo({
|
||||||
@@ -288,7 +253,6 @@ class Tool {
|
|||||||
fail: reject,
|
fail: reject,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 保存到相册
|
// 保存到相册
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
uni.saveImageToPhotosAlbum({
|
uni.saveImageToPhotosAlbum({
|
||||||
@@ -297,12 +261,10 @@ class Tool {
|
|||||||
fail: reject,
|
fail: reject,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.alert('已保存到相册')
|
this.alert('已保存到相册')
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存图片失败:', error)
|
console.error('保存图片失败:', error)
|
||||||
|
|
||||||
if (error.errMsg && error.errMsg.includes('auth')) {
|
if (error.errMsg && error.errMsg.includes('auth')) {
|
||||||
// 权限相关错误
|
// 权限相关错误
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
@@ -313,16 +275,240 @@ class Tool {
|
|||||||
success: resolve,
|
success: resolve,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
uni.openSetting()
|
uni.openSetting()
|
||||||
} else {
|
} else {
|
||||||
this.alert('保存失败,请重试')
|
this.alert('保存失败,请重试')
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 防抖函数
|
||||||
|
* @param {Function} func 要防抖的函数
|
||||||
|
* @param {number} wait 延迟时间(ms)
|
||||||
|
* @param {boolean} immediate 是否立即执行
|
||||||
|
* @returns {Function} 防抖后的函数
|
||||||
|
*/
|
||||||
|
debounce(func, wait, immediate = false) {
|
||||||
|
let timeout
|
||||||
|
return function (...args) {
|
||||||
|
const later = () => {
|
||||||
|
timeout = null
|
||||||
|
if (!immediate) func.apply(this, args)
|
||||||
|
}
|
||||||
|
const callNow = immediate && !timeout
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(later, wait)
|
||||||
|
if (callNow) func.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 节流函数
|
||||||
|
* @param {Function} func 要节流的函数
|
||||||
|
* @param {number} wait 延迟时间(ms)
|
||||||
|
* @returns {Function} 节流后的函数
|
||||||
|
*/
|
||||||
|
throttle(func, wait) {
|
||||||
|
let timeout
|
||||||
|
let previous = 0
|
||||||
|
return function (...args) {
|
||||||
|
const now = Date.now()
|
||||||
|
const remaining = wait - (now - previous)
|
||||||
|
const context = this
|
||||||
|
if (remaining <= 0 || remaining > wait) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = null
|
||||||
|
}
|
||||||
|
previous = now
|
||||||
|
func.apply(context, args)
|
||||||
|
} else if (!timeout) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
previous = Date.now()
|
||||||
|
timeout = null
|
||||||
|
func.apply(context, args)
|
||||||
|
}, remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 日期格式化
|
||||||
|
* @param {Date|string|number} date 日期对象、字符串或时间戳
|
||||||
|
* @param {string} fmt 格式字符串, 默认为 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
* @returns {string} 格式化后的日期字符串
|
||||||
|
*/
|
||||||
|
formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
|
// 如果传入的是时间戳,转换为Date对象
|
||||||
|
if (typeof date === 'number') {
|
||||||
|
date = new Date(date)
|
||||||
|
} else if (typeof date === 'string') {
|
||||||
|
// 如果传入的是字符串,尝试转换为Date对象
|
||||||
|
date = new Date(date)
|
||||||
|
}
|
||||||
|
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||||
|
throw new Error('Invalid date')
|
||||||
|
}
|
||||||
|
const o = {
|
||||||
|
'Y+': date.getFullYear(),
|
||||||
|
'M+': date.getMonth() + 1,
|
||||||
|
'D+': date.getDate(),
|
||||||
|
'H+': date.getHours(),
|
||||||
|
'm+': date.getMinutes(),
|
||||||
|
's+': date.getSeconds(),
|
||||||
|
'q+': Math.floor((date.getMonth() + 3) / 3),
|
||||||
|
S: date.getMilliseconds(),
|
||||||
|
}
|
||||||
|
for (let k in o) {
|
||||||
|
if (new RegExp('(' + k + ')').test(fmt)) {
|
||||||
|
let str = o[k] + ''
|
||||||
|
if (k === 'S') {
|
||||||
|
fmt = fmt.replace(RegExp.$1, str.padStart(3, '0'))
|
||||||
|
} else {
|
||||||
|
fmt = fmt.replace(RegExp.$1, str.length === 1 ? '0' + str : str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 深拷贝函数
|
||||||
|
* @param {*} obj 要深拷贝的对象
|
||||||
|
* @returns {*} 拷贝后的对象
|
||||||
|
*/
|
||||||
|
deepClone(obj) {
|
||||||
|
// 处理null、undefined和原始类型
|
||||||
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
// 处理日期对象
|
||||||
|
if (obj instanceof Date) {
|
||||||
|
return new Date(obj.getTime())
|
||||||
|
}
|
||||||
|
// 处理数组和对象
|
||||||
|
if (obj instanceof Array) {
|
||||||
|
return obj.map(item => this.deepClone(item))
|
||||||
|
}
|
||||||
|
// 处理普通对象
|
||||||
|
const clonedObj = {}
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
clonedObj[key] = this.deepClone(obj[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clonedObj
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 表单验证工具
|
||||||
|
* @class Validator
|
||||||
|
*/
|
||||||
|
getValidator() {
|
||||||
|
const rules = {
|
||||||
|
/**
|
||||||
|
* 必填验证
|
||||||
|
* @param {*} value 值
|
||||||
|
* @param {boolean} required 是否必填
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
required(value, required = true) {
|
||||||
|
if (!required) return true
|
||||||
|
return value !== undefined && value !== null && value !== ''
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 邮箱验证
|
||||||
|
* @param {string} value 值
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
email(value) {
|
||||||
|
if (!value) return true
|
||||||
|
const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||||
|
return reg.test(value)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 手机号验证
|
||||||
|
* @param {string} value 值
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
mobile(value) {
|
||||||
|
if (!value) return true
|
||||||
|
const reg = /^1[3-9]\d{9}$/
|
||||||
|
return reg.test(value)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 最小长度验证
|
||||||
|
* @param {string} value 值
|
||||||
|
* @param {number} length 最小长度
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
minLength(value, length) {
|
||||||
|
if (!value) return true
|
||||||
|
return value.length >= length
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 最大长度验证
|
||||||
|
* @param {string} value 值
|
||||||
|
* @param {number} length 最大长度
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
maxLength(value, length) {
|
||||||
|
if (!value) return true
|
||||||
|
return value.length <= length
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 数值范围验证
|
||||||
|
* @param {number} value 值
|
||||||
|
* @param {number} min 最小值
|
||||||
|
* @param {number} max 最大值
|
||||||
|
* @returns {boolean} 验证结果
|
||||||
|
*/
|
||||||
|
range(value, min, max) {
|
||||||
|
if (value === undefined || value === null) return true
|
||||||
|
return value >= min && value <= max
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* 验证单个字段
|
||||||
|
* @param {*} value 值
|
||||||
|
* @param {Object} rule 规则
|
||||||
|
* @returns {string|null} 错误信息
|
||||||
|
*/
|
||||||
|
validateField(value, rule) {
|
||||||
|
// 必填验证
|
||||||
|
if (rule.required !== undefined && !rules.required(value, rule.required)) {
|
||||||
|
return rule.message || '此字段为必填项'
|
||||||
|
}
|
||||||
|
// 如果值为空且非必填,跳过其他验证
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// 其他验证规则
|
||||||
|
if (rule.type && rules[rule.type]) {
|
||||||
|
if (!rules[rule.type](value, rule.param1, rule.param2)) {
|
||||||
|
return rule.message || '字段格式不正确'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证整个表单
|
||||||
|
* @param {Object} data 表单数据
|
||||||
|
* @param {Object} rules 验证规则
|
||||||
|
* @returns {Object} 验证结果 { isValid: boolean, errors: Object }
|
||||||
|
*/
|
||||||
|
validate(data, rules) {
|
||||||
|
const errors = {}
|
||||||
|
let isValid = true
|
||||||
|
for (let field in rules) {
|
||||||
|
const error = this.validateField(data[field], rules[field])
|
||||||
|
if (error) {
|
||||||
|
errors[field] = error
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isValid, errors }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 微信支付
|
* 微信支付
|
||||||
* @param {Object} paymentData 支付参数
|
* @param {Object} paymentData 支付参数
|
||||||
@@ -338,6 +524,11 @@ class Tool {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 文件上传
|
||||||
|
* @param {string} filePath 文件路径
|
||||||
|
* @returns {Promise<Object>} 上传结果
|
||||||
|
*/
|
||||||
upload(filePath) {
|
upload(filePath) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.uploadFile({
|
uni.uploadFile({
|
||||||
@@ -357,7 +548,120 @@ class Tool {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 暗黑模式切换
|
||||||
|
* @param {boolean} isDark 是否启用暗黑模式
|
||||||
|
*/
|
||||||
|
toggleDarkMode(isDark) {
|
||||||
|
try {
|
||||||
|
if (isDark) {
|
||||||
|
// 启用暗黑模式
|
||||||
|
document.documentElement.setAttribute('data-theme', 'dark')
|
||||||
|
uni.setStorageSync('theme', 'dark')
|
||||||
|
} else {
|
||||||
|
// 启用亮色模式
|
||||||
|
document.documentElement.setAttribute('data-theme', 'light')
|
||||||
|
uni.setStorageSync('theme', 'light')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('切换主题失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取当前主题模式
|
||||||
|
* @returns {string} 当前主题 ('light' | 'dark')
|
||||||
|
*/
|
||||||
|
getCurrentTheme() {
|
||||||
|
try {
|
||||||
|
// 优先使用用户设置的主题
|
||||||
|
const savedTheme = uni.getStorageSync('theme')
|
||||||
|
if (savedTheme) {
|
||||||
|
return savedTheme
|
||||||
|
}
|
||||||
|
// 检查系统主题
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
if (systemInfo.theme) {
|
||||||
|
return systemInfo.theme
|
||||||
|
}
|
||||||
|
// 默认返回亮色主题
|
||||||
|
return 'light'
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取主题失败:', error)
|
||||||
|
return 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 权限检查
|
||||||
|
* @param {string} permission 权限标识
|
||||||
|
* @returns {boolean} 是否拥有权限
|
||||||
|
*/
|
||||||
|
hasPermission(permission) {
|
||||||
|
try {
|
||||||
|
// 获取用户权限列表
|
||||||
|
const permissions = uni.getStorageSync('permissions') || []
|
||||||
|
return permissions.includes(permission)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('权限检查失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置用户权限
|
||||||
|
* @param {Array<string>} permissions 权限列表
|
||||||
|
*/
|
||||||
|
setPermissions(permissions) {
|
||||||
|
try {
|
||||||
|
uni.setStorageSync('permissions', Array.isArray(permissions) ? permissions : [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设置权限失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取用户所有权限
|
||||||
|
* @returns {Array<string>} 权限列表
|
||||||
|
*/
|
||||||
|
getUserPermissions() {
|
||||||
|
try {
|
||||||
|
return uni.getStorageSync('permissions') || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取权限失败:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查用户是否有任意权限
|
||||||
|
* @param {Array<string>} permissions 权限列表
|
||||||
|
* @returns {boolean} 是否有任意权限
|
||||||
|
*/
|
||||||
|
hasAnyPermission(permissions) {
|
||||||
|
if (!Array.isArray(permissions)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const userPermissions = this.getUserPermissions()
|
||||||
|
return permissions.some(permission => userPermissions.includes(permission))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 检查用户是否拥有所有权限
|
||||||
|
* @param {Array<string>} permissions 权限列表
|
||||||
|
* @returns {boolean} 是否拥有所有权限
|
||||||
|
*/
|
||||||
|
hasAllPermissions(permissions) {
|
||||||
|
if (!Array.isArray(permissions)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const userPermissions = this.getUserPermissions()
|
||||||
|
return permissions.every(permission => userPermissions.includes(permission))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清除用户权限
|
||||||
|
*/
|
||||||
|
clearPermissions() {
|
||||||
|
try {
|
||||||
|
uni.removeStorageSync('permissions')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除权限失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建单例并导出
|
// 创建单例并导出
|
||||||
export default new Tool()
|
export default new Tool()
|
||||||
|
|||||||
74
components/common/Card.vue
Normal file
74
components/common/Card.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<view class="card" :class="{ 'card--shadow': shadow, 'card--border': border }">
|
||||||
|
<view class="card__header" v-if="title || $slots.header">
|
||||||
|
<slot name="header">
|
||||||
|
<view class="card__title">{{ title }}</view>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<view class="card__body">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
<view class="card__footer" v-if="$slots.footer">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 定义props
|
||||||
|
defineProps({
|
||||||
|
// 卡片标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 是否显示阴影
|
||||||
|
shadow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否显示边框
|
||||||
|
border: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// 阴影样式
|
||||||
|
&--shadow {
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边框样式
|
||||||
|
&--border {
|
||||||
|
border: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding: 24rpx 32rpx;
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
195
components/common/CustomButton.vue
Normal file
195
components/common/CustomButton.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="custom-button"
|
||||||
|
:class="[`custom-button--${type}`, `custom-button--${size}`, { 'custom-button--disabled': disabled, 'custom-button--loading': loading }]"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<view class="custom-button__loading" v-if="loading">
|
||||||
|
<u-loading-icon mode="circle" size="20" color="#fff" />
|
||||||
|
</view>
|
||||||
|
<view class="custom-button__content">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
// 按钮类型
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary', // primary, secondary, danger, ghost
|
||||||
|
validator: (value) => ['primary', 'secondary', 'danger', 'ghost'].includes(value)
|
||||||
|
},
|
||||||
|
// 按钮大小
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'medium', // small, medium, large
|
||||||
|
validator: (value) => ['small', 'medium', 'large'].includes(value)
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否加载中
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 防抖时间(毫秒)
|
||||||
|
debounce: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义emits
|
||||||
|
const emit = defineEmits(['click'])
|
||||||
|
|
||||||
|
// 防抖定时器
|
||||||
|
const debounceTimer = ref(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理点击事件
|
||||||
|
* @param {Event} e 点击事件对象
|
||||||
|
*/
|
||||||
|
function handleClick(e) {
|
||||||
|
// 如果禁用或加载中,不处理点击
|
||||||
|
if (props.disabled || props.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设置了防抖
|
||||||
|
if (props.debounce > 0) {
|
||||||
|
if (debounceTimer.value) {
|
||||||
|
clearTimeout(debounceTimer.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceTimer.value = setTimeout(() => {
|
||||||
|
emit('click', e)
|
||||||
|
debounceTimer.value = null
|
||||||
|
}, props.debounce)
|
||||||
|
} else {
|
||||||
|
emit('click', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件卸载前清除定时器
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (debounceTimer.value) {
|
||||||
|
clearTimeout(debounceTimer.value)
|
||||||
|
debounceTimer.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.custom-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
border: 1rpx solid transparent;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// 主要按钮
|
||||||
|
&--primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1890ff;
|
||||||
|
border-color: #1890ff;
|
||||||
|
|
||||||
|
&:not(.custom-button--disabled):active {
|
||||||
|
background-color: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 次要按钮
|
||||||
|
&--secondary {
|
||||||
|
color: #333;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
|
||||||
|
&:not(.custom-button--disabled):active {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
border-color: #bfbfbf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 危险按钮
|
||||||
|
&--danger {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
|
||||||
|
&:not(.custom-button--disabled):active {
|
||||||
|
background-color: #ff7875;
|
||||||
|
border-color: #ff7875;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 幽灵按钮
|
||||||
|
&--ghost {
|
||||||
|
color: #1890ff;
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: #1890ff;
|
||||||
|
|
||||||
|
&:not(.custom-button--disabled):active {
|
||||||
|
color: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
|
&--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
&--loading {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小尺寸
|
||||||
|
&--small {
|
||||||
|
height: 56rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中等尺寸
|
||||||
|
&--medium {
|
||||||
|
height: 72rpx;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大尺寸
|
||||||
|
&--large {
|
||||||
|
height: 88rpx;
|
||||||
|
padding: 0 40rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
141
components/common/FormInput.vue
Normal file
141
components/common/FormInput.vue
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<view class="form-input">
|
||||||
|
<view class="form-input__label" v-if="label">
|
||||||
|
{{ label }}
|
||||||
|
<text class="form-input__required" v-if="required">*</text>
|
||||||
|
</view>
|
||||||
|
<view class="form-input__wrapper">
|
||||||
|
<input
|
||||||
|
class="form-input__field"
|
||||||
|
:type="type"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:value="modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
:maxlength="maxlength"
|
||||||
|
@input="handleInput"
|
||||||
|
@blur="handleBlur"
|
||||||
|
@focus="handleFocus"
|
||||||
|
/>
|
||||||
|
<view class="form-input__suffix" v-if="$slots.suffix">
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="form-input__error" v-if="error">{{ error }}</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
// 输入框的值
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 标签
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 占位符
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 输入框类型
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
// 是否必填
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 最大长度
|
||||||
|
maxlength: {
|
||||||
|
type: Number,
|
||||||
|
default: 140
|
||||||
|
},
|
||||||
|
// 错误信息
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义emits
|
||||||
|
const emit = defineEmits(['update:modelValue', 'blur', 'focus'])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入事件
|
||||||
|
* @param {Event} e 输入事件对象
|
||||||
|
*/
|
||||||
|
function handleInput(e) {
|
||||||
|
emit('update:modelValue', e.detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理失去焦点事件
|
||||||
|
* @param {Event} e 失去焦点事件对象
|
||||||
|
*/
|
||||||
|
function handleBlur(e) {
|
||||||
|
emit('blur', e.detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理获得焦点事件
|
||||||
|
* @param {Event} e 获得焦点事件对象
|
||||||
|
*/
|
||||||
|
function handleFocus(e) {
|
||||||
|
emit('focus', e.detail.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.form-input {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__required {
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-left: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1rpx solid #d9d9d9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__field {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__suffix {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
141
directives/index.js
Normal file
141
directives/index.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* 自定义指令入口文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 防止重复点击指令
|
||||||
|
export const preventReClick = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
if (el.disabled) return
|
||||||
|
el.disabled = true
|
||||||
|
setTimeout(() => {
|
||||||
|
el.disabled = false
|
||||||
|
}, binding.value || 1000)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长按指令
|
||||||
|
export const longPress = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
let timer = null
|
||||||
|
let startTime = 0
|
||||||
|
const handler = binding.value
|
||||||
|
const duration = binding.arg || 1000
|
||||||
|
|
||||||
|
// 取消定时器
|
||||||
|
const cancel = () => {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始计时
|
||||||
|
const start = () => {
|
||||||
|
startTime = Date.now()
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
handler(el)
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
el.addEventListener('touchstart', start)
|
||||||
|
el.addEventListener('touchend', cancel)
|
||||||
|
el.addEventListener('touchcancel', cancel)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限控制指令
|
||||||
|
export const permission = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
const { value } = binding
|
||||||
|
const permissions = uni.getStorageSync('permissions') || []
|
||||||
|
|
||||||
|
if (value && !permissions.includes(value)) {
|
||||||
|
// 隐藏元素
|
||||||
|
el.style.display = 'none'
|
||||||
|
el.__vue__ && (el.__vue__.isDisplay = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated(el, binding) {
|
||||||
|
const { value } = binding
|
||||||
|
const permissions = uni.getStorageSync('permissions') || []
|
||||||
|
|
||||||
|
if (value && !permissions.includes(value)) {
|
||||||
|
// 隐藏元素
|
||||||
|
el.style.display = 'none'
|
||||||
|
el.__vue__ && (el.__vue__.isDisplay = false)
|
||||||
|
} else {
|
||||||
|
// 显示元素
|
||||||
|
el.style.display = ''
|
||||||
|
el.__vue__ && (el.__vue__.isDisplay = true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽指令
|
||||||
|
export const drag = {
|
||||||
|
mounted(el) {
|
||||||
|
let startX = 0
|
||||||
|
let startY = 0
|
||||||
|
let initialX = 0
|
||||||
|
let initialY = 0
|
||||||
|
let isDragging = false
|
||||||
|
|
||||||
|
// 获取元素初始位置
|
||||||
|
const getInitialPosition = () => {
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
initialX = rect.left
|
||||||
|
initialY = rect.top
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标按下事件
|
||||||
|
const handleMouseDown = e => {
|
||||||
|
isDragging = true
|
||||||
|
startX = e.clientX
|
||||||
|
startY = e.clientY
|
||||||
|
getInitialPosition()
|
||||||
|
el.style.position = 'absolute'
|
||||||
|
el.style.left = initialX + 'px'
|
||||||
|
el.style.top = initialY + 'px'
|
||||||
|
el.style.zIndex = 9999
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标移动事件
|
||||||
|
const handleMouseMove = e => {
|
||||||
|
if (!isDragging) return
|
||||||
|
const dx = e.clientX - startX
|
||||||
|
const dy = e.clientY - startY
|
||||||
|
el.style.left = initialX + dx + 'px'
|
||||||
|
el.style.top = initialY + dy + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标松开事件
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
el.addEventListener('mousedown', handleMouseDown)
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
|
||||||
|
// 在元素上存储事件处理函数,以便在指令解绑时移除
|
||||||
|
el._dragHandlers = {
|
||||||
|
handleMouseDown,
|
||||||
|
handleMouseMove,
|
||||||
|
handleMouseUp,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
// 移除事件监听器
|
||||||
|
if (el._dragHandlers) {
|
||||||
|
const { handleMouseDown, handleMouseMove, handleMouseUp } = el._dragHandlers
|
||||||
|
el.removeEventListener('mousedown', handleMouseDown)
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
delete el._dragHandlers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
7
hooks/index.js
Normal file
7
hooks/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Hooks统一导出入口
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { useRequest } from './useRequest.js'
|
||||||
|
export { useState, useStorageState, useFormState } from './useState.js'
|
||||||
|
export { useGet, usePost } from './useApi.js'
|
||||||
157
hooks/useApi.js
Normal file
157
hooks/useApi.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { http } from '@/api/index.js'
|
||||||
|
import tool from '@/common/utils/tool.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET请求Hook
|
||||||
|
* @param {string} url 请求URL
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
* @returns {Object} 请求相关状态和方法
|
||||||
|
*/
|
||||||
|
export function useGet(url, options = {}) {
|
||||||
|
const {
|
||||||
|
manual = false, // 是否手动触发
|
||||||
|
params = {}, // 请求参数
|
||||||
|
cache = false, // 是否使用缓存
|
||||||
|
loadingDelay = 0, // loading延迟时间
|
||||||
|
onSuccess = () => {}, // 成功回调
|
||||||
|
onError = () => {}, // 失败回调
|
||||||
|
} = options
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const data = ref(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(null)
|
||||||
|
|
||||||
|
// loading延迟定时器
|
||||||
|
let loadingTimer = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行GET请求
|
||||||
|
* @param {Object} requestParams 请求参数
|
||||||
|
*/
|
||||||
|
async function run(requestParams = {}) {
|
||||||
|
// 清除之前的loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有loading延迟,先启动定时器
|
||||||
|
if (loadingDelay > 0) {
|
||||||
|
loadingTimer = setTimeout(() => {
|
||||||
|
loading.value = true
|
||||||
|
}, loadingDelay)
|
||||||
|
} else {
|
||||||
|
loading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await http.get(url, {
|
||||||
|
params: { ...params, ...requestParams },
|
||||||
|
cache,
|
||||||
|
})
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
data.value = response.data
|
||||||
|
error.value = null
|
||||||
|
onSuccess(response.data)
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
error.value = err
|
||||||
|
data.value = null
|
||||||
|
console.error(`GET请求失败: ${url}`, err)
|
||||||
|
onError(err)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消请求
|
||||||
|
*/
|
||||||
|
function cancel() {
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是手动触发,立即执行
|
||||||
|
if (!manual) {
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
run(params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
run,
|
||||||
|
cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST请求Hook
|
||||||
|
* @param {string} url 请求URL
|
||||||
|
* @returns {Object} 请求相关状态和方法
|
||||||
|
*/
|
||||||
|
export function usePost(url) {
|
||||||
|
/**
|
||||||
|
* 执行POST请求
|
||||||
|
* @param {Object} data 请求数据
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
*/
|
||||||
|
async function run(data, options = {}) {
|
||||||
|
const { loadingDelay = 0, onSuccess = () => {}, onError = () => {} } = options
|
||||||
|
|
||||||
|
let loadingTimer = null
|
||||||
|
|
||||||
|
// 如果有loading延迟,先启动定时器
|
||||||
|
if (loadingDelay > 0) {
|
||||||
|
loadingTimer = setTimeout(() => {
|
||||||
|
tool.loading()
|
||||||
|
}, loadingDelay)
|
||||||
|
} else {
|
||||||
|
tool.loading()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await http.post(url, data)
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
tool.hideLoading()
|
||||||
|
onSuccess(response.data)
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
tool.hideLoading()
|
||||||
|
console.error(`POST请求失败: ${url}`, err)
|
||||||
|
onError(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run,
|
||||||
|
}
|
||||||
|
}
|
||||||
113
hooks/useRequest.js
Normal file
113
hooks/useRequest.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import tool from '@/common/utils/tool.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用请求Hook
|
||||||
|
* @param {Function} apiFunction API函数
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
* @returns {Object} 请求相关状态和方法
|
||||||
|
*/
|
||||||
|
export function useRequest(apiFunction, options = {}) {
|
||||||
|
const {
|
||||||
|
manual = false, // 是否手动触发
|
||||||
|
defaultParams = [], // 默认参数
|
||||||
|
onSuccess = () => {}, // 成功回调
|
||||||
|
onError = () => {}, // 失败回调
|
||||||
|
loadingDelay = 0, // loading延迟时间
|
||||||
|
} = options
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
const data = ref(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref(null)
|
||||||
|
const params = ref([])
|
||||||
|
|
||||||
|
// loading延迟定时器
|
||||||
|
let loadingTimer = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行请求
|
||||||
|
* @param {...any} args 请求参数
|
||||||
|
*/
|
||||||
|
async function run(...args) {
|
||||||
|
// 清除之前的loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置参数
|
||||||
|
params.value = args
|
||||||
|
|
||||||
|
// 如果有loading延迟,先启动定时器
|
||||||
|
if (loadingDelay > 0) {
|
||||||
|
loadingTimer = setTimeout(() => {
|
||||||
|
loading.value = true
|
||||||
|
}, loadingDelay)
|
||||||
|
} else {
|
||||||
|
loading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await apiFunction(...args)
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
data.value = result
|
||||||
|
error.value = null
|
||||||
|
onSuccess(result, args)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
// 清除loading延迟定时器
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
error.value = err
|
||||||
|
data.value = null
|
||||||
|
console.error('请求失败:', err)
|
||||||
|
onError(err, args)
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消请求
|
||||||
|
*/
|
||||||
|
function cancel() {
|
||||||
|
if (loadingTimer) {
|
||||||
|
clearTimeout(loadingTimer)
|
||||||
|
loadingTimer = null
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重新执行请求
|
||||||
|
*/
|
||||||
|
function refresh() {
|
||||||
|
return run(...params.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是手动触发,立即执行
|
||||||
|
if (!manual) {
|
||||||
|
// 使用nextTick确保在组件挂载后执行
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
run(...defaultParams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
params,
|
||||||
|
run,
|
||||||
|
cancel,
|
||||||
|
refresh,
|
||||||
|
}
|
||||||
|
}
|
||||||
140
hooks/useState.js
Normal file
140
hooks/useState.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import tool from '@/common/utils/tool.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态管理Hook
|
||||||
|
* @param {any} initialState 初始状态
|
||||||
|
* @returns {Object} 状态和操作方法
|
||||||
|
*/
|
||||||
|
export function useState(initialState) {
|
||||||
|
const state = ref(initialState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置状态
|
||||||
|
* @param {any} newState 新状态
|
||||||
|
*/
|
||||||
|
function setState(newState) {
|
||||||
|
if (typeof newState === 'function') {
|
||||||
|
state.value = newState(state.value)
|
||||||
|
} else {
|
||||||
|
state.value = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置状态到初始值
|
||||||
|
*/
|
||||||
|
function resetState() {
|
||||||
|
state.value = typeof initialState === 'function' ? initialState() : initialState
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: computed(() => state.value),
|
||||||
|
setState,
|
||||||
|
resetState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地存储状态Hook
|
||||||
|
* @param {string} key 存储键名
|
||||||
|
* @param {any} defaultValue 默认值
|
||||||
|
* @returns {Object} 状态和操作方法
|
||||||
|
*/
|
||||||
|
export function useStorageState(key, defaultValue = null) {
|
||||||
|
const storageValue = tool.storage(key) ?? defaultValue
|
||||||
|
const { state, setState, resetState } = useState(storageValue)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置状态并同步到本地存储
|
||||||
|
* @param {any} newState 新状态
|
||||||
|
*/
|
||||||
|
function setStorageState(newState) {
|
||||||
|
if (typeof newState === 'function') {
|
||||||
|
newState = newState(state.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(newState)
|
||||||
|
tool.storage(key, newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除本地存储中的值
|
||||||
|
*/
|
||||||
|
function clearStorage() {
|
||||||
|
tool.removeStorage(key)
|
||||||
|
resetState()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
setState: setStorageState,
|
||||||
|
resetState: () => {
|
||||||
|
clearStorage()
|
||||||
|
},
|
||||||
|
clearStorage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单状态Hook
|
||||||
|
* @param {Object} initialFormData 初始表单数据
|
||||||
|
* @returns {Object} 表单状态和操作方法
|
||||||
|
*/
|
||||||
|
export function useFormState(initialFormData = {}) {
|
||||||
|
const { state, setState, resetState } = useState({ ...initialFormData })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表单项
|
||||||
|
* @param {string} field 字段名
|
||||||
|
* @param {any} value 字段值
|
||||||
|
*/
|
||||||
|
function setField(field, value) {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置表单到初始状态
|
||||||
|
*/
|
||||||
|
function resetForm() {
|
||||||
|
resetState()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空表单
|
||||||
|
*/
|
||||||
|
function clearForm() {
|
||||||
|
setState({})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置表单值
|
||||||
|
* @param {Object} values 表单值对象
|
||||||
|
*/
|
||||||
|
function setFields(values) {
|
||||||
|
setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
...values,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表单验证器
|
||||||
|
* @returns {Object} 验证器实例
|
||||||
|
*/
|
||||||
|
function getValidator() {
|
||||||
|
return tool.getValidator()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
form: state,
|
||||||
|
setField,
|
||||||
|
resetForm,
|
||||||
|
clearForm,
|
||||||
|
setFields,
|
||||||
|
getValidator,
|
||||||
|
}
|
||||||
|
}
|
||||||
8
main.js
8
main.js
@@ -2,18 +2,22 @@ import App from './App'
|
|||||||
import uviewPlus from '/uview-plus'
|
import uviewPlus from '/uview-plus'
|
||||||
import globalMixin from './mixins/global'
|
import globalMixin from './mixins/global'
|
||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
|
import { preventReClick, longPress, permission, drag } from './directives/index'
|
||||||
import './uni.promisify.adaptor'
|
import './uni.promisify.adaptor'
|
||||||
|
|
||||||
uni.$zp = {
|
uni.$zp = {
|
||||||
config: {
|
config: {
|
||||||
'empty-view-text': '空空如也~~',
|
'empty-view-text': '空空如也~~',
|
||||||
'refresher-enabled': true,
|
'refresher-enabled': true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
app.use(uviewPlus)
|
app.use(uviewPlus)
|
||||||
app.use(globalMixin)
|
app.use(globalMixin)
|
||||||
|
// 注册自定义指令
|
||||||
|
app.directive('prevent-re-click', preventReClick)
|
||||||
|
app.directive('long-press', longPress)
|
||||||
|
app.directive('permission', permission)
|
||||||
|
app.directive('drag', drag)
|
||||||
return { app }
|
return { app }
|
||||||
}
|
}
|
||||||
|
|||||||
26
package.json
26
package.json
@@ -1,7 +1,33 @@
|
|||||||
{
|
{
|
||||||
|
"name": "uniapp-template",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "基于 UniApp + Vue3 + uView-Plus 的微信小程序快速开发模板",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint src/**/*.{js,jsx,ts,tsx,vue} --fix",
|
||||||
|
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,vue,json,css,scss}",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"dayjs": "*",
|
"dayjs": "*",
|
||||||
"vue": "^3.5.21"
|
"vue": "^3.5.21"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.0.0",
|
||||||
|
"eslint-plugin-vue": "^9.0.0",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"husky": "^8.0.0",
|
||||||
|
"lint-staged": "^15.0.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"src/**/*.{js,jsx,ts,tsx,vue}": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
pages.json
20
pages.json
@@ -7,9 +7,27 @@
|
|||||||
"subPackages": [
|
"subPackages": [
|
||||||
{
|
{
|
||||||
"root": "subPages",
|
"root": "subPages",
|
||||||
"pages": []
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "welcome/index"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#c3c3c3",
|
||||||
|
"selectedColor": "#454c5c",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"borderStyle": "black",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index/index",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "static/assets/icon_home.png",
|
||||||
|
"selectedIconPath": "static/assets/icon_home_selected.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"easycom": {
|
"easycom": {
|
||||||
"autoscan": true,
|
"autoscan": true,
|
||||||
"custom": {
|
"custom": {
|
||||||
|
|||||||
BIN
static/assets/icon_home.png
Normal file
BIN
static/assets/icon_home.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/assets/icon_home_selected.png
Normal file
BIN
static/assets/icon_home_selected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
13
subPages/welcome/index.vue
Normal file
13
subPages/welcome/index.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
64
uni.scss
64
uni.scss
@@ -68,9 +68,69 @@ $uni-spacing-col-lg: 12px;
|
|||||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
/* 文章场景相关 */
|
/* 文章场景相关 */
|
||||||
$uni-color-title: #2C405A; // 文章标题颜色
|
$uni-color-title: #2c405a; // 文章标题颜色
|
||||||
$uni-font-size-title: 20px;
|
$uni-font-size-title: 20px;
|
||||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
$uni-font-size-subtitle: 26px;
|
$uni-font-size-subtitle: 26px;
|
||||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||||
$uni-font-size-paragraph: 15px;
|
$uni-font-size-paragraph: 15px;
|
||||||
|
|
||||||
|
/* 全局资源URL变量 */
|
||||||
|
$ASSETSURL: 'https://cdn.vrupup.com/s/1732/assets/';
|
||||||
|
|
||||||
|
/* 亮色模式颜色变量 */
|
||||||
|
$uni-color-primary-light: #007aff;
|
||||||
|
$uni-color-success-light: #4cd964;
|
||||||
|
$uni-color-warning-light: #f0ad4e;
|
||||||
|
$uni-color-error-light: #dd524d;
|
||||||
|
|
||||||
|
$uni-text-color-light: #333;
|
||||||
|
$uni-text-color-inverse-light: #fff;
|
||||||
|
$uni-text-color-grey-light: #999;
|
||||||
|
$uni-text-color-placeholder-light: #808080;
|
||||||
|
$uni-text-color-disable-light: #c0c0c0;
|
||||||
|
|
||||||
|
$uni-bg-color-light: #ffffff;
|
||||||
|
$uni-bg-color-grey-light: #f8f8f8;
|
||||||
|
$uni-bg-color-hover-light: #f1f1f1;
|
||||||
|
$uni-bg-color-mask-light: rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
$uni-border-color-light: #c8c7cc;
|
||||||
|
|
||||||
|
/* 暗色模式颜色变量 */
|
||||||
|
$uni-color-primary-dark: #0a84ff;
|
||||||
|
$uni-color-success-dark: #34c759;
|
||||||
|
$uni-color-warning-dark: #ff9500;
|
||||||
|
$uni-color-error-dark: #ff3b30;
|
||||||
|
|
||||||
|
$uni-text-color-dark: #f2f2f7;
|
||||||
|
$uni-text-color-inverse-dark: #000000;
|
||||||
|
$uni-text-color-grey-dark: #8e8e93;
|
||||||
|
$uni-text-color-placeholder-dark: #4c4c4c;
|
||||||
|
$uni-text-color-disable-dark: #636366;
|
||||||
|
|
||||||
|
$uni-bg-color-dark: #1c1c1e;
|
||||||
|
$uni-bg-color-grey-dark: #2c2c2e;
|
||||||
|
$uni-bg-color-hover-dark: #3a3a3c;
|
||||||
|
$uni-bg-color-mask-dark: rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
$uni-border-color-dark: #434346;
|
||||||
|
|
||||||
|
/* 默认使用亮色模式颜色 */
|
||||||
|
$uni-color-primary: $uni-color-primary-light;
|
||||||
|
$uni-color-success: $uni-color-success-light;
|
||||||
|
$uni-color-warning: $uni-color-warning-light;
|
||||||
|
$uni-color-error: $uni-color-error-light;
|
||||||
|
|
||||||
|
$uni-text-color: $uni-text-color-light;
|
||||||
|
$uni-text-color-inverse: $uni-text-color-inverse-light;
|
||||||
|
$uni-text-color-grey: $uni-text-color-grey-light;
|
||||||
|
$uni-text-color-placeholder: $uni-text-color-placeholder-light;
|
||||||
|
$uni-text-color-disable: $uni-text-color-disable-light;
|
||||||
|
|
||||||
|
$uni-bg-color: $uni-bg-color-light;
|
||||||
|
$uni-bg-color-grey: $uni-bg-color-grey-light;
|
||||||
|
$uni-bg-color-hover: $uni-bg-color-hover-light;
|
||||||
|
$uni-bg-color-mask: $uni-bg-color-mask-light;
|
||||||
|
|
||||||
|
$uni-border-color: $uni-border-color-light;
|
||||||
|
|||||||
@@ -1,36 +1,42 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import uni from '@dcloudio/vite-plugin-uni'
|
import uni from '@dcloudio/vite-plugin-uni'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { readFileSync, writeFileSync } from 'fs'
|
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
|
||||||
// 自定义插件:替换 manifest.json 中的 appid
|
// 自定义插件:替换 manifest.json 中的 appid
|
||||||
|
// 仅在通过HBuilder首次编译时执行,避免热重载时重复执行导致内存占用高
|
||||||
function replaceManifestAppid() {
|
function replaceManifestAppid() {
|
||||||
return {
|
return {
|
||||||
name: 'replace-manifest-appid',
|
name: 'replace-manifest-appid',
|
||||||
buildStart() {
|
buildStart() {
|
||||||
|
// 检查是否通过HBuilder编译
|
||||||
|
// 检查是否已经执行过插件
|
||||||
|
const manifestUpdatedFlag = resolve(__dirname, '.manifest-updated')
|
||||||
|
const isFirstCompile = !existsSync(manifestUpdatedFlag)
|
||||||
|
// 仅首次编译时执行
|
||||||
|
if (!isFirstCompile) {
|
||||||
|
console.log('跳过 manifest appid 更新(已执行过首次编译)')
|
||||||
|
return
|
||||||
|
}
|
||||||
// 获取环境变量,明确指定路径为项目根目录
|
// 获取环境变量,明确指定路径为项目根目录
|
||||||
dotenv.config({ path: resolve(__dirname, '.env') })
|
dotenv.config({ path: resolve(__dirname, '.env') })
|
||||||
const appid = process.env.VITE_APPID
|
const appid = process.env.VITE_APPID
|
||||||
const uni_appId = process.env.VITE_UNI_APPID
|
const uni_appId = process.env.VITE_UNI_APPID
|
||||||
|
|
||||||
if (appid && uni_appId) {
|
if (appid && uni_appId) {
|
||||||
// 读取 manifest.json 文件
|
// 读取 manifest.json 文件
|
||||||
const manifestPath = resolve(__dirname, 'manifest.json')
|
const manifestPath = resolve(__dirname, 'manifest.json')
|
||||||
let manifestContent = readFileSync(manifestPath, 'utf-8')
|
let manifestContent = readFileSync(manifestPath, 'utf-8')
|
||||||
|
|
||||||
// 解析 JSON
|
// 解析 JSON
|
||||||
const manifest = JSON.parse(manifestContent)
|
const manifest = JSON.parse(manifestContent)
|
||||||
|
|
||||||
// 替换
|
// 替换
|
||||||
manifest.appid = uni_appId
|
manifest.appid = uni_appId
|
||||||
if (manifest['mp-weixin']) {
|
if (manifest['mp-weixin']) {
|
||||||
manifest['mp-weixin'].appid = appid
|
manifest['mp-weixin'].appid = appid
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写回文件
|
// 写回文件
|
||||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4))
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4))
|
||||||
|
// 创建标记文件,表示已执行过插件
|
||||||
|
writeFileSync(manifestUpdatedFlag, 'Manifest updated by HBuilder first compile')
|
||||||
console.log(`Manifest appid 已更新为: ${uni_appId}`)
|
console.log(`Manifest appid 已更新为: ${uni_appId}`)
|
||||||
console.log(`Manifest mp-weixin appid 已更新为: ${appid}`)
|
console.log(`Manifest mp-weixin appid 已更新为: ${appid}`)
|
||||||
} else {
|
} else {
|
||||||
@@ -39,14 +45,74 @@ function replaceManifestAppid() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 检查是否通过HBuilder编译,决定是否启用插件
|
||||||
|
const manifestUpdatedFlag = resolve(__dirname, '.manifest-updated')
|
||||||
|
if (existsSync(manifestUpdatedFlag)) {
|
||||||
|
unlinkSync(manifestUpdatedFlag)
|
||||||
|
console.log('已清理 manifest 更新标记文件')
|
||||||
|
}
|
||||||
|
const plugins = manifestUpdatedFlag ? [replaceManifestAppid(), uni()] : [uni()]
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [replaceManifestAppid(), uni()],
|
plugins,
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
minify: 'terser',
|
minify: 'terser',
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
drop_console: true,
|
drop_console: true,
|
||||||
|
drop_debugger: true,
|
||||||
|
pure_funcs: ['console.log'], // 移除console.log
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
// 静态资源分类打包
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
let extType = assetInfo.name.split('.').at(1)
|
||||||
|
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
|
||||||
|
extType = 'img'
|
||||||
|
} else if (/woff|woff2|eot|ttf|otf/i.test(extType)) {
|
||||||
|
extType = 'font'
|
||||||
|
}
|
||||||
|
return `static/${extType}/[name]-[hash].[ext]`
|
||||||
|
},
|
||||||
|
// 分包
|
||||||
|
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'static/js/[name]-[hash].js',
|
||||||
|
manualChunks: {
|
||||||
|
// 将第三方库单独打包
|
||||||
|
vue: ['vue'],
|
||||||
|
'uview-plus': ['uview-plus'],
|
||||||
|
'luch-request': ['luch-request'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 启用 CSS 代码分割
|
||||||
|
cssCodeSplit: true,
|
||||||
|
// 小于此阈值的导入或引用资源将内联为 base64 编码
|
||||||
|
assetsInlineLimit: 4096,
|
||||||
|
// 启用 gzip 压缩
|
||||||
|
brotliSize: true,
|
||||||
|
},
|
||||||
|
// 优化选项
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['vue', 'uview-plus', 'luch-request'],
|
||||||
|
},
|
||||||
|
// 开发服务器配置
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 3000,
|
||||||
|
open: true,
|
||||||
|
// 代理配置
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: process.env.VITE_BASE_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user