1837 lines
48 KiB
Markdown
1837 lines
48 KiB
Markdown
# Cursor Rules - 移动端开发规范 (Vue 2 + uniapp + uview-ui)
|
||
|
||
## 适用范围
|
||
|
||
- **项目目录**: `mobile/` (智慧校园移动应用)
|
||
- **开发语言**: JavaScript (Vue 2)
|
||
- **平台支持**: H5、小程序、APP、鸿蒙
|
||
- **UI 框架**: uView UI 2.0.38 (Vue 2 版本)
|
||
|
||
---
|
||
|
||
# 智慧校园移动端开发规范
|
||
|
||
## 1. 🎯 技术栈规范
|
||
|
||
### 核心技术
|
||
|
||
- **基础框架**: uni-app + Vue 2.6.14
|
||
- **UI 组件库**: uview-ui 2.0.38 (Vue 2 版本)
|
||
- **开发语言**: JavaScript (ES6+)
|
||
- **样式预处理**: Sass 1.90.0
|
||
- **构建工具**: Vue CLI 5.0.8
|
||
- **代码规范**: ESLint 5.0.8
|
||
|
||
### 版本信息
|
||
|
||
```json
|
||
{
|
||
"name": "mobile2",
|
||
"version": "0.1.0",
|
||
"dependencies": {
|
||
"@dcloudio/uni-app": "^2.0.2-4070520250711001",
|
||
"uview-ui": "2.0.38",
|
||
"vue": ">= 2.6.14 < 2.7",
|
||
"vue-class-component": "^6.3.2",
|
||
"vue-property-decorator": "^8.0.0",
|
||
"vuex": "^3.2.0"
|
||
},
|
||
"devDependencies": {
|
||
"sass": "^1.90.0",
|
||
"vue-template-compiler": ">= 2.6.14 < 2.7",
|
||
"typescript": "~4.5.5"
|
||
}
|
||
}
|
||
```
|
||
|
||
## 2. 📁 项目结构规范
|
||
|
||
### 目录结构
|
||
|
||
```
|
||
mobile/
|
||
├── src/ # 源码目录
|
||
│ ├── components/ # 通用组件
|
||
│ │ ├── Card.vue # 卡片组件
|
||
│ │ ├── ServiceItem.vue # 服务项组件
|
||
│ │ └── GridLayout.vue # 网格布局组件
|
||
│ ├── pages/ # 页面文件
|
||
│ │ ├── index/ # 首页
|
||
│ │ │ └── index.vue # 首页内容
|
||
│ │ ├── login/ # 登录页
|
||
│ │ │ └── index.vue # 登录页面
|
||
│ │ └── task/ # 工作流页面
|
||
│ │ ├── myDocument.vue # 我发起的
|
||
│ │ ├── allTaskWaiting.vue # 我的待办
|
||
│ │ ├── taskFinish.vue # 我的已办
|
||
│ │ └── taskCopyList.vue # 抄送我的
|
||
│ ├── utils/ # 工具类
|
||
│ │ ├── request.js # 网络请求工具
|
||
│ │ ├── user.js # 用户管理工具
|
||
│ │ ├── permission.js # 权限管理工具
|
||
│ │ └── router.js # 路由管理工具
|
||
│ ├── common/ # 公共模块
|
||
│ │ ├── request.js # API请求封装
|
||
│ │ ├── storage.js # 存储工具
|
||
│ │ └── crypto.js # 加密工具
|
||
│ ├── static/ # 静态资源
|
||
│ │ ├── fonts/ # 字体文件
|
||
│ │ │ └── iconfont.css # 自定义图标
|
||
│ │ └── images/ # 图片资源
|
||
│ ├── App.vue # 应用入口
|
||
│ ├── main.ts # 项目入口
|
||
│ ├── pages.json # 页面配置
|
||
│ ├── manifest.json # 应用配置
|
||
│ └── uni.scss # 全局样式
|
||
├── package.json # 项目配置
|
||
├── vue.config.js # Vue CLI配置
|
||
└── README.md # 项目说明
|
||
```
|
||
|
||
## 3. 🎨 页面开发规范
|
||
|
||
### 页面模板标准
|
||
|
||
```vue
|
||
<template>
|
||
<view class="page-container">
|
||
<!-- 自定义导航栏 -->
|
||
<view class="custom-navbar">
|
||
<text class="navbar-title">{{ pageTitle }}</text>
|
||
</view>
|
||
|
||
<!-- 主要内容区域 -->
|
||
<scroll-view class="main-content" scroll-y enhanced :show-scrollbar="false">
|
||
<!-- 页面内容 -->
|
||
<view class="content">
|
||
<!-- 使用uView组件 -->
|
||
<u-cell-group>
|
||
<u-cell title="示例项目"></u-cell>
|
||
</u-cell-group>
|
||
</view>
|
||
|
||
<!-- 底部间距 -->
|
||
<view class="bottom-spacer"></view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'PageName',
|
||
data() {
|
||
return {
|
||
pageTitle: '页面标题',
|
||
// 页面数据
|
||
};
|
||
},
|
||
onLoad() {
|
||
// 页面加载逻辑
|
||
this.initPage();
|
||
},
|
||
onShow() {
|
||
// 页面显示逻辑
|
||
},
|
||
onUnload() {
|
||
// 页面卸载时清理定时器等
|
||
if (this.timer) {
|
||
clearInterval(this.timer);
|
||
}
|
||
},
|
||
methods: {
|
||
initPage() {
|
||
// 初始化页面逻辑
|
||
},
|
||
handleRefresh() {
|
||
// 刷新逻辑
|
||
uni.showLoading({ title: '刷新中...' });
|
||
setTimeout(() => {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '刷新成功', icon: 'success' });
|
||
}, 1000);
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page-container {
|
||
min-height: 100vh;
|
||
background-color: var(--background-color);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.custom-navbar {
|
||
background: linear-gradient(135deg, #007aff, #0051d9);
|
||
padding: calc(var(--status-bar-height) + 12px) 16px 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.navbar-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
padding-bottom: calc(50px + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.content {
|
||
padding: var(--spacing-md);
|
||
}
|
||
|
||
.bottom-spacer {
|
||
height: 20px;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 页面样式规范
|
||
|
||
#### 使用 CSS 变量系统
|
||
|
||
```scss
|
||
// 主题色彩变量
|
||
:root {
|
||
--primary-color: rgb(93, 135, 255);
|
||
--primary-light: rgba(93, 135, 255, 0.1);
|
||
--primary-dark: rgb(73, 115, 235);
|
||
--success-color: #52c41a;
|
||
--warning-color: #faad14;
|
||
--error-color: #ff4d4f;
|
||
--text-primary: #262626;
|
||
--text-secondary: #595959;
|
||
--text-disabled: #bfbfbf;
|
||
--border-color: #f0f0f0;
|
||
--background-color: #f8f9fa;
|
||
--white: #ffffff;
|
||
--shadow-light: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
--shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||
--radius-small: 4px;
|
||
--radius-medium: 8px;
|
||
--radius-large: 12px;
|
||
--spacing-xs: 4px;
|
||
--spacing-sm: 8px;
|
||
--spacing-md: 16px;
|
||
--spacing-lg: 24px;
|
||
--spacing-xl: 32px;
|
||
--transition-fast: 0.2s ease;
|
||
--transition-medium: 0.3s ease;
|
||
--font-size-xs: 12px;
|
||
--font-size-sm: 14px;
|
||
--font-size-md: 16px;
|
||
--font-size-lg: 18px;
|
||
--font-size-xl: 20px;
|
||
}
|
||
```
|
||
|
||
## 4. 🧩 组件开发规范
|
||
|
||
### 自定义组件模板
|
||
|
||
```vue
|
||
<template>
|
||
<view class="component-wrapper" :class="computedClass">
|
||
<!-- 组件内容 -->
|
||
<slot></slot>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
/**
|
||
* 智慧校园通用组件
|
||
* @property {String} customClass 自定义样式类
|
||
* @property {Boolean} disabled 是否禁用
|
||
* @example <custom-component :disabled="false">内容</custom-component>
|
||
*/
|
||
export default {
|
||
name: 'CustomComponent',
|
||
props: {
|
||
customClass: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
},
|
||
computed: {
|
||
computedClass() {
|
||
return {
|
||
disabled: this.disabled,
|
||
[this.customClass]: !!this.customClass,
|
||
};
|
||
},
|
||
},
|
||
methods: {
|
||
handleClick() {
|
||
if (this.disabled) return;
|
||
|
||
this.$emit('click');
|
||
// 触觉反馈
|
||
uni.vibrateShort();
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.component-wrapper {
|
||
// 组件样式
|
||
transition: var(--transition-fast);
|
||
|
||
&.disabled {
|
||
opacity: 0.5;
|
||
pointer-events: none;
|
||
}
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 组件命名规范
|
||
|
||
- **文件命名**: 使用 PascalCase (如: `Card.vue`, `ServiceItem.vue`)
|
||
- **组件名称**: 使用 PascalCase (如: `Card`, `ServiceItem`)
|
||
- **引用方式**: 支持驼峰和短横线 (如: `<Card>` 或 `<card>`)
|
||
|
||
## 5. 🎨 图标使用规范
|
||
|
||
### 🚨 强制性图标使用规范
|
||
|
||
**⚠️ 重要说明**: 移动端项目使用 iconfont 图标系统,同时支持 uview-ui 的 u-icon 组件!
|
||
|
||
#### 📋 图标使用策略
|
||
|
||
1. **✅ 优先使用 iconfont 图标**
|
||
```vue
|
||
<!-- ✅ 推荐:使用 iconfont 图标 -->
|
||
<text class="iconfont icon-search" style="font-size: 20px; color: #666;"></text>
|
||
<text class="iconfont icon-account-circle" style="font-size: 24px;"></text>
|
||
<text class="iconfont icon-close" @click="handleClose"></text>
|
||
```
|
||
|
||
2. **✅ 支持 uview-ui 的 u-icon 组件**
|
||
```vue
|
||
<!-- ✅ 支持:使用 uview-ui 图标 -->
|
||
<u-icon name="search" size="20" color="#666"></u-icon>
|
||
<u-icon name="account" size="24"></u-icon>
|
||
<u-icon name="close" @click="handleClose"></u-icon>
|
||
```
|
||
|
||
3. **🔄 动态图标类名**
|
||
```vue
|
||
<!-- ✅ 动态切换图标 -->
|
||
<text
|
||
:class="'iconfont ' + (expanded ? 'icon-arrow-down' : 'icon-arrow-right')"
|
||
style="font-size: 16px; color: #999;"
|
||
@click="toggleExpand"
|
||
></text>
|
||
```
|
||
|
||
### 图标配置文件
|
||
|
||
**位置**: `mobile/src/static/fonts/iconfont.css`
|
||
|
||
```css
|
||
.iconfont {
|
||
display: inline-block;
|
||
width: 16px;
|
||
height: 16px;
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
}
|
||
|
||
/* 用户图标 */
|
||
.icon-user {
|
||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E");
|
||
}
|
||
|
||
/* 密码锁图标 */
|
||
.icon-lock {
|
||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z'/%3E%3C/svg%3E");
|
||
}
|
||
|
||
/* 更多图标... */
|
||
```
|
||
|
||
### 全局引入配置
|
||
|
||
**位置**: `mobile/src/main.ts`
|
||
|
||
```javascript
|
||
import Vue from 'vue'
|
||
import App from './App.vue'
|
||
import './uni.promisify.adaptor'
|
||
import uView from "uview-ui";
|
||
Vue.use(uView);
|
||
Vue.config.productionTip = false
|
||
|
||
const app = new (typeof App === 'function' ? App : Vue.extend(Object.assign({ mpType: 'app' }, App)))
|
||
app.$mount();
|
||
```
|
||
|
||
**位置**: `mobile/src/App.vue`
|
||
|
||
```vue
|
||
<script lang="ts">
|
||
import Vue from 'vue';
|
||
export default Vue.extend({
|
||
mpType: 'app',
|
||
onLaunch() {
|
||
console.log('App Launch');
|
||
},
|
||
onShow() {
|
||
console.log('App Show');
|
||
},
|
||
onHide() {
|
||
console.log('App Hide');
|
||
},
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
|
||
@import 'uview-ui/index.scss';
|
||
|
||
/* 引入自定义图标系统 */
|
||
@import '@/static/fonts/iconfont.css';
|
||
</style>
|
||
```
|
||
|
||
### 标准使用方法
|
||
|
||
#### 基础用法
|
||
|
||
```vue
|
||
<template>
|
||
<!-- iconfont 图标 -->
|
||
<text class="iconfont icon-user"></text>
|
||
<text class="iconfont icon-lock"></text>
|
||
<text class="iconfont icon-search"></text>
|
||
|
||
<!-- uview-ui 图标 -->
|
||
<u-icon name="account" size="20" color="#666"></u-icon>
|
||
<u-icon name="lock" size="20" color="#666"></u-icon>
|
||
<u-icon name="search" size="20" color="#666"></u-icon>
|
||
|
||
<!-- 在表单中使用 -->
|
||
<view class="input-group">
|
||
<text class="iconfont icon-user input-icon"></text>
|
||
<input class="login-input" placeholder="账号" />
|
||
</view>
|
||
|
||
<!-- 在按钮中使用 -->
|
||
<button class="btn-primary">
|
||
<text class="iconfont icon-login btn-icon"></text>
|
||
<text>登录</text>
|
||
</button>
|
||
</template>
|
||
```
|
||
|
||
## 6. 📱 移动端UI组件规范
|
||
|
||
### 列表操作按钮设计规范 (2025-01-03 新增)
|
||
|
||
#### 🎯 设计原则
|
||
移动端列表中的操作按钮统一采用**图标+文字垂直排列**的无边框设计,提升用户体验和界面美观度。
|
||
|
||
#### 📋 标准按钮模板
|
||
|
||
```vue
|
||
<!-- 标准操作按钮容器 -->
|
||
<view class="card-actions">
|
||
<!-- 查看详细按钮 -->
|
||
<view class="action-btn info-btn" @click="handleView(item)">
|
||
<u-icon name="eye" size="16" color="#3c9cff"></u-icon>
|
||
<text class="btn-text">查看详细</text>
|
||
</view>
|
||
|
||
<!-- 编辑修改按钮 -->
|
||
<view class="action-btn success-btn" @click="handleEdit(item)">
|
||
<u-icon name="edit-pen" size="16" color="#5ac725"></u-icon>
|
||
<text class="btn-text">编辑</text>
|
||
</view>
|
||
|
||
<!-- 历史记录按钮 -->
|
||
<view class="action-btn warning-btn" @click="handleHistory(item)">
|
||
<u-icon name="clock" size="16" color="#ff9900"></u-icon>
|
||
<text class="btn-text">历史记录</text>
|
||
</view>
|
||
|
||
<!-- 删除操作按钮 -->
|
||
<view class="action-btn error-btn" @click="handleDelete(item)">
|
||
<u-icon name="trash" size="16" color="#f56c6c"></u-icon>
|
||
<text class="btn-text">删除</text>
|
||
</view>
|
||
</view>
|
||
```
|
||
|
||
#### 🎨 标准样式定义
|
||
|
||
```scss
|
||
/* 操作按钮容器 */
|
||
.card-actions {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
padding: 20rpx;
|
||
border-top: 1px solid #f0f0f0;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
/* 统一按钮样式 */
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 16rpx 20rpx;
|
||
border-radius: 12rpx;
|
||
background-color: #f8f9fa;
|
||
min-width: 120rpx;
|
||
transition: all 0.3s ease;
|
||
|
||
/* 点击动画效果 */
|
||
&:active {
|
||
transform: scale(0.95);
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
/* 按钮文字样式 */
|
||
.btn-text {
|
||
font-size: 24rpx;
|
||
margin-top: 8rpx;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 主题色彩方案 */
|
||
.info-btn {
|
||
background-color: rgba(60, 156, 255, 0.1);
|
||
.btn-text { color: #3c9cff; }
|
||
}
|
||
|
||
.success-btn {
|
||
background-color: rgba(90, 199, 37, 0.1);
|
||
.btn-text { color: #5ac725; }
|
||
}
|
||
|
||
.warning-btn {
|
||
background-color: rgba(255, 153, 0, 0.1);
|
||
.btn-text { color: #ff9900; }
|
||
}
|
||
|
||
.error-btn {
|
||
background-color: rgba(245, 108, 108, 0.1);
|
||
.btn-text { color: #f56c6c; }
|
||
}
|
||
|
||
.primary-btn {
|
||
background-color: rgba(51, 102, 255, 0.1);
|
||
.btn-text { color: #3366ff; }
|
||
}
|
||
|
||
.default-btn {
|
||
background-color: rgba(144, 147, 153, 0.1);
|
||
.btn-text { color: #909399; }
|
||
}
|
||
|
||
#### 🎨 按钮背景色规范
|
||
|
||
**📱 移动端按钮背景色设计规范**
|
||
|
||
**背景色选项**:
|
||
- ✅ **完全透明**: `rgba(r, g, b, 0)` - 无背景色,纯文字按钮
|
||
- ✅ **半透明背景色**: `rgba(r, g, b, 0.1)` 到 `rgba(r, g, b, 0.3)` - 有背景色的按钮
|
||
- ✅ **实色背景**: `rgba(r, g, b, 1)` - 完全实色背景
|
||
|
||
**推荐使用场景**:
|
||
- **完全透明背景**: 适用于简洁设计,强调文字内容
|
||
- **半透明背景**: 适用于需要视觉层次的场景
|
||
- **实色背景**: 适用于重要操作按钮
|
||
|
||
**当前项目使用**:
|
||
```scss
|
||
.info-btn {
|
||
background-color: rgba(60, 156, 255, 0); // ✅ 完全透明
|
||
.btn-text { color: #3c9cff; }
|
||
}
|
||
|
||
.success-btn {
|
||
background-color: rgba(90, 199, 37, 0); // ✅ 完全透明
|
||
.btn-text { color: #5ac725; }
|
||
}
|
||
|
||
.warning-btn {
|
||
background-color: rgba(255, 153, 0, 0); // ✅ 完全透明
|
||
.btn-text { color: #ff9900; }
|
||
}
|
||
```
|
||
|
||
**设计原则**:
|
||
- 根据设计需求选择合适的背景色
|
||
- 确保按钮文字清晰可读
|
||
- 保持整体视觉一致性
|
||
- 符合项目设计风格
|
||
```
|
||
|
||
#### 🔧 图标选择规范
|
||
|
||
| 功能类型 | 推荐图标 | 颜色 | 样式类 | 使用场景 |
|
||
|---------|---------|------|--------|----------|
|
||
| 查看详细 | `eye` | #3c9cff | info-btn | 查看、预览、详情 |
|
||
| 编辑修改 | `edit-pen` | #5ac725 | success-btn | 编辑、修改、更新 |
|
||
| 历史记录 | `clock` | #ff9900 | warning-btn | 时间、历史、记录 |
|
||
| 删除操作 | `trash` | #f56c6c | error-btn | 删除、移除、清空 |
|
||
| 下载导出 | `download` | #3c9cff | info-btn | 下载、导出、保存 |
|
||
| 上传提交 | `upload` | #5ac725 | success-btn | 上传、提交、发送 |
|
||
| 审核通过 | `checkmark` | #5ac725 | success-btn | 通过、确认、同意 |
|
||
| 审核拒绝 | `close` | #f56c6c | error-btn | 拒绝、关闭、取消 |
|
||
| 详情信息 | `info-circle` | #3c9cff | info-btn | 信息、说明、帮助 |
|
||
| 设置配置 | `setting` | #909399 | default-btn | 设置、配置、管理 |
|
||
| 分享转发 | `share` | #3366ff | primary-btn | 分享、转发、推送 |
|
||
| 收藏点赞 | `heart` | #f56c6c | error-btn | 收藏、点赞、喜欢 |
|
||
|
||
#### 💡 使用指导
|
||
|
||
1. **✅ 强制使用场景**
|
||
- 所有移动端列表页面的操作按钮
|
||
- 卡片组件中的操作按钮
|
||
- 表格行操作按钮
|
||
|
||
2. **🎨 设计要求**
|
||
- 图标大小统一为16px
|
||
- 文字大小统一为24rpx(12px)
|
||
- 按钮最小宽度120rpx
|
||
- 圆角半径12rpx
|
||
|
||
3. **🔄 交互要求**
|
||
- 必须包含点击动画效果(:active伪类)
|
||
- 缩放比例0.95,透明度0.8
|
||
- 过渡动画时间0.3s
|
||
|
||
4. **📏 布局要求**
|
||
- 使用flex布局,space-around分布
|
||
- 按钮间距16rpx
|
||
- 容器内边距20rpx
|
||
- 顶部边框分割线
|
||
|
||
#### 🚨 注意事项
|
||
|
||
- **禁止使用传统边框按钮**: 移动端列表不再使用`<u-button>`组件
|
||
- **图标组件规范**: 统一使用`<u-icon />`组件
|
||
- **颜色主题一致**: 严格按照表格中的颜色方案
|
||
- **响应式适配**: 确保在不同屏幕尺寸下正常显示
|
||
- **无障碍访问**: 文字描述清晰,图标语义明确
|
||
|
||
#### 📝 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<view class="list-container">
|
||
<!-- 列表项 -->
|
||
<view class="list-item" v-for="item in dataList" :key="item.id">
|
||
<!-- 主要内容 -->
|
||
<view class="item-content">
|
||
<text class="item-title">{{ item.title }}</text>
|
||
<text class="item-desc">{{ item.description }}</text>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="card-actions">
|
||
<view class="action-btn info-btn" @click="handleView(item)">
|
||
<u-icon name="eye" size="16" color="#3c9cff"></u-icon>
|
||
<text class="btn-text">查看详细</text>
|
||
</view>
|
||
|
||
<view v-if="item.canEdit" class="action-btn success-btn" @click="handleEdit(item)">
|
||
<u-icon name="edit-pen" size="16" color="#5ac725"></u-icon>
|
||
<text class="btn-text">编辑</text>
|
||
</view>
|
||
|
||
<view class="action-btn warning-btn" @click="handleHistory(item)">
|
||
<u-icon name="clock" size="16" color="#ff9900"></u-icon>
|
||
<text class="btn-text">历史记录</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
dataList: [
|
||
{ id: 1, title: '示例标题1', description: '示例描述', canEdit: true },
|
||
{ id: 2, title: '示例标题2', description: '示例描述', canEdit: false }
|
||
]
|
||
}
|
||
},
|
||
methods: {
|
||
handleView(item) {
|
||
console.log('查看详细:', item);
|
||
// 跳转到详情页面
|
||
},
|
||
handleEdit(item) {
|
||
console.log('编辑项目:', item);
|
||
// 跳转到编辑页面
|
||
},
|
||
handleHistory(item) {
|
||
console.log('查看历史:', item);
|
||
// 跳转到历史页面
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.list-container {
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.list-item {
|
||
background-color: #ffffff;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.item-content {
|
||
padding: 30rpx 30rpx 20rpx;
|
||
}
|
||
|
||
.item-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.item-desc {
|
||
font-size: 28rpx;
|
||
color: #606266;
|
||
}
|
||
|
||
/* 引入标准按钮样式 */
|
||
.card-actions {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
padding: 20rpx;
|
||
border-top: 1px solid #f0f0f0;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 16rpx 20rpx;
|
||
border-radius: 12rpx;
|
||
background-color: #f8f9fa;
|
||
min-width: 120rpx;
|
||
transition: all 0.3s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 24rpx;
|
||
margin-top: 8rpx;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-btn {
|
||
background-color: rgba(60, 156, 255, 0.1);
|
||
.btn-text { color: #3c9cff; }
|
||
}
|
||
|
||
.success-btn {
|
||
background-color: rgba(90, 199, 37, 0.1);
|
||
.btn-text { color: #5ac725; }
|
||
}
|
||
|
||
.warning-btn {
|
||
background-color: rgba(255, 153, 0, 0.1);
|
||
.btn-text { color: #ff9900; }
|
||
}
|
||
</style>
|
||
```
|
||
|
||
## 7. 🌐 API 接口规范
|
||
|
||
### 网络请求工具 (common/request.js)
|
||
|
||
```javascript
|
||
// API请求配置
|
||
const baseURL =
|
||
process.env.NODE_ENV === 'development'
|
||
? 'http://localhost:8080'
|
||
: 'https://your-api-domain.com';
|
||
|
||
// 统一请求方法
|
||
export const request = (options) => {
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
url: baseURL + options.url,
|
||
method: options.method || 'GET',
|
||
data: options.data || {},
|
||
header: {
|
||
'Content-Type': 'application/json',
|
||
Authorization: getToken(),
|
||
...options.header,
|
||
},
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
resolve(res.data);
|
||
} else {
|
||
reject(res);
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
reject(error);
|
||
},
|
||
});
|
||
});
|
||
};
|
||
|
||
// 获取token
|
||
function getToken() {
|
||
try {
|
||
const token = uni.getStorageSync('token');
|
||
return token ? `Bearer ${token}` : '';
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
// API方法封装
|
||
export const api = {
|
||
// 用户登录
|
||
login: (data) =>
|
||
request({
|
||
url: '/auth/login',
|
||
method: 'POST',
|
||
data: {
|
||
tenantId: data.tenantId || '000000',
|
||
username: data.username,
|
||
password: data.password,
|
||
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||
grantType: data.grantType || 'password',
|
||
},
|
||
header: {
|
||
isToken: false,
|
||
isEncrypt: true,
|
||
repeatSubmit: false,
|
||
},
|
||
}),
|
||
|
||
// 获取用户信息
|
||
getUserInfo: () =>
|
||
request({
|
||
url: '/system/user/getInfo',
|
||
method: 'GET',
|
||
}),
|
||
|
||
// 获取租户列表
|
||
getTenantList: (isToken = false) =>
|
||
request({
|
||
url: '/auth/tenant/list',
|
||
method: 'GET',
|
||
header: {
|
||
isToken: isToken,
|
||
},
|
||
}),
|
||
|
||
// 注销登录
|
||
logout: () =>
|
||
request({
|
||
url: '/auth/logout',
|
||
method: 'POST',
|
||
}),
|
||
};
|
||
```
|
||
|
||
### 用户信息缓存工具 (common/storage.js)
|
||
|
||
```javascript
|
||
// 用户信息缓存工具
|
||
export const userStorage = {
|
||
// 保存登录信息
|
||
saveLoginInfo: (loginResult, userInfo) => {
|
||
try {
|
||
// 保存token
|
||
uni.setStorageSync('access_token', loginResult.access_token);
|
||
|
||
// 保存用户信息
|
||
if (userInfo) {
|
||
uni.setStorageSync('userInfo', JSON.stringify(userInfo));
|
||
}
|
||
|
||
// 保存登录状态
|
||
uni.setStorageSync('isLoggedIn', true);
|
||
|
||
console.log('用户登录信息已保存');
|
||
} catch (e) {
|
||
console.error('保存登录信息失败:', e);
|
||
}
|
||
},
|
||
|
||
// 获取用户信息
|
||
getUserInfo: () => {
|
||
try {
|
||
const userInfo = uni.getStorageSync('userInfo');
|
||
return userInfo ? JSON.parse(userInfo) : null;
|
||
} catch (e) {
|
||
console.error('获取用户信息失败:', e);
|
||
return null;
|
||
}
|
||
},
|
||
|
||
// 获取token
|
||
getToken: () => {
|
||
try {
|
||
return uni.getStorageSync('access_token') || '';
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
},
|
||
|
||
// 检查登录状态
|
||
isLoggedIn: () => {
|
||
try {
|
||
const isLoggedIn = uni.getStorageSync('isLoggedIn');
|
||
const token = uni.getStorageSync('access_token');
|
||
return !!(isLoggedIn && token);
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 清除登录信息
|
||
clearLoginInfo: () => {
|
||
try {
|
||
uni.removeStorageSync('access_token');
|
||
uni.removeStorageSync('userInfo');
|
||
uni.removeStorageSync('isLoggedIn');
|
||
console.log('用户登录信息已清除');
|
||
} catch (e) {
|
||
console.error('清除登录信息失败:', e);
|
||
}
|
||
},
|
||
};
|
||
```
|
||
|
||
## 7. 🔐 登录状态管理规范
|
||
|
||
### 登录页面开发规范
|
||
|
||
#### 登录页面模板 (pages/login/index.vue)
|
||
|
||
```vue
|
||
<template>
|
||
<view class="login-bg">
|
||
<view class="login-card">
|
||
<!-- Logo和标题区域 -->
|
||
<view class="login-header">
|
||
<view class="login-logo">
|
||
<image class="logo-img" src="/static/logo-2.png" mode="aspectFit" />
|
||
</view>
|
||
<view class="login-text">
|
||
<view class="login-title">智慧校园</view>
|
||
<view class="login-subtitle">杭州第一技师学院</view>
|
||
</view>
|
||
</view>
|
||
<!-- 登录表单 -->
|
||
<view class="login-form">
|
||
<!-- 账号输入框 -->
|
||
<view class="input-wrapper">
|
||
<u-input
|
||
v-model="username"
|
||
placeholder="教工号/学号"
|
||
type="text"
|
||
border="true"
|
||
:custom-style="inputStyle"
|
||
placeholder-style="color: #999; font-size: 16px"
|
||
>
|
||
<template #prefix>
|
||
<u-icon
|
||
name="account"
|
||
size="20"
|
||
color="#b0b8c9"
|
||
class="input-prefix-icon"
|
||
/>
|
||
</template>
|
||
</u-input>
|
||
</view>
|
||
<!-- 密码输入框 -->
|
||
<view class="input-wrapper">
|
||
<u-input
|
||
v-model="password"
|
||
placeholder="密码"
|
||
type="password"
|
||
border="true"
|
||
:custom-style="inputStyle"
|
||
placeholder-style="color: #999; font-size: 16px"
|
||
@confirm="onPasswordConfirm"
|
||
>
|
||
<template #prefix>
|
||
<u-icon
|
||
name="lock"
|
||
size="20"
|
||
color="#b0b8c9"
|
||
class="input-prefix-icon"
|
||
/>
|
||
</template>
|
||
</u-input>
|
||
</view>
|
||
<!-- 登录按钮 -->
|
||
<u-button
|
||
type="primary"
|
||
:custom-style="buttonStyle"
|
||
:loading="loading"
|
||
:disabled="loading"
|
||
@click="onLogin"
|
||
>
|
||
<view class="login-btn-content" v-if="!loading">
|
||
<text class="login-btn-text">登录</text>
|
||
</view>
|
||
<view class="login-btn-content" v-else>
|
||
<text class="login-btn-text">登录中...</text>
|
||
</view>
|
||
</u-button>
|
||
</view>
|
||
<!-- 版权信息 -->
|
||
<view class="login-copyright">© 2025 杭州第一技师学院 All Rights Reserved.</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { api } from '@/common/request';
|
||
import { userStorage } from '@/common/storage';
|
||
|
||
export default {
|
||
name: 'LoginPage',
|
||
data() {
|
||
return {
|
||
username: '',
|
||
password: '',
|
||
loading: false,
|
||
inputStyle: {
|
||
height: '45px',
|
||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||
borderRadius: '12px',
|
||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||
color: '#ffffff',
|
||
},
|
||
buttonStyle: {
|
||
height: '50px',
|
||
fontSize: '18px',
|
||
fontWeight: 'bold',
|
||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||
borderRadius: '25px',
|
||
},
|
||
};
|
||
},
|
||
onLoad() {
|
||
// 检查是否已登录
|
||
if (userStorage.isLoggedIn()) {
|
||
this.redirectToHome();
|
||
}
|
||
},
|
||
methods: {
|
||
async onLogin() {
|
||
if (!this.username || !this.password) {
|
||
uni.showToast({
|
||
title: '请输入用户名和密码',
|
||
icon: 'none',
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.loading = true;
|
||
|
||
try {
|
||
const loginResponse = await api.login({
|
||
username: this.username,
|
||
password: this.password,
|
||
});
|
||
|
||
if (loginResponse && loginResponse.data) {
|
||
const userInfoResponse = await api.getUserInfo();
|
||
|
||
userStorage.saveLoginInfo(loginResponse.data, userInfoResponse.data);
|
||
|
||
uni.showToast({ title: '登录成功', icon: 'success' });
|
||
|
||
setTimeout(() => {
|
||
this.redirectToHome();
|
||
}, 1500);
|
||
}
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
|
||
const message = error.msg || error.message || '登录失败,请检查用户名和密码';
|
||
uni.showToast({ title: message, icon: 'error', duration: 3000 });
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
onPasswordConfirm() {
|
||
this.onLogin();
|
||
},
|
||
|
||
redirectToHome() {
|
||
uni.reLaunch({ url: '/pages/index/index' });
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.login-bg {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.login-card {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(20px);
|
||
border-radius: 20px;
|
||
padding: 40px 30px;
|
||
width: 100%;
|
||
max-width: 400px;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.login-header {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.login-logo {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.logo-img {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 15px;
|
||
}
|
||
|
||
.login-title {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
color: #ffffff;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.login-subtitle {
|
||
font-size: 16px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.login-form {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.input-wrapper {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.login-btn-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.login-btn-text {
|
||
color: #ffffff;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.login-copyright {
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
margin-top: 20px;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 登录状态验证规范
|
||
|
||
#### 全局登录验证
|
||
|
||
每个需要登录的页面都必须在 `onLoad` 生命周期中检查登录状态:
|
||
|
||
```javascript
|
||
// 页面登录状态检查模板
|
||
export default {
|
||
onLoad() {
|
||
this.checkLoginStatus();
|
||
},
|
||
|
||
methods: {
|
||
checkLoginStatus() {
|
||
if (!userStorage.isLoggedIn()) {
|
||
uni.reLaunch({
|
||
url: '/pages/login/index',
|
||
});
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
|
||
loadUserInfo() {
|
||
const userInfo = userStorage.getUserInfo();
|
||
if (userInfo && userInfo.user) {
|
||
this.userInfo.name = userInfo.user.nickName || userInfo.user.userName || '用户';
|
||
// 根据需要设置其他用户信息
|
||
}
|
||
},
|
||
},
|
||
};
|
||
```
|
||
|
||
## 8. 📱 页面配置规范
|
||
|
||
### pages.json 配置
|
||
|
||
```json
|
||
{
|
||
"easycom": {
|
||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||
},
|
||
"pages": [
|
||
{
|
||
"path": "pages/login/index",
|
||
"style": {
|
||
"navigationBarTitleText": "登录",
|
||
"navigationStyle": "custom"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/index/index",
|
||
"style": {
|
||
"navigationBarTitleText": "杭州第一技师学院智慧校园",
|
||
"navigationStyle": "custom"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/task/myDocument",
|
||
"style": {
|
||
"navigationBarTitleText": "我发起的",
|
||
"navigationBarBackgroundColor": "#5d87ff",
|
||
"navigationBarTextStyle": "white"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/task/allTaskWaiting",
|
||
"style": {
|
||
"navigationBarTitleText": "我的待办",
|
||
"navigationBarBackgroundColor": "#5d87ff",
|
||
"navigationBarTextStyle": "white"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/task/taskFinish",
|
||
"style": {
|
||
"navigationBarTitleText": "我的已办",
|
||
"navigationBarBackgroundColor": "#5d87ff",
|
||
"navigationBarTextStyle": "white"
|
||
}
|
||
},
|
||
{
|
||
"path": "pages/task/taskCopyList",
|
||
"style": {
|
||
"navigationBarTitleText": "抄送我的",
|
||
"navigationBarBackgroundColor": "#5d87ff",
|
||
"navigationBarTextStyle": "white"
|
||
}
|
||
}
|
||
],
|
||
"globalStyle": {
|
||
"navigationBarTextStyle": "black",
|
||
"navigationBarTitleText": "智慧校园",
|
||
"navigationBarBackgroundColor": "#5d87ff",
|
||
"backgroundColor": "#F8F8F8"
|
||
}
|
||
}
|
||
```
|
||
|
||
### manifest.json 配置
|
||
|
||
```json
|
||
{
|
||
"name": "mobile2",
|
||
"appid": "",
|
||
"description": "",
|
||
"versionName": "1.0.0",
|
||
"versionCode": "100",
|
||
"transformPx": false,
|
||
"app-plus": {
|
||
"modules": {},
|
||
"distribute": {
|
||
"android": {
|
||
"permissions": [
|
||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>"
|
||
]
|
||
},
|
||
"ios": {},
|
||
"sdkConfigs": {}
|
||
},
|
||
"usingComponents": true
|
||
},
|
||
"quickapp": {},
|
||
"mp-weixin": {
|
||
"usingComponents": true,
|
||
"appid": "",
|
||
"setting": {
|
||
"urlCheck": true
|
||
}
|
||
},
|
||
"mp-alipay": {
|
||
"usingComponents": true
|
||
},
|
||
"mp-baidu": {
|
||
"usingComponents": true
|
||
},
|
||
"mp-toutiao": {
|
||
"usingComponents": true
|
||
},
|
||
"mp-qq": {
|
||
"usingComponents": true
|
||
}
|
||
}
|
||
```
|
||
|
||
## 9. 🎭 用户体验规范
|
||
|
||
### 交互设计规范
|
||
|
||
#### 动画效果使用
|
||
|
||
```javascript
|
||
// utils/animations.js - 动画工具
|
||
export const animationUtils = {
|
||
// 淡入动画
|
||
fadeIn: (element) => {
|
||
element.style.animation = 'fadeIn 0.3s ease';
|
||
},
|
||
|
||
// 滑入动画
|
||
slideUp: (element) => {
|
||
element.style.animation = 'slideUp 0.3s ease';
|
||
},
|
||
|
||
// 缩放动画
|
||
scale: (element) => {
|
||
element.style.animation = 'scale 0.2s ease';
|
||
},
|
||
}
|
||
|
||
// 在页面中使用
|
||
methods: {
|
||
handleItemClick() {
|
||
// 触觉反馈
|
||
uni.vibrateShort();
|
||
|
||
// 视觉反馈
|
||
// 处理点击逻辑
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 手势操作支持
|
||
|
||
```javascript
|
||
// utils/gestures.js - 手势识别
|
||
export const gestureManager = {
|
||
// 初始化手势
|
||
init: (element, callbacks) => {
|
||
let startX, startY, startTime;
|
||
|
||
element.addEventListener('touchstart', (e) => {
|
||
startX = e.touches[0].clientX;
|
||
startY = e.touches[0].clientY;
|
||
startTime = Date.now();
|
||
});
|
||
|
||
element.addEventListener('touchend', (e) => {
|
||
const endX = e.changedTouches[0].clientX;
|
||
const endY = e.changedTouches[0].clientY;
|
||
const endTime = Date.now();
|
||
|
||
// 判断手势类型
|
||
const deltaX = endX - startX;
|
||
const deltaY = endY - startY;
|
||
const deltaTime = endTime - startTime;
|
||
|
||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
|
||
if (deltaX > 0) {
|
||
callbacks.swipeRight && callbacks.swipeRight();
|
||
} else {
|
||
callbacks.swipeLeft && callbacks.swipeLeft();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
};
|
||
```
|
||
|
||
### 无障碍访问规范
|
||
|
||
```javascript
|
||
// utils/accessibility.js - 无障碍访问工具
|
||
export const accessibilityUtils = {
|
||
// 设置焦点
|
||
setFocus: (element) => {
|
||
element.focus();
|
||
},
|
||
|
||
// 添加ARIA标签
|
||
addAriaLabel: (element, label) => {
|
||
element.setAttribute('aria-label', label);
|
||
},
|
||
|
||
// 键盘导航支持
|
||
enableKeyboardNavigation: (container) => {
|
||
container.addEventListener('keydown', (e) => {
|
||
switch (e.key) {
|
||
case 'Tab':
|
||
// Tab导航逻辑
|
||
break;
|
||
case 'Enter':
|
||
case ' ':
|
||
// 激活元素
|
||
e.target.click();
|
||
break;
|
||
case 'Escape':
|
||
// 取消操作
|
||
break;
|
||
}
|
||
});
|
||
},
|
||
};
|
||
```
|
||
|
||
## 10. 🚀 性能优化规范
|
||
|
||
### 代码优化
|
||
|
||
#### 组件懒加载
|
||
|
||
```javascript
|
||
// 页面中使用懒加载
|
||
export default {
|
||
components: {
|
||
// 懒加载组件
|
||
LazyComponent: () => import('@/components/LazyComponent.vue'),
|
||
},
|
||
};
|
||
```
|
||
|
||
#### 图片优化
|
||
|
||
```vue
|
||
<template>
|
||
<!-- 响应式图片 -->
|
||
<image :src="imageSrc" mode="aspectFit" lazy-load :fade-show="true" @error="handleImageError" />
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
methods: {
|
||
handleImageError() {
|
||
// 图片加载失败处理
|
||
console.log('图片加载失败');
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 网络优化
|
||
|
||
#### 请求缓存策略
|
||
|
||
```javascript
|
||
// 智能缓存策略
|
||
export const cacheStrategy = {
|
||
// 短期缓存 (5分钟)
|
||
short: 5 * 60,
|
||
// 中期缓存 (30分钟)
|
||
medium: 30 * 60,
|
||
// 长期缓存 (24小时)
|
||
long: 24 * 60 * 60,
|
||
};
|
||
|
||
// API调用示例
|
||
export const api = {
|
||
getUserInfo: () => {
|
||
const cacheKey = 'userInfo';
|
||
const cached = storage.get(cacheKey);
|
||
|
||
if (cached) {
|
||
return Promise.resolve(cached);
|
||
}
|
||
|
||
return request({
|
||
url: '/user/info',
|
||
}).then((data) => {
|
||
storage.set(cacheKey, data, cacheStrategy.medium);
|
||
return data;
|
||
});
|
||
},
|
||
};
|
||
```
|
||
|
||
## 11. 🛠️ 开发工具配置
|
||
|
||
### 配置文件管理规范
|
||
|
||
#### manifest.json 配置 (推荐)
|
||
|
||
**⚠️ 重要**: uni-app 项目中,开发服务器配置应统一在 `manifest.json` 的 `h5.devServer` 中:
|
||
|
||
```json
|
||
{
|
||
"h5": {
|
||
"devServer": {
|
||
"https": false,
|
||
"allowedHosts": "all",
|
||
"port": 8081,
|
||
"host": "0.0.0.0",
|
||
"proxy": {
|
||
"/api": {
|
||
"target": "http://localhost:8080",
|
||
"changeOrigin": true,
|
||
"pathRewrite": {
|
||
"^/api": ""
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"publicPath": "./",
|
||
"router": {
|
||
"mode": "hash"
|
||
},
|
||
"title": "智慧校园移动端"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Vue CLI 配置 (vue.config.js)
|
||
|
||
**🔧 仅用于构建优化**,避免与 `manifest.json` 产生配置冲突:
|
||
|
||
```javascript
|
||
module.exports = {
|
||
transpileDependencies: ['uview-ui'],
|
||
configureWebpack: {
|
||
resolve: {
|
||
alias: {
|
||
'@': require('path').resolve(__dirname, 'src'),
|
||
},
|
||
},
|
||
},
|
||
};
|
||
```
|
||
|
||
#### 配置优先级说明
|
||
|
||
**uni-app 配置优先级**:
|
||
- **H5平台**: `manifest.json` > `vue.config.js`
|
||
- **小程序平台**: 仅使用 `manifest.json`
|
||
- **App平台**: 仅使用 `manifest.json`
|
||
|
||
**最佳实践**:
|
||
✅ **统一配置**: 开发服务器配置统一在 `manifest.json` 中
|
||
✅ **构建工具**: `vue.config.js` 仅用于构建优化
|
||
✅ **避免冲突**: 不要在两个文件中重复配置同一项目
|
||
✅ **多端兼容**: 确保配置在所有平台都生效
|
||
|
||
### 开发命令规范
|
||
|
||
```bash
|
||
# 安装依赖
|
||
npm install
|
||
|
||
# H5开发
|
||
npm run dev:h5
|
||
|
||
# 微信小程序开发
|
||
npm run dev:mp-weixin
|
||
|
||
# App开发
|
||
npm run dev:app-plus
|
||
|
||
# 构建H5
|
||
npm run build:h5
|
||
|
||
# 构建微信小程序
|
||
npm run build:mp-weixin
|
||
|
||
# 构建App
|
||
npm run build:app-plus
|
||
```
|
||
|
||
## 12. ✅ 最佳实践总结
|
||
|
||
### 代码规范
|
||
|
||
1. **文件组织**: 按功能模块组织文件结构
|
||
2. **组件复用**: 提取通用组件,避免重复代码
|
||
3. **样式管理**: 使用 CSS 变量系统,便于主题切换
|
||
4. **性能优化**: 合理使用缓存和懒加载
|
||
5. **用户体验**: 添加加载状态、错误处理和无障碍支持
|
||
|
||
### 开发流程
|
||
|
||
1. **需求分析** → 确定页面结构和功能
|
||
2. **组件设计** → 拆分可复用组件
|
||
3. **页面开发** → 按模板编写页面代码
|
||
4. **样式调优** → 使用统一的设计规范
|
||
5. **功能测试** → 多平台测试确保兼容性
|
||
6. **性能优化** → 优化加载速度和用户体验
|
||
|
||
### 质量保证
|
||
|
||
1. **代码审查**: 确保代码符合规范
|
||
2. **功能测试**: 确保所有功能正常工作
|
||
3. **兼容性测试**: 确保多平台兼容
|
||
4. **性能测试**: 确保应用性能满足要求
|
||
5. **用户体验测试**: 确保交互流畅自然
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
本规范基于实际的 Vue 2 + uniapp + uview-ui 项目制定,提供了完整的移动端开发流程。
|
||
|
||
**✨ 技术特色**:
|
||
|
||
- ✅ **Vue 2**: 稳定的响应式框架
|
||
- ✅ **uview-ui**: 成熟的 Vue 2 UI 组件库
|
||
- ✅ **现代 CSS**: 使用 CSS 变量的设计系统
|
||
- ✅ **无障碍访问**: 完整的无障碍访问支持
|
||
- ✅ **智能缓存**: 高效的数据缓存策略
|
||
- ✅ **用户体验**: 流畅的动画和手势支持
|
||
|
||
**🎨 设计理念**:
|
||
|
||
- 现代简约的视觉设计
|
||
- 一致的交互体验
|
||
- 响应式适配
|
||
- 无障碍友好
|
||
|
||
**📱 平台支持**:
|
||
|
||
- H5 (Web)
|
||
- 微信小程序
|
||
- 支付宝小程序
|
||
- iOS/Android App
|
||
- 鸿蒙 App
|
||
|
||
遵循本规范能够确保代码质量、提升开发效率,并为用户提供优秀的移动端体验。
|
||
|
||
## 13. 🔧 uView 2.0 组件使用规范
|
||
|
||
### uview-ui 图标组件规范(2025-01-03 更新)
|
||
- 移动端UI开发中,图标组件统一使用 <u-icon />(uview-ui 2.x 官方标准),同时支持iconfont自定义图标。
|
||
- 推荐通过 easycom 自动全局注册,无需手动 import。
|
||
- 标准用法示例:
|
||
```vue
|
||
<u-icon name="account" size="22" color="#b0b8c9" />
|
||
<u-icon name="lock-open" size="22" color="#b0b8c9" />
|
||
```
|
||
- 图标名称请参考 uview-ui 官方文档:https://www.uviewui.com/components/icon.html
|
||
|
||
### uview-ui 选择器组件规范(2025-01-03 新增)
|
||
**⚠️ 重要:u-picker组件必须严格按照以下规范使用,否则会出现数据显示空白问题!**
|
||
|
||
#### 1. 组件属性规范
|
||
```vue
|
||
<!-- ✅ 正确用法 -->
|
||
<u-picker
|
||
:columns="[optionsArray]" <!-- 必须是二维数组格式 -->
|
||
keyName="label" <!-- 指定显示字段名 -->
|
||
v-model="indexArray" <!-- 选中项索引数组 -->
|
||
:show="showPicker" <!-- 控制显示/隐藏 -->
|
||
@confirm="onConfirm" <!-- 确认事件 -->
|
||
@close="showPicker = false" <!-- 关闭事件 -->
|
||
></u-picker>
|
||
|
||
<!-- ❌ 错误用法(旧版本写法)-->
|
||
<u-picker
|
||
:range="optionsArray" <!-- 错误:应该用columns -->
|
||
range-key="label" <!-- 错误:应该用keyName -->
|
||
@change="onChange" <!-- 错误:应该用confirm -->
|
||
></u-picker>
|
||
```
|
||
|
||
#### 2. 数据格式要求
|
||
```javascript
|
||
// ✅ 正确的数据格式
|
||
data() {
|
||
return {
|
||
// 对象数组数据
|
||
optionsArray: [
|
||
{ label: "选项1", value: "1" },
|
||
{ label: "选项2", value: "2" },
|
||
{ label: "选项3", value: "3" }
|
||
],
|
||
// 选中索引(必须是数组格式)
|
||
indexArray: [0],
|
||
// 控制显示
|
||
showPicker: false
|
||
}
|
||
}
|
||
|
||
// 在模板中使用时,columns必须包装成二维数组
|
||
:columns="[optionsArray]" // 注意中括号包装
|
||
```
|
||
|
||
#### 3. 事件处理规范
|
||
```javascript
|
||
// ✅ 正确的事件处理
|
||
onConfirm(e) {
|
||
console.log('picker选择结果:', e);
|
||
const index = e.value[0]; // uview2格式:e.value
|
||
const selectedItem = this.optionsArray[index];
|
||
this.form.fieldName = selectedItem.value;
|
||
this.indexArray = [index];
|
||
this.showPicker = false;
|
||
}
|
||
|
||
// ❌ 错误的事件处理
|
||
onConfirm(e) {
|
||
const index = e.detail.value[0]; // 错误:小程序原生格式
|
||
}
|
||
```
|
||
|
||
#### 4. 完整使用示例
|
||
```vue
|
||
<template>
|
||
<view>
|
||
<!-- 显示区域 -->
|
||
<view class="picker-input" @click="showPicker = true">
|
||
{{ getSelectedLabel() || '请选择' }}
|
||
</view>
|
||
|
||
<!-- picker组件 -->
|
||
<u-picker
|
||
:columns="[optionsArray]"
|
||
keyName="label"
|
||
v-model="indexArray"
|
||
:show="showPicker"
|
||
@confirm="onConfirm"
|
||
@close="showPicker = false"
|
||
></u-picker>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
optionsArray: [
|
||
{ label: "汉族", value: "01" },
|
||
{ label: "蒙古族", value: "02" },
|
||
{ label: "回族", value: "03" }
|
||
],
|
||
indexArray: [0],
|
||
showPicker: false,
|
||
form: {
|
||
nationality: ''
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
onConfirm(e) {
|
||
const index = e.value[0];
|
||
this.indexArray = [index];
|
||
this.form.nationality = this.optionsArray[index]?.value || '';
|
||
this.showPicker = false;
|
||
},
|
||
getSelectedLabel() {
|
||
const index = this.indexArray[0];
|
||
return this.optionsArray[index]?.label || '';
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
#### 5. 常见错误及解决
|
||
- **问题1**: picker弹出后数据区域空白
|
||
- **原因**: 使用了`:range`而非`:columns`,或数据格式不是二维数组
|
||
- **解决**: 改用`:columns="[dataArray]"`格式
|
||
|
||
- **问题2**: 事件处理获取不到正确数据
|
||
- **原因**: 使用了`e.detail.value`而非`e.value`
|
||
- **解决**: 改用`e.value[0]`获取索引
|
||
|
||
- **问题3**: 无法显示中文选项文本
|
||
- **原因**: 未指定`keyName`属性
|
||
- **解决**: 添加`keyName="label"`指定显示字段
|
||
|
||
- 详细文档参考:https://www.uviewui.com/components/picker.html
|
||
|
||
### uview-ui 弹窗组件规范(2025-01-03 新增)
|
||
- 移动端弹窗开发统一使用 <u-popup />(uview-ui 2.x 官方标准)
|
||
- 推荐通过 easycom 自动全局注册,无需手动 import。
|
||
- 标准用法示例:
|
||
```vue
|
||
<u-popup v-model="show" mode="bottom" height="500">
|
||
<view class="popup-content">
|
||
<!-- 弹窗内容 -->
|
||
</view>
|
||
</u-popup>
|
||
```
|
||
- 常用属性:
|
||
- `v-model`: 控制弹窗显示/隐藏
|
||
- `mode`: 弹出方向(bottom、top、left、right、center)
|
||
- `height`: 弹窗高度(bottom/top模式)
|
||
- `width`: 弹窗宽度(left/right模式)
|
||
- `border-radius`: 圆角大小
|
||
- `closeable`: 是否显示关闭图标
|
||
- `mask-close`: 是否允许点击遮罩关闭
|
||
- 详细文档参考:https://www.uviewui.com/components/popup.html
|
||
|
||
---
|
||
|
||
**📝 维护**: 移动端开发规范文档
|
||
**🔄 更新**: 2025-01-03 (调整为Vue 2 + uview-ui技术栈,新增弹窗组件规范)
|
||
**📁 文件**: .cursor/cursorrules-mobile.md
|