保险前后端,养殖端和保险端小程序
This commit is contained in:
226
insurance_mini_program/App.vue
Normal file
226
insurance_mini_program/App.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<view id="app">
|
||||
<!-- 全局加载组件 -->
|
||||
<view v-if="appStore.globalLoading" class="global-loading">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 网络状态提示 -->
|
||||
<view v-if="!appStore.isOnline" class="network-offline">
|
||||
<text>网络连接已断开</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 获取store实例
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch')
|
||||
|
||||
// 初始化应用
|
||||
initApp()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
console.log('App Show')
|
||||
|
||||
// 检查登录状态
|
||||
userStore.checkAuth()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
console.log('App Hide')
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
async function initApp() {
|
||||
try {
|
||||
// 初始化应用store
|
||||
await appStore.initApp()
|
||||
|
||||
// 监听网络状态变化
|
||||
appStore.watchNetworkStatus()
|
||||
|
||||
// 检查登录状态
|
||||
userStore.checkAuth()
|
||||
|
||||
console.log('应用初始化完成')
|
||||
} catch (error) {
|
||||
console.error('应用初始化失败:', error)
|
||||
appStore.addError(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 全局样式 */
|
||||
@import '@/styles/variables.scss';
|
||||
@import '@/styles/base.scss';
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 全局加载样式 */
|
||||
.global-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.loading-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid $primary-color;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 网络离线提示 */
|
||||
.network-offline {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ff4d4f;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
/* 通用样式类 */
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 8rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.btn-primary {
|
||||
background: $primary-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background: darken($primary-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-secondary {
|
||||
background: $background-light;
|
||||
color: $text-primary;
|
||||
border: 1rpx solid $border-color;
|
||||
|
||||
&:active {
|
||||
background: darken($background-light, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: $success-color;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.mb-20 {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.p-20 {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.p-30 {
|
||||
padding: 30rpx;
|
||||
}
|
||||
</style>
|
||||
232
insurance_mini_program/README.md
Normal file
232
insurance_mini_program/README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# 保险端微信小程序 - 开发指南
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个基于 Vue.js 3.x + uni-app 开发的保险服务微信小程序,后端完全动态调用现有的 insurance_backend 接口,实现了用户登录、产品浏览、在线投保、保单管理、理赔申请等核心功能。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **前端框架**: Vue.js 3.x (Composition API)
|
||||
- **小程序框架**: uni-app
|
||||
- **状态管理**: Pinia
|
||||
- **UI组件**: 自定义组件 + uni-app内置组件
|
||||
- **网络请求**: 基于uni.request()封装
|
||||
- **后端API**: 动态调用 c:\nxxmdata\insurance_backend 的所有接口
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js: **严格要求 16.20.2**
|
||||
- npm: >= 8.0.0
|
||||
- 微信开发者工具: 最新稳定版
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
# 进入项目目录
|
||||
cd c:\nxxmdata\insurance_mini_program
|
||||
|
||||
# 检查Node.js版本(必须是16.20.2)
|
||||
node -v
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. 启动后端服务
|
||||
|
||||
```bash
|
||||
# 启动保险端后端服务
|
||||
cd c:\nxxmdata\insurance_backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
确保后端服务运行在 http://localhost:3000
|
||||
|
||||
### 3. 配置小程序
|
||||
|
||||
1. 修改 `utils/request.js` 中的 BASE_URL,确保指向正确的后端地址
|
||||
2. 在微信开发者工具中配置合法域名(开发时可关闭域名校验)
|
||||
|
||||
### 4. 编译运行
|
||||
|
||||
```bash
|
||||
# 开发模式编译到微信小程序
|
||||
npm run dev:mp-weixin
|
||||
|
||||
# 或使用
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### 5. 在微信开发者工具中预览
|
||||
|
||||
1. 打开微信开发者工具
|
||||
2. 导入项目,选择 `dist/dev/mp-weixin` 目录
|
||||
3. 配置AppID(或使用测试号)
|
||||
4. 预览调试
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
insurance_mini_program/
|
||||
├── components/ # 公共组件
|
||||
│ ├── ProductCard.vue # 产品卡片组件
|
||||
│ ├── StatusBadge.vue # 状态标签组件
|
||||
│ ├── LoadingSpinner.vue # 加载组件
|
||||
│ └── PolicyCard.vue # 保单卡片组件
|
||||
├── pages/ # 页面
|
||||
│ ├── index/ # 首页
|
||||
│ ├── login/ # 登录页
|
||||
│ ├── products/ # 产品列表
|
||||
│ ├── application/ # 投保申请
|
||||
│ └── my/ # 个人中心
|
||||
├── store/ # Pinia状态管理
|
||||
│ ├── index.js # Store入口
|
||||
│ ├── user.js # 用户状态
|
||||
│ └── insurance.js # 保险业务状态
|
||||
├── utils/ # 工具类
|
||||
│ ├── api.js # API接口封装
|
||||
│ ├── auth.js # 认证工具
|
||||
│ ├── request.js # 请求封装
|
||||
│ └── constants.js # 常量定义
|
||||
├── static/ # 静态资源
|
||||
├── App.vue # 应用入口组件
|
||||
├── main.js # 应用入口文件
|
||||
├── manifest.json # 应用配置
|
||||
├── pages.json # 页面配置
|
||||
├── uni.scss # 全局样式
|
||||
└── package.json # 项目配置
|
||||
```
|
||||
|
||||
## 核心功能模块
|
||||
|
||||
### 1. 用户认证模块
|
||||
- **微信一键登录**: 集成微信小程序授权登录
|
||||
- **账号密码登录**: 支持传统用户名密码登录
|
||||
- **登录状态管理**: 基于Pinia的用户状态管理
|
||||
- **Token管理**: JWT token自动管理和刷新
|
||||
|
||||
### 2. 产品浏览模块
|
||||
- **产品列表**: 动态加载保险产品,支持分类筛选
|
||||
- **产品搜索**: 实时搜索,防抖优化
|
||||
- **产品详情**: 展示详细的产品信息和条款
|
||||
|
||||
### 3. 投保申请模块
|
||||
- **在线申请**: 完整的投保表单,支持表单验证
|
||||
- **实时保费计算**: 根据保额动态计算保费
|
||||
- **材料上传**: 支持身份证等材料上传
|
||||
|
||||
### 4. 保单管理模块
|
||||
- **保单列表**: 查看个人所有保单
|
||||
- **保单详情**: 详细的保单信息展示
|
||||
- **保单状态**: 实时同步保单状态
|
||||
|
||||
### 5. 理赔申请模块
|
||||
- **理赔申请**: 在线提交理赔申请
|
||||
- **材料上传**: 支持理赔材料上传
|
||||
- **进度查询**: 实时查看理赔进度
|
||||
|
||||
## API接口说明
|
||||
|
||||
本项目所有API接口都动态调用 `c:\nxxmdata\insurance_backend` 的接口:
|
||||
|
||||
### 认证接口
|
||||
- `POST /api/auth/wx-login` - 微信登录(需后端扩展)
|
||||
- `POST /api/auth/login` - 账号密码登录
|
||||
- `GET /api/auth/profile` - 获取用户信息
|
||||
|
||||
### 产品接口
|
||||
- `GET /api/insurance-types` - 获取产品列表
|
||||
- `GET /api/insurance-types/:id` - 获取产品详情
|
||||
|
||||
### 申请接口
|
||||
- `POST /api/insurance/applications` - 提交投保申请
|
||||
- `GET /api/miniprogram/my-applications` - 获取我的申请(需后端扩展)
|
||||
|
||||
### 保单接口
|
||||
- `GET /api/miniprogram/my-policies` - 获取我的保单(需后端扩展)
|
||||
- `GET /api/policies/:id` - 获取保单详情
|
||||
|
||||
### 理赔接口
|
||||
- `POST /api/claims` - 提交理赔申请
|
||||
- `GET /api/miniprogram/my-claims` - 获取我的理赔(需后端扩展)
|
||||
|
||||
## 后端扩展要求
|
||||
|
||||
为了支持小程序的完整功能,需要在 `insurance_backend` 中扩展以下接口:
|
||||
|
||||
1. **微信登录接口**: `POST /api/auth/wx-login`
|
||||
2. **小程序专用路由**: `GET /api/miniprogram/*`
|
||||
3. **用户关联**: 在相关模型中添加 `user_id` 字段
|
||||
|
||||
详细的后端扩展代码已在开发文档中提供。
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 1. 代码规范
|
||||
- 使用 Vue 3 Composition API
|
||||
- 组件使用 PascalCase 命名
|
||||
- 文件使用 kebab-case 命名
|
||||
- 遵循 ESLint 规则
|
||||
|
||||
### 2. 状态管理
|
||||
- 使用 Pinia 进行状态管理
|
||||
- 按业务模块划分 Store
|
||||
- 异步操作统一在 actions 中处理
|
||||
|
||||
### 3. API调用
|
||||
- 统一使用封装的 request 方法
|
||||
- 错误处理统一在请求拦截器中
|
||||
- 支持请求缓存和防抖
|
||||
|
||||
### 4. 组件开发
|
||||
- 组件职责单一,高内聚低耦合
|
||||
- 合理使用 props 和 emit
|
||||
- 样式使用 scoped,避免污染
|
||||
|
||||
## 调试指南
|
||||
|
||||
### 1. 网络调试
|
||||
- 在微信开发者工具中查看网络请求
|
||||
- 检查后端服务是否正常运行
|
||||
- 确认API接口返回格式正确
|
||||
|
||||
### 2. 状态调试
|
||||
- 使用 Vue DevTools 调试状态
|
||||
- 在控制台查看 Pinia store 数据
|
||||
- 检查本地存储数据
|
||||
|
||||
### 3. 常见问题
|
||||
- **登录失败**: 检查后端微信登录接口是否实现
|
||||
- **API调用失败**: 确认后端服务运行状态
|
||||
- **页面跳转失败**: 检查页面路径配置
|
||||
|
||||
## 部署上线
|
||||
|
||||
### 1. 小程序发布
|
||||
1. 执行 `npm run build:mp-weixin` 构建生产版本
|
||||
2. 在微信开发者工具中上传代码
|
||||
3. 在微信公众平台提交审核
|
||||
4. 审核通过后发布上线
|
||||
|
||||
### 2. 后端配置
|
||||
- 配置生产环境的数据库
|
||||
- 设置正确的微信小程序配置
|
||||
- 配置HTTPS域名和SSL证书
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **版本兼容**: 严格使用 Node.js 16.20.2,避免版本冲突
|
||||
2. **接口调用**: 所有数据都从后端动态获取,禁止硬编码
|
||||
3. **错误处理**: 完善的错误提示和用户反馈
|
||||
4. **性能优化**: 合理使用分页加载和图片懒加载
|
||||
5. **安全考虑**: 敏感信息加密传输,Token安全管理
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题,请参考:
|
||||
1. [uni-app官方文档](https://uniapp.dcloud.net.cn/)
|
||||
2. [Vue.js官方文档](https://vuejs.org/)
|
||||
3. [Pinia官方文档](https://pinia.vuejs.org/)
|
||||
4. [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
||||
71
insurance_mini_program/index.html
Normal file
71
insurance_mini_program/index.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>保险服务 - H5版本</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.message {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>保险服务</h1>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div class="message">
|
||||
<h2>欢迎使用保险服务H5版本</h2>
|
||||
<p>这是一个临时的入口页面,用于测试H5版本的访问。</p>
|
||||
<p>完整的H5应用正在加载中,请稍候...</p>
|
||||
<a href="/insurance/" class="button">进入应用</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 自动重定向到实际的应用入口
|
||||
setTimeout(() => {
|
||||
window.location.href = '/insurance/';
|
||||
}, 3000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
14
insurance_mini_program/main.js
Normal file
14
insurance_mini_program/main.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import store from './store'
|
||||
import App from './App.vue'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// 使用Pinia状态管理
|
||||
app.use(store)
|
||||
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
44
insurance_mini_program/manifest.json
Normal file
44
insurance_mini_program/manifest.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "保险服务小程序",
|
||||
"appid": "your-miniprogram-appid",
|
||||
"description": "专业的保险服务平台,提供产品浏览、在线投保、理赔申请等服务",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"h5": {
|
||||
"title": "保险服务",
|
||||
"router": {
|
||||
"mode": "hash",
|
||||
"base": "/insurance/"
|
||||
}
|
||||
},
|
||||
"mp-weixin": {
|
||||
"appid": "your-miniprogram-appid",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"minified": true,
|
||||
"postcss": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "获取位置信息用于投保地址定位"
|
||||
}
|
||||
},
|
||||
"requiredBackgroundModes": ["location"],
|
||||
"plugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.3",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"quickapp": {}
|
||||
}
|
||||
17756
insurance_mini_program/package-lock.json
generated
Normal file
17756
insurance_mini_program/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
insurance_mini_program/package.json
Normal file
45
insurance_mini_program/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "insurance-miniprogram",
|
||||
"version": "1.0.0",
|
||||
"description": "保险端微信小程序 - 基于Vue.js 3.x + uni-app",
|
||||
"scripts": {
|
||||
"serve": "npm run dev:mp-weixin",
|
||||
"build": "npm run build:mp-weixin",
|
||||
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin uni",
|
||||
"build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin uni build",
|
||||
"dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 uni",
|
||||
"build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 uni build",
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
"lint:fix": "eslint --ext .js,.vue . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-components": "3.0.0-3081220230817001",
|
||||
"vue": "^3.3.4",
|
||||
"pinia": "^2.1.6",
|
||||
"@vant/weapp": "^1.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-3081220230817001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-3081220230817001",
|
||||
"@dcloudio/uni-automator": "3.0.0-3081220230817001",
|
||||
"@vue/runtime-core": "^3.3.4",
|
||||
"vite": "^4.4.9",
|
||||
"sass": "^1.64.1",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.20.2",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"author": "Insurance Team",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"insurance",
|
||||
"miniprogram",
|
||||
"vue3",
|
||||
"uni-app",
|
||||
"weixin"
|
||||
]
|
||||
}
|
||||
132
insurance_mini_program/pages.json
Normal file
132
insurance_mini_program/pages.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保险服务",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/products/products",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保险产品",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/product-detail/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "产品详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/application/application",
|
||||
"style": {
|
||||
"navigationBarTitleText": "投保申请"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/application-result/application-result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请结果"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/my/my",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/policies/policies",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的保单",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/policy-detail/policy-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保单详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/claims/claims",
|
||||
"style": {
|
||||
"navigationBarTitleText": "理赔申请",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/claim-detail/claim-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "理赔详情"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "保险服务",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"backgroundTextStyle": "dark",
|
||||
"app-plus": {
|
||||
"background": "#f5f5f5"
|
||||
}
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#1890ff",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "white",
|
||||
"height": "50px",
|
||||
"fontSize": "12px",
|
||||
"iconWidth": "24px",
|
||||
"spacing": "3px",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/images/tab-home.png",
|
||||
"selectedIconPath": "static/images/tab-home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/products/products",
|
||||
"text": "产品",
|
||||
"iconPath": "static/images/tab-products.png",
|
||||
"selectedIconPath": "static/images/tab-products-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/my",
|
||||
"text": "我的",
|
||||
"iconPath": "static/images/tab-my.png",
|
||||
"selectedIconPath": "static/images/tab-my-active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "首页",
|
||||
"path": "pages/index/index",
|
||||
"query": ""
|
||||
},
|
||||
{
|
||||
"name": "产品详情",
|
||||
"path": "pages/product-detail/product-detail",
|
||||
"query": "id=1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25
insurance_mini_program/project.config.json
Normal file
25
insurance_mini_program/project.config.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"uglifyFileName": false,
|
||||
"enhance": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"useCompilerPlugins": false,
|
||||
"minifyWXML": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"appid": "wx363d2520963f1853",
|
||||
"editorSetting": {}
|
||||
}
|
||||
14
insurance_mini_program/project.private.config.json
Normal file
14
insurance_mini_program/project.private.config.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"libVersion": "3.10.0",
|
||||
"projectname": "insurance_mini_program",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
||||
226
insurance_mini_program/src/App.vue
Normal file
226
insurance_mini_program/src/App.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<view id="app">
|
||||
<!-- 全局加载组件 -->
|
||||
<view v-if="appStore.globalLoading" class="global-loading">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 网络状态提示 -->
|
||||
<view v-if="!appStore.isOnline" class="network-offline">
|
||||
<text>网络连接已断开</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 获取store实例
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch')
|
||||
|
||||
// 初始化应用
|
||||
initApp()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
console.log('App Show')
|
||||
|
||||
// 检查登录状态
|
||||
userStore.checkAuth()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
console.log('App Hide')
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
async function initApp() {
|
||||
try {
|
||||
// 初始化应用store
|
||||
await appStore.initApp()
|
||||
|
||||
// 监听网络状态变化
|
||||
appStore.watchNetworkStatus()
|
||||
|
||||
// 检查登录状态
|
||||
userStore.checkAuth()
|
||||
|
||||
console.log('应用初始化完成')
|
||||
} catch (error) {
|
||||
console.error('应用初始化失败:', error)
|
||||
appStore.addError(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 全局样式 */
|
||||
@import '@/styles/variables.scss';
|
||||
@import '@/styles/base.scss';
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* 全局加载样式 */
|
||||
.global-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.loading-spinner {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid $primary-color;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 网络离线提示 */
|
||||
.network-offline {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ff4d4f;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
font-size: 24rpx;
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
/* 通用样式类 */
|
||||
.container {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 8rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.btn-primary {
|
||||
background: $primary-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background: darken($primary-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-secondary {
|
||||
background: $background-light;
|
||||
color: $text-primary;
|
||||
border: 1rpx solid $border-color;
|
||||
|
||||
&:active {
|
||||
background: darken($background-light, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: $success-color;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.mb-20 {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.p-20 {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.p-30 {
|
||||
padding: 30rpx;
|
||||
}
|
||||
</style>
|
||||
94
insurance_mini_program/src/components/LoadingSpinner.vue
Normal file
94
insurance_mini_program/src/components/LoadingSpinner.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<view class="loading-spinner" v-if="show">
|
||||
<view class="spinner-container">
|
||||
<view class="spinner" :class="{ spinning: spinning }"></view>
|
||||
<text v-if="text" class="loading-text">{{ text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
validator: (value) => ['small', 'normal', 'large'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const spinning = ref(false)
|
||||
|
||||
// 启动动画
|
||||
onMounted(() => {
|
||||
spinning.value = true
|
||||
})
|
||||
|
||||
// 清理动画
|
||||
onUnmounted(() => {
|
||||
spinning.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 60rpx;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f0f0f0;
|
||||
border-top: 4rpx solid #1890ff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.spinner.spinning {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 尺寸变体 */
|
||||
.spinner.size-small {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-width: 3rpx;
|
||||
}
|
||||
|
||||
.spinner.size-large {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-width: 5rpx;
|
||||
}
|
||||
</style>
|
||||
155
insurance_mini_program/src/components/PolicyCard.vue
Normal file
155
insurance_mini_program/src/components/PolicyCard.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<view class="policy-card" @tap="handleCardClick">
|
||||
<view class="policy-header">
|
||||
<view class="policy-info">
|
||||
<text class="policy-name">{{ policy.application?.insurance_type?.name }}</text>
|
||||
<StatusBadge
|
||||
:status="policy.status"
|
||||
type="policy"
|
||||
size="small"
|
||||
/>
|
||||
</view>
|
||||
<text class="policy-number">{{ policy.policy_number }}</text>
|
||||
</view>
|
||||
|
||||
<view class="policy-content">
|
||||
<view class="info-row">
|
||||
<text class="info-label">保险金额</text>
|
||||
<text class="info-value">{{ formatAmount(policy.coverage_amount) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">年保费</text>
|
||||
<text class="info-value primary">¥{{ policy.premium_amount }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">保险期间</text>
|
||||
<text class="info-value">{{ formatDateRange(policy.start_date, policy.end_date) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="policy-footer">
|
||||
<text class="create-date">投保时间:{{ formatDate(policy.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import StatusBadge from './StatusBadge.vue'
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
policy: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount) => {
|
||||
if (!amount) return '0万'
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
// 格式化日期范围
|
||||
const formatDateRange = (startDate, endDate) => {
|
||||
if (!startDate || !endDate) return ''
|
||||
const start = new Date(startDate).toLocaleDateString('zh-CN')
|
||||
const end = new Date(endDate).toLocaleDateString('zh-CN')
|
||||
return `${start} - ${end}`
|
||||
}
|
||||
|
||||
// 处理卡片点击
|
||||
const handleCardClick = () => {
|
||||
emit('click', props.policy)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.policy-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.policy-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.policy-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.policy-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.policy-number {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.policy-content {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 20rpx 0;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value.primary {
|
||||
color: #1890ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.policy-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.create-date {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
193
insurance_mini_program/src/components/ProductCard.vue
Normal file
193
insurance_mini_program/src/components/ProductCard.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view class="product-card" @tap="handleCardClick">
|
||||
<image
|
||||
:src="product.icon || '/static/images/default-product.png'"
|
||||
class="product-image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="product-content">
|
||||
<view class="product-header">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text v-if="product.is_hot" class="hot-tag">热门</text>
|
||||
</view>
|
||||
<text class="product-desc">{{ product.description }}</text>
|
||||
<view class="product-features" v-if="features.length > 0">
|
||||
<text
|
||||
v-for="feature in features"
|
||||
:key="feature"
|
||||
class="feature-tag"
|
||||
>
|
||||
{{ feature }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="product-footer">
|
||||
<view class="price-info">
|
||||
<text class="price-label">起保费</text>
|
||||
<text class="price-value">¥{{ product.min_premium }}</text>
|
||||
<text class="price-unit">/年</text>
|
||||
</view>
|
||||
<view class="coverage-info">
|
||||
<text class="coverage-label">最高保额</text>
|
||||
<text class="coverage-value">{{ formatAmount(product.max_amount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
product: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
showFeatures: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
// 计算属性
|
||||
const features = computed(() => {
|
||||
if (!props.showFeatures) return []
|
||||
|
||||
const featureList = []
|
||||
if (props.product.is_hot) featureList.push('热门推荐')
|
||||
if (props.product.min_premium <= 100) featureList.push('低保费')
|
||||
if (props.product.max_amount >= 1000000) featureList.push('高保额')
|
||||
|
||||
return featureList.slice(0, 2)
|
||||
})
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount) => {
|
||||
if (!amount) return '0'
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
}
|
||||
|
||||
// 处理卡片点击
|
||||
const handleCardClick = () => {
|
||||
emit('click', props.product)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-card {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hot-tag {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.product-features {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
background: #f0f9ff;
|
||||
color: #1890ff;
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.coverage-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.coverage-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coverage-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
136
insurance_mini_program/src/components/StatusBadge.vue
Normal file
136
insurance_mini_program/src/components/StatusBadge.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<text
|
||||
class="status-badge"
|
||||
:class="[`status-${type}`, size && `size-${size}`]"
|
||||
>
|
||||
{{ text }}
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
APPLICATION_STATUS_TEXT,
|
||||
POLICY_STATUS_TEXT,
|
||||
CLAIM_STATUS_TEXT
|
||||
} from '@/utils/constants'
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps({
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: (value) => ['default', 'application', 'policy', 'claim'].includes(value)
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
validator: (value) => ['small', 'normal', 'large'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
// 计算显示文本
|
||||
const text = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'application':
|
||||
return APPLICATION_STATUS_TEXT[props.status] || props.status
|
||||
case 'policy':
|
||||
return POLICY_STATUS_TEXT[props.status] || props.status
|
||||
case 'claim':
|
||||
return CLAIM_STATUS_TEXT[props.status] || props.status
|
||||
default:
|
||||
return props.status
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 尺寸变体 */
|
||||
.size-small {
|
||||
padding: 4rpx 12rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.size-normal {
|
||||
padding: 6rpx 16rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.size-large {
|
||||
padding: 8rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 默认状态样式 */
|
||||
.status-default {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 申请状态样式 */
|
||||
.status-pending {
|
||||
background: #fff7e6;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.status-under_review {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-approved {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-rejected {
|
||||
background: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
/* 保单状态样式 */
|
||||
.status-active {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background: #fff2f0;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.status-suspended {
|
||||
background: #fff7e6;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
/* 理赔状态样式 */
|
||||
.status-reviewing {
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.status-paid {
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
</style>
|
||||
14
insurance_mini_program/src/main.js
Normal file
14
insurance_mini_program/src/main.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
import store from './store'
|
||||
import App from './App.vue'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// 使用Pinia状态管理
|
||||
app.use(store)
|
||||
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
44
insurance_mini_program/src/manifest.json
Normal file
44
insurance_mini_program/src/manifest.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "保险服务小程序",
|
||||
"appid": "your-miniprogram-appid",
|
||||
"description": "专业的保险服务平台,提供产品浏览、在线投保、理赔申请等服务",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"h5": {
|
||||
"title": "保险服务",
|
||||
"router": {
|
||||
"mode": "hash",
|
||||
"base": "/insurance/"
|
||||
}
|
||||
},
|
||||
"mp-weixin": {
|
||||
"appid": "your-miniprogram-appid",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"minified": true,
|
||||
"postcss": true
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "获取位置信息用于投保地址定位"
|
||||
}
|
||||
},
|
||||
"requiredBackgroundModes": ["location"],
|
||||
"plugins": {
|
||||
"WechatSI": {
|
||||
"version": "0.3.3",
|
||||
"provider": "wx069ba97219f66d99"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mp-alipay": {
|
||||
"usingComponents": true
|
||||
},
|
||||
"quickapp": {}
|
||||
}
|
||||
132
insurance_mini_program/src/pages.json
Normal file
132
insurance_mini_program/src/pages.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保险服务",
|
||||
"enablePullDownRefresh": true,
|
||||
"backgroundTextStyle": "dark"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/products/products",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保险产品",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/product-detail/product-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "产品详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/application/application",
|
||||
"style": {
|
||||
"navigationBarTitleText": "投保申请"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/application-result/application-result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请结果"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/my/my",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/policies/policies",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的保单",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/policy-detail/policy-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "保单详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/claims/claims",
|
||||
"style": {
|
||||
"navigationBarTitleText": "理赔申请",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/claim-detail/claim-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "理赔详情"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "保险服务",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"backgroundTextStyle": "dark",
|
||||
"app-plus": {
|
||||
"background": "#f5f5f5"
|
||||
}
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#1890ff",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "white",
|
||||
"height": "50px",
|
||||
"fontSize": "12px",
|
||||
"iconWidth": "24px",
|
||||
"spacing": "3px",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/images/tab-home.png",
|
||||
"selectedIconPath": "static/images/tab-home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/products/products",
|
||||
"text": "产品",
|
||||
"iconPath": "static/images/tab-products.png",
|
||||
"selectedIconPath": "static/images/tab-products-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/my",
|
||||
"text": "我的",
|
||||
"iconPath": "static/images/tab-my.png",
|
||||
"selectedIconPath": "static/images/tab-my-active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"condition": {
|
||||
"current": 0,
|
||||
"list": [
|
||||
{
|
||||
"name": "首页",
|
||||
"path": "pages/index/index",
|
||||
"query": ""
|
||||
},
|
||||
{
|
||||
"name": "产品详情",
|
||||
"path": "pages/product-detail/product-detail",
|
||||
"query": "id=1"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
663
insurance_mini_program/src/pages/application/application.vue
Normal file
663
insurance_mini_program/src/pages/application/application.vue
Normal file
@@ -0,0 +1,663 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 产品信息卡片 -->
|
||||
<view v-if="product" class="product-card">
|
||||
<view class="product-header">
|
||||
<image :src="product.icon || '/static/images/default-product.png'" class="product-icon" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-desc">{{ product.description }}</text>
|
||||
<view class="product-price">
|
||||
<text class="price-range">保费:¥{{ product.min_premium }} - ¥{{ product.max_premium }}/年</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 投保表单 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">
|
||||
<text>投保信息</text>
|
||||
<text class="required-tip">* 为必填项</text>
|
||||
</view>
|
||||
|
||||
<!-- 投保人信息 -->
|
||||
<view class="form-group">
|
||||
<text class="form-label">投保人姓名 *</text>
|
||||
<input
|
||||
v-model="formData.customer_name"
|
||||
class="form-input"
|
||||
placeholder="请输入真实姓名"
|
||||
@input="onInputChange('customer_name', $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">身份证号 *</text>
|
||||
<input
|
||||
v-model="formData.customer_id_card"
|
||||
class="form-input"
|
||||
placeholder="请输入18位身份证号码"
|
||||
maxlength="18"
|
||||
@input="onInputChange('customer_id_card', $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">手机号码 *</text>
|
||||
<input
|
||||
v-model="formData.customer_phone"
|
||||
class="form-input"
|
||||
placeholder="请输入11位手机号码"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
@input="onInputChange('customer_phone', $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">联系地址 *</text>
|
||||
<textarea
|
||||
v-model="formData.customer_address"
|
||||
class="form-textarea"
|
||||
placeholder="请输入详细联系地址"
|
||||
maxlength="200"
|
||||
@input="onInputChange('customer_address', $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 保险信息 -->
|
||||
<view class="form-group">
|
||||
<text class="form-label">保险金额 *</text>
|
||||
<view class="amount-input-wrapper">
|
||||
<input
|
||||
v-model="formData.application_amount"
|
||||
class="form-input amount-input"
|
||||
placeholder="请输入保险金额"
|
||||
type="digit"
|
||||
@input="onInputChange('application_amount', $event)"
|
||||
/>
|
||||
<text class="amount-unit">万元</text>
|
||||
</view>
|
||||
<view class="amount-tips">
|
||||
<text>建议保额:{{ product?.min_amount }}万 - {{ product?.max_amount }}万</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 受益人信息 -->
|
||||
<view class="beneficiary-section">
|
||||
<text class="section-subtitle">受益人信息(可选)</text>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">受益人姓名</text>
|
||||
<input
|
||||
v-model="formData.beneficiary_name"
|
||||
class="form-input"
|
||||
placeholder="请输入受益人姓名"
|
||||
@input="onInputChange('beneficiary_name', $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">与投保人关系</text>
|
||||
<picker
|
||||
:value="relationIndex"
|
||||
:range="relationOptions"
|
||||
@change="onRelationChange"
|
||||
>
|
||||
<view class="picker-input">
|
||||
{{ formData.beneficiary_relation || '请选择与受益人关系' }}
|
||||
<text class="picker-arrow">></text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 附加信息 -->
|
||||
<view class="form-group">
|
||||
<text class="form-label">备注信息</text>
|
||||
<textarea
|
||||
v-model="formData.remarks"
|
||||
class="form-textarea"
|
||||
placeholder="请输入其他需要说明的信息(可选)"
|
||||
maxlength="500"
|
||||
@input="onInputChange('remarks', $event)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保费预览 -->
|
||||
<view class="premium-preview" v-if="estimatedPremium">
|
||||
<view class="preview-header">
|
||||
<text>保费预览</text>
|
||||
</view>
|
||||
<view class="preview-content">
|
||||
<view class="preview-item">
|
||||
<text class="preview-label">保险金额</text>
|
||||
<text class="preview-value">{{ formData.application_amount }}万元</text>
|
||||
</view>
|
||||
<view class="preview-item">
|
||||
<text class="preview-label">预估年保费</text>
|
||||
<text class="preview-value primary">¥{{ estimatedPremium }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 条款确认 -->
|
||||
<view class="agreement-section">
|
||||
<view class="agreement-item" @tap="toggleAgreement">
|
||||
<image
|
||||
:src="agreedToTerms ? '/static/images/checkbox-checked.png' : '/static/images/checkbox-unchecked.png'"
|
||||
class="checkbox"
|
||||
/>
|
||||
<text class="agreement-text">
|
||||
我已阅读并同意
|
||||
<text class="agreement-link" @tap.stop="showTerms">《保险条款》</text>
|
||||
和
|
||||
<text class="agreement-link" @tap.stop="showPrivacy">《隐私政策》</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-section">
|
||||
<button
|
||||
class="submit-btn"
|
||||
:class="{ disabled: !canSubmit }"
|
||||
:disabled="!canSubmit || submitting"
|
||||
@tap="submitApplication"
|
||||
>
|
||||
{{ submitting ? '提交中...' : '提交申请' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useInsuranceStore } from '@/store/insurance'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { auth } from '@/utils/auth'
|
||||
import { BENEFICIARY_RELATIONS } from '@/utils/constants'
|
||||
|
||||
// 状态管理
|
||||
const insuranceStore = useInsuranceStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 获取路由参数
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const productId = ref(currentPage.options.productId)
|
||||
|
||||
// 响应式数据
|
||||
const product = ref(null)
|
||||
const submitting = ref(false)
|
||||
const agreedToTerms = ref(false)
|
||||
const relationIndex = ref(-1)
|
||||
|
||||
// 关系选项
|
||||
const relationOptions = ref(BENEFICIARY_RELATIONS)
|
||||
|
||||
// 表单数据(使用reactive实现响应式)
|
||||
const formData = reactive({
|
||||
customer_name: '',
|
||||
customer_id_card: '',
|
||||
customer_phone: '',
|
||||
customer_address: '',
|
||||
application_amount: '',
|
||||
beneficiary_name: '',
|
||||
beneficiary_relation: '',
|
||||
remarks: ''
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const canSubmit = computed(() => {
|
||||
return formData.customer_name &&
|
||||
formData.customer_id_card &&
|
||||
formData.customer_phone &&
|
||||
formData.customer_address &&
|
||||
formData.application_amount &&
|
||||
agreedToTerms.value &&
|
||||
!submitting.value
|
||||
})
|
||||
|
||||
// 预估保费计算
|
||||
const estimatedPremium = computed(() => {
|
||||
if (!formData.application_amount || !product.value) return 0
|
||||
|
||||
const amount = parseFloat(formData.application_amount)
|
||||
const rate = product.value.premium_rate || 0.005 // 默认费率0.5%
|
||||
|
||||
return Math.round(amount * 10000 * rate)
|
||||
})
|
||||
|
||||
// 加载产品信息(动态调用保险端后端)
|
||||
const loadProductInfo = async () => {
|
||||
try {
|
||||
if (!productId.value) {
|
||||
uni.showToast({
|
||||
title: '产品信息缺失',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 动态调用 /api/insurance-types/:id
|
||||
const productData = await insuranceStore.fetchProductDetail(productId.value)
|
||||
product.value = productData
|
||||
|
||||
console.log('产品信息加载成功:', productData)
|
||||
} catch (error) {
|
||||
console.error('加载产品信息失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载产品信息失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 表单输入处理(手动更新避免v-model问题)
|
||||
const onInputChange = (field, event) => {
|
||||
const value = event.detail ? event.detail.value : event.target.value
|
||||
formData[field] = value
|
||||
|
||||
// 特殊处理身份证号格式
|
||||
if (field === 'customer_id_card') {
|
||||
formData[field] = value.toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
// 关系选择处理
|
||||
const onRelationChange = (event) => {
|
||||
const index = event.detail.value
|
||||
relationIndex.value = index
|
||||
formData.beneficiary_relation = relationOptions.value[index]
|
||||
}
|
||||
|
||||
// 切换条款同意状态
|
||||
const toggleAgreement = () => {
|
||||
agreedToTerms.value = !agreedToTerms.value
|
||||
}
|
||||
|
||||
// 显示保险条款
|
||||
const showTerms = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/terms/terms?type=insurance'
|
||||
})
|
||||
}
|
||||
|
||||
// 显示隐私政策
|
||||
const showPrivacy = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/terms/terms?type=privacy'
|
||||
})
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
if (!formData.customer_name.trim()) {
|
||||
uni.showToast({ title: '请输入姓名', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
// 身份证号验证
|
||||
const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/
|
||||
if (!idCardRegex.test(formData.customer_id_card)) {
|
||||
uni.showToast({ title: '请输入正确的身份证号', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
// 手机号验证
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(formData.customer_phone)) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
if (!formData.customer_address.trim()) {
|
||||
uni.showToast({ title: '请输入联系地址', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
const amount = parseFloat(formData.application_amount)
|
||||
if (!amount || amount <= 0) {
|
||||
uni.showToast({ title: '请输入正确的保险金额', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查保额范围
|
||||
if (product.value) {
|
||||
if (amount < product.value.min_amount || amount > product.value.max_amount) {
|
||||
uni.showToast({
|
||||
title: `保险金额应在${product.value.min_amount}-${product.value.max_amount}万元之间`,
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!agreedToTerms.value) {
|
||||
uni.showToast({ title: '请先同意保险条款', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 提交申请(动态调用保险端后端)
|
||||
const submitApplication = async () => {
|
||||
// 检查登录状态
|
||||
if (!auth.requireAuth()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateForm()) return
|
||||
if (submitting.value) return
|
||||
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
const applicationData = {
|
||||
...formData,
|
||||
insurance_type_id: productId.value,
|
||||
application_amount: parseFloat(formData.application_amount) * 10000 // 转换为元
|
||||
}
|
||||
|
||||
console.log('提交申请数据:', applicationData)
|
||||
|
||||
// 动态调用保险端 /api/insurance/applications 接口
|
||||
const result = await insuranceStore.submitApplication(applicationData)
|
||||
|
||||
uni.showToast({
|
||||
title: '申请提交成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 跳转到申请结果页面
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
url: `/pages/application-result/application-result?id=${result.id}`
|
||||
})
|
||||
}, 1500)
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交申请失败:', error)
|
||||
|
||||
let errorMessage = '提交失败,请重试'
|
||||
if (error.message) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadProductInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 28rpx;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 40rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
padding: 0 30rpx;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 120rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.amount-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.amount-unit {
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.amount-tips {
|
||||
margin-top: 15rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.beneficiary-section {
|
||||
margin-top: 40rpx;
|
||||
padding-top: 40rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.picker-input {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
padding: 0 30rpx;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.premium-preview {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.preview-value.primary {
|
||||
color: #1890ff;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.agreement-section {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.agreement-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: #1890ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.submit-section {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 20rpx 30rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.submit-btn.disabled {
|
||||
background: #d9d9d9;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
372
insurance_mini_program/src/pages/index/index.vue
Normal file
372
insurance_mini_program/src/pages/index/index.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 轮播图 -->
|
||||
<swiper class="banner-swiper" indicator-dots="true" autoplay="true" circular="true">
|
||||
<swiper-item v-for="banner in banners" :key="banner.id">
|
||||
<image :src="banner.image" class="banner-image" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 快捷入口 -->
|
||||
<view class="quick-actions">
|
||||
<view class="action-item" @tap="goToProducts">
|
||||
<image src="/static/images/icon-products.png" class="action-icon" />
|
||||
<text class="action-text">保险产品</text>
|
||||
</view>
|
||||
<view class="action-item" @tap="goToMy">
|
||||
<image src="/static/images/icon-policy.png" class="action-icon" />
|
||||
<text class="action-text">我的保单</text>
|
||||
</view>
|
||||
<view class="action-item" @tap="goToClaims">
|
||||
<image src="/static/images/icon-claim.png" class="action-icon" />
|
||||
<text class="action-text">理赔申请</text>
|
||||
</view>
|
||||
<view class="action-item" @tap="goToService">
|
||||
<image src="/static/images/icon-service.png" class="action-icon" />
|
||||
<text class="action-text">客服服务</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门产品 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">热门产品</text>
|
||||
<text class="section-more" @tap="goToProducts">更多 ></text>
|
||||
</view>
|
||||
|
||||
<view class="product-grid" v-if="!loading">
|
||||
<view
|
||||
v-for="item in hotProducts"
|
||||
:key="item.id"
|
||||
class="product-card"
|
||||
@tap="goToProductDetail(item.id)"
|
||||
>
|
||||
<image :src="item.icon || '/static/images/default-product.png'" class="product-icon" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.name }}</text>
|
||||
<text class="product-desc">{{ item.description }}</text>
|
||||
<view class="product-price">
|
||||
<text class="price-label">起保费:</text>
|
||||
<text class="price-value">¥{{ item.min_premium }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading" class="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && hotProducts.length === 0" class="empty-state">
|
||||
<image src="/static/images/empty-products.png" class="empty-icon" />
|
||||
<text>暂无热门产品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务优势 -->
|
||||
<view class="section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">服务优势</text>
|
||||
</view>
|
||||
|
||||
<view class="advantage-grid">
|
||||
<view class="advantage-item">
|
||||
<image src="/static/images/advantage-1.png" class="advantage-icon" />
|
||||
<text class="advantage-title">专业保障</text>
|
||||
<text class="advantage-desc">专业团队提供全方位保险咨询</text>
|
||||
</view>
|
||||
<view class="advantage-item">
|
||||
<image src="/static/images/advantage-2.png" class="advantage-icon" />
|
||||
<text class="advantage-title">快速理赔</text>
|
||||
<text class="advantage-desc">7x24小时快速理赔服务</text>
|
||||
</view>
|
||||
<view class="advantage-item">
|
||||
<image src="/static/images/advantage-3.png" class="advantage-icon" />
|
||||
<text class="advantage-title">安全可靠</text>
|
||||
<text class="advantage-desc">银行级安全保障体系</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useInsuranceStore } from '@/store/insurance'
|
||||
|
||||
// 状态管理
|
||||
const insuranceStore = useInsuranceStore()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(true)
|
||||
const banners = ref([
|
||||
{
|
||||
id: 1,
|
||||
image: '/static/images/banner-1.jpg',
|
||||
title: '专业保险服务'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
image: '/static/images/banner-2.jpg',
|
||||
title: '安心保障'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
image: '/static/images/banner-3.jpg',
|
||||
title: '贴心理赔'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const hotProducts = computed(() => insuranceStore.getHotProducts)
|
||||
|
||||
// 加载页面数据(动态调用保险端后端)
|
||||
const loadPageData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
// 动态调用保险端 /api/insurance-types 接口获取热门产品
|
||||
await insuranceStore.fetchHotProducts()
|
||||
|
||||
console.log('热门产品加载成功:', hotProducts.value)
|
||||
} catch (error) {
|
||||
console.error('加载首页数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 页面跳转方法
|
||||
const goToProductDetail = (id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product-detail/product-detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
const goToProducts = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/products/products'
|
||||
})
|
||||
}
|
||||
|
||||
const goToMy = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/my/my'
|
||||
})
|
||||
}
|
||||
|
||||
const goToClaims = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/claims/claims'
|
||||
})
|
||||
}
|
||||
|
||||
const goToService = () => {
|
||||
uni.showModal({
|
||||
title: '客服服务',
|
||||
content: '客服电话:400-888-8888\n服务时间:周一至周日 9:00-18:00',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onPullDownRefresh = async () => {
|
||||
try {
|
||||
await loadPageData()
|
||||
} finally {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadPageData()
|
||||
})
|
||||
|
||||
// 页面显示时刷新数据
|
||||
onShow(() => {
|
||||
// 如果数据为空,重新加载
|
||||
if (hotProducts.value.length === 0) {
|
||||
loadPageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
height: 400rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 40rpx 20rpx;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 20rpx;
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-more {
|
||||
color: #1890ff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 12rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.product-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-right: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 32rpx;
|
||||
color: #ff6b35;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.advantage-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.advantage-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.advantage-icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.advantage-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.advantage-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
300
insurance_mini_program/src/pages/login/login.vue
Normal file
300
insurance_mini_program/src/pages/login/login.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="navbar-content">
|
||||
<text class="navbar-title">登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录内容 -->
|
||||
<view class="login-content">
|
||||
<!-- Logo区域 -->
|
||||
<view class="logo-section">
|
||||
<image src="/static/images/logo.png" class="logo" />
|
||||
<text class="app-name">保险服务</text>
|
||||
<text class="app-desc">专业的保险服务平台</text>
|
||||
</view>
|
||||
|
||||
<!-- 微信登录按钮 -->
|
||||
<view class="login-section">
|
||||
<button
|
||||
class="wx-login-btn"
|
||||
:disabled="isLogging"
|
||||
@tap="handleWxLogin"
|
||||
>
|
||||
<image src="/static/images/wechat-icon.png" class="wx-icon" />
|
||||
<text class="btn-text">{{ isLogging ? '登录中...' : '微信登录' }}</text>
|
||||
</button>
|
||||
|
||||
<!-- 登录说明 -->
|
||||
<view class="login-tips">
|
||||
<text class="tip-text">登录即表示同意</text>
|
||||
<text class="link-text" @tap="showPrivacyPolicy">《隐私政策》</text>
|
||||
<text class="tip-text">和</text>
|
||||
<text class="link-text" @tap="showUserAgreement">《用户协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<view class="footer">
|
||||
<text class="footer-text">安全可靠 · 专业服务</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
// 状态管理
|
||||
const userStore = useUserStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
// 响应式数据
|
||||
const isLogging = ref(false)
|
||||
const statusBarHeight = ref(0)
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 获取状态栏高度
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 检查是否已登录
|
||||
if (userStore.isAuthenticated) {
|
||||
redirectToHome()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 微信登录处理
|
||||
* 动态调用保险端后端微信认证接口
|
||||
*/
|
||||
async function handleWxLogin() {
|
||||
if (isLogging.value) return
|
||||
|
||||
isLogging.value = true
|
||||
|
||||
try {
|
||||
console.log('开始微信登录...')
|
||||
|
||||
// 显示登录提示
|
||||
uni.showLoading({
|
||||
title: '登录中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 调用store中的微信登录方法(动态调用保险端后端)
|
||||
const result = await userStore.wxLogin()
|
||||
|
||||
console.log('微信登录成功:', result)
|
||||
|
||||
// 登录成功提示
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 延迟跳转,让用户看到成功提示
|
||||
setTimeout(() => {
|
||||
redirectToHome()
|
||||
}, 1500)
|
||||
|
||||
} catch (error) {
|
||||
console.error('微信登录失败:', error)
|
||||
|
||||
// 处理不同类型的错误
|
||||
let errorMsg = '登录失败,请稍后重试'
|
||||
|
||||
if (error.message) {
|
||||
if (error.message.includes('用户拒绝')) {
|
||||
errorMsg = '需要您的授权才能登录'
|
||||
} else if (error.message.includes('网络')) {
|
||||
errorMsg = '网络连接失败,请检查网络设置'
|
||||
} else {
|
||||
errorMsg = error.message
|
||||
}
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '登录失败',
|
||||
content: errorMsg,
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
})
|
||||
|
||||
// 记录错误到应用store
|
||||
appStore.addError({
|
||||
type: 'login_error',
|
||||
message: errorMsg,
|
||||
detail: error
|
||||
})
|
||||
|
||||
} finally {
|
||||
isLogging.value = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到首页
|
||||
*/
|
||||
function redirectToHome() {
|
||||
// 使用 reLaunch 重新启动到首页,清除页面栈
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示隐私政策
|
||||
*/
|
||||
function showPrivacyPolicy() {
|
||||
uni.showModal({
|
||||
title: '隐私政策',
|
||||
content: '我们重视您的隐私,详细的隐私政策请访问官网查看。',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示用户协议
|
||||
*/
|
||||
function showUserAgreement() {
|
||||
uni.showModal({
|
||||
title: '用户协议',
|
||||
content: '请仔细阅读用户协议条款,详细协议请访问官网查看。',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 自定义导航栏 */
|
||||
.custom-navbar {
|
||||
position: relative;
|
||||
|
||||
.navbar-content {
|
||||
height: 88rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.navbar-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 登录内容 */
|
||||
.login-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60rpx 60rpx 120rpx;
|
||||
}
|
||||
|
||||
/* Logo区域 */
|
||||
.logo-section {
|
||||
text-align: center;
|
||||
margin-bottom: 120rpx;
|
||||
|
||||
.logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 32rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.app-desc {
|
||||
font-size: $font-size-md;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
/* 登录区域 */
|
||||
.login-section {
|
||||
.wx-login-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: white;
|
||||
border-radius: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: $shadow-normal;
|
||||
border: none;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.wx-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: $font-size-lg;
|
||||
color: $text-primary;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.login-tips {
|
||||
text-align: center;
|
||||
|
||||
.tip-text {
|
||||
font-size: $font-size-xs;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.link-text {
|
||||
font-size: $font-size-xs;
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部信息 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
|
||||
.footer-text {
|
||||
font-size: $font-size-sm;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
463
insurance_mini_program/src/pages/my/my.vue
Normal file
463
insurance_mini_program/src/pages/my/my.vue
Normal file
@@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info" v-if="userStore.isAuthenticated">
|
||||
<image :src="userStore.avatar" class="avatar" />
|
||||
<view class="user-details">
|
||||
<text class="username">{{ userStore.nickname }}</text>
|
||||
<text class="user-desc">{{ userStore.userInfo?.phone || '未绑定手机号' }}</text>
|
||||
</view>
|
||||
<text class="settings-btn" @tap="goToSettings">设置</text>
|
||||
</view>
|
||||
|
||||
<!-- 未登录状态 -->
|
||||
<view class="login-prompt" v-else @tap="goToLogin">
|
||||
<image src="/static/images/default-avatar.png" class="avatar" />
|
||||
<view class="user-details">
|
||||
<text class="username">点击登录</text>
|
||||
<text class="user-desc">登录后享受更多服务</text>
|
||||
</view>
|
||||
<text class="login-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计数据 -->
|
||||
<view class="stats-section" v-if="userStore.isAuthenticated">
|
||||
<view class="stats-grid">
|
||||
<view class="stats-item" @tap="goToPolicies">
|
||||
<text class="stats-number">{{ stats.policies }}</text>
|
||||
<text class="stats-label">我的保单</text>
|
||||
</view>
|
||||
<view class="stats-item" @tap="goToClaims">
|
||||
<text class="stats-number">{{ stats.claims }}</text>
|
||||
<text class="stats-label">理赔申请</text>
|
||||
</view>
|
||||
<view class="stats-item" @tap="goToApplications">
|
||||
<text class="stats-number">{{ stats.applications }}</text>
|
||||
<text class="stats-label">投保申请</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能菜单 -->
|
||||
<view class="menu-section">
|
||||
<!-- 我的服务 -->
|
||||
<view class="menu-group">
|
||||
<text class="group-title">我的服务</text>
|
||||
|
||||
<view class="menu-item" @tap="goToPolicies">
|
||||
<image src="/static/images/menu-policy.png" class="menu-icon" />
|
||||
<text class="menu-text">我的保单</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="goToClaims">
|
||||
<image src="/static/images/menu-claim.png" class="menu-icon" />
|
||||
<text class="menu-text">理赔申请</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="goToApplications">
|
||||
<image src="/static/images/menu-application.png" class="menu-icon" />
|
||||
<text class="menu-text">投保申请</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="goToProfile">
|
||||
<image src="/static/images/menu-profile.png" class="menu-icon" />
|
||||
<text class="menu-text">个人资料</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 帮助中心 -->
|
||||
<view class="menu-group">
|
||||
<text class="group-title">帮助中心</text>
|
||||
|
||||
<view class="menu-item" @tap="goToHelp">
|
||||
<image src="/static/images/menu-help.png" class="menu-icon" />
|
||||
<text class="menu-text">常见问题</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="contactService">
|
||||
<image src="/static/images/menu-service.png" class="menu-icon" />
|
||||
<text class="menu-text">联系客服</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="goToFeedback">
|
||||
<image src="/static/images/menu-feedback.png" class="menu-icon" />
|
||||
<text class="menu-text">意见反馈</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 其他 -->
|
||||
<view class="menu-group">
|
||||
<text class="group-title">其他</text>
|
||||
|
||||
<view class="menu-item" @tap="goToAbout">
|
||||
<image src="/static/images/menu-about.png" class="menu-icon" />
|
||||
<text class="menu-text">关于我们</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" @tap="checkUpdate">
|
||||
<image src="/static/images/menu-update.png" class="menu-icon" />
|
||||
<text class="menu-text">检查更新</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class="logout-section" v-if="userStore.isAuthenticated">
|
||||
<button class="logout-btn" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useInsuranceStore } from '@/store/insurance'
|
||||
|
||||
// 状态管理
|
||||
const userStore = useUserStore()
|
||||
const insuranceStore = useInsuranceStore()
|
||||
|
||||
// 响应式数据
|
||||
const stats = reactive({
|
||||
policies: 0,
|
||||
claims: 0,
|
||||
applications: 0
|
||||
})
|
||||
|
||||
// 加载用户统计数据(动态调用保险端后端)
|
||||
const loadUserStats = async () => {
|
||||
if (!userStore.isAuthenticated) return
|
||||
|
||||
try {
|
||||
// 这里可以添加专门的统计接口调用
|
||||
// 暂时使用模拟数据
|
||||
stats.policies = 3
|
||||
stats.claims = 1
|
||||
stats.applications = 2
|
||||
|
||||
console.log('用户统计数据加载成功')
|
||||
} catch (error) {
|
||||
console.error('加载用户统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面跳转方法
|
||||
const goToLogin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
|
||||
const goToSettings = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/settings/settings'
|
||||
})
|
||||
}
|
||||
|
||||
const goToPolicies = () => {
|
||||
if (!userStore.isAuthenticated) {
|
||||
goToLogin()
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/policies/policies'
|
||||
})
|
||||
}
|
||||
|
||||
const goToClaims = () => {
|
||||
if (!userStore.isAuthenticated) {
|
||||
goToLogin()
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/claims/claims'
|
||||
})
|
||||
}
|
||||
|
||||
const goToApplications = () => {
|
||||
if (!userStore.isAuthenticated) {
|
||||
goToLogin()
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/applications/applications'
|
||||
})
|
||||
}
|
||||
|
||||
const goToProfile = () => {
|
||||
if (!userStore.isAuthenticated) {
|
||||
goToLogin()
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/profile/profile'
|
||||
})
|
||||
}
|
||||
|
||||
const goToHelp = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/help/help'
|
||||
})
|
||||
}
|
||||
|
||||
const contactService = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['拨打客服电话', '在线客服'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: '400-888-8888'
|
||||
})
|
||||
} else if (res.tapIndex === 1) {
|
||||
uni.showToast({
|
||||
title: '在线客服功能开发中',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToFeedback = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/feedback/feedback'
|
||||
})
|
||||
}
|
||||
|
||||
const goToAbout = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/about/about'
|
||||
})
|
||||
}
|
||||
|
||||
const checkUpdate = () => {
|
||||
uni.showLoading({
|
||||
title: '检查中...'
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '当前已是最新版本',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: '确认退出',
|
||||
content: '确定要退出登录吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
// 调用用户状态管理的退出方法
|
||||
await userStore.logout()
|
||||
|
||||
// 清理统计数据
|
||||
stats.policies = 0
|
||||
stats.claims = 0
|
||||
stats.applications = 0
|
||||
|
||||
uni.showToast({
|
||||
title: '已退出登录',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('退出登录失败:', error)
|
||||
uni.showToast({
|
||||
title: '退出失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onPullDownRefresh = async () => {
|
||||
try {
|
||||
if (userStore.isAuthenticated) {
|
||||
await Promise.all([
|
||||
userStore.getUserProfile(),
|
||||
loadUserStats()
|
||||
])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新失败:', error)
|
||||
} finally {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadUserStats()
|
||||
})
|
||||
|
||||
// 页面显示时刷新数据
|
||||
onShow(() => {
|
||||
if (userStore.isAuthenticated) {
|
||||
loadUserStats()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
padding: 40rpx 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.user-info,
|
||||
.login-prompt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
margin-right: 30rpx;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-desc {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.settings-btn,
|
||||
.login-arrow {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10rpx 20rpx;
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1890ff;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.menu-group {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #fff;
|
||||
color: #f5222d;
|
||||
border: 1px solid #f5222d;
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
background: #fff2f0;
|
||||
}
|
||||
</style>
|
||||
498
insurance_mini_program/src/pages/products/products.vue
Normal file
498
insurance_mini_program/src/pages/products/products.vue
Normal file
@@ -0,0 +1,498 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input-wrapper">
|
||||
<image src="/static/images/search-icon.png" class="search-icon" />
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="search-input"
|
||||
placeholder="搜索保险产品"
|
||||
@input="onSearchInput"
|
||||
@confirm="handleSearch"
|
||||
/>
|
||||
<text v-if="searchKeyword" class="clear-btn" @tap="clearSearch">×</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<view class="filter-tabs">
|
||||
<scroll-view class="tabs-scroll" scroll-x="true">
|
||||
<view class="tab-list">
|
||||
<text
|
||||
v-for="category in categories"
|
||||
:key="category.value"
|
||||
class="tab-item"
|
||||
:class="{ active: selectedCategory === category.value }"
|
||||
@tap="selectCategory(category.value)"
|
||||
>
|
||||
{{ category.label }}
|
||||
</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 产品列表 -->
|
||||
<view class="product-list">
|
||||
<view
|
||||
v-for="product in products"
|
||||
:key="product.id"
|
||||
class="product-item"
|
||||
@tap="goToProductDetail(product.id)"
|
||||
>
|
||||
<image
|
||||
:src="product.icon || '/static/images/default-product.png'"
|
||||
class="product-image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="product-content">
|
||||
<view class="product-header">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text v-if="product.is_hot" class="hot-tag">热门</text>
|
||||
</view>
|
||||
<text class="product-desc">{{ product.description }}</text>
|
||||
<view class="product-features">
|
||||
<text
|
||||
v-for="feature in getProductFeatures(product)"
|
||||
:key="feature"
|
||||
class="feature-tag"
|
||||
>
|
||||
{{ feature }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="product-footer">
|
||||
<view class="price-info">
|
||||
<text class="price-label">起保费</text>
|
||||
<text class="price-value">¥{{ product.min_premium }}</text>
|
||||
<text class="price-unit">/年</text>
|
||||
</view>
|
||||
<view class="coverage-info">
|
||||
<text class="coverage-label">最高保额</text>
|
||||
<text class="coverage-value">{{ formatAmount(product.max_amount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-if="loading" class="loading">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && products.length === 0" class="empty-state">
|
||||
<image src="/static/images/empty-products.png" class="empty-icon" />
|
||||
<text class="empty-text">{{ searchKeyword ? '未找到相关产品' : '暂无产品' }}</text>
|
||||
<button v-if="searchKeyword" class="empty-btn" @tap="clearSearch">清除搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="hasMore && !loading" class="load-more" @tap="loadMore">
|
||||
<text>加载更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 没有更多数据 -->
|
||||
<view v-if="!hasMore && products.length > 0" class="no-more">
|
||||
<text>没有更多数据了</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useInsuranceStore } from '@/store/insurance'
|
||||
import { INSURANCE_TYPES_TEXT } from '@/utils/constants'
|
||||
|
||||
// 状态管理
|
||||
const insuranceStore = useInsuranceStore()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const selectedCategory = ref('')
|
||||
const searchTimer = ref(null)
|
||||
|
||||
// 产品分类
|
||||
const categories = ref([
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '人寿保险', value: 'life' },
|
||||
{ label: '健康保险', value: 'health' },
|
||||
{ label: '车险', value: 'car' },
|
||||
{ label: '财产保险', value: 'property' },
|
||||
{ label: '旅行保险', value: 'travel' }
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const products = computed(() => insuranceStore.insuranceTypes)
|
||||
const hasMore = computed(() => {
|
||||
const pagination = insuranceStore.pagination.products
|
||||
return pagination.page * pagination.limit < pagination.total
|
||||
})
|
||||
|
||||
// 获取产品特色标签
|
||||
const getProductFeatures = (product) => {
|
||||
const features = []
|
||||
if (product.is_hot) features.push('热门推荐')
|
||||
if (product.min_premium <= 100) features.push('低保费')
|
||||
if (product.max_amount >= 1000000) features.push('高保额')
|
||||
return features.slice(0, 2)
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatAmount = (amount) => {
|
||||
if (amount >= 10000) {
|
||||
return (amount / 10000).toFixed(0) + '万'
|
||||
}
|
||||
return amount.toString()
|
||||
}
|
||||
|
||||
// 加载产品列表(动态调用保险端后端)
|
||||
const loadProducts = async (isRefresh = true) => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
if (isRefresh) {
|
||||
insuranceStore.resetPagination('products')
|
||||
}
|
||||
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
page: isRefresh ? 1 : insuranceStore.pagination.products.page + 1
|
||||
}
|
||||
|
||||
if (searchKeyword.value.trim()) {
|
||||
params.name = searchKeyword.value.trim()
|
||||
}
|
||||
|
||||
if (selectedCategory.value) {
|
||||
params.category = selectedCategory.value
|
||||
}
|
||||
|
||||
// 动态调用保险端 /api/insurance-types 接口
|
||||
await insuranceStore.fetchInsuranceTypes(params)
|
||||
|
||||
console.log('产品列表加载成功')
|
||||
} catch (error) {
|
||||
console.error('加载产品列表失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索输入处理(使用防抖优化)
|
||||
const onSearchInput = (event) => {
|
||||
const value = event.detail.value
|
||||
searchKeyword.value = value
|
||||
|
||||
// 清除之前的定时器
|
||||
if (searchTimer.value) {
|
||||
clearTimeout(searchTimer.value)
|
||||
}
|
||||
|
||||
// 设置新的定时器(300ms 防抖)
|
||||
searchTimer.value = setTimeout(() => {
|
||||
handleSearch()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
const handleSearch = () => {
|
||||
console.log('搜索关键词:', searchKeyword.value)
|
||||
loadProducts(true)
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
if (searchTimer.value) {
|
||||
clearTimeout(searchTimer.value)
|
||||
}
|
||||
loadProducts(true)
|
||||
}
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (category) => {
|
||||
selectedCategory.value = category
|
||||
console.log('选择分类:', category)
|
||||
loadProducts(true)
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (loading.value || !hasMore.value) return
|
||||
loadProducts(false)
|
||||
}
|
||||
|
||||
// 跳转到产品详情
|
||||
const goToProductDetail = (id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product-detail/product-detail?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onPullDownRefresh = async () => {
|
||||
try {
|
||||
await loadProducts(true)
|
||||
} finally {
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 页面触底加载更多
|
||||
const onReachBottom = () => {
|
||||
loadMore()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadProducts(true)
|
||||
})
|
||||
|
||||
// 页面显示时刷新
|
||||
onShow(() => {
|
||||
// 如果列表为空,重新加载
|
||||
if (products.value.length === 0) {
|
||||
loadProducts(true)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
background: #fff;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50rpx;
|
||||
padding: 0 30rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 40rpx;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
padding: 0 10rpx;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
border-bottom: 3rpx solid transparent;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hot-tag {
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.product-features {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.feature-tag {
|
||||
background: #f0f9ff;
|
||||
color: #1890ff;
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.coverage-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.coverage-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coverage-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 40rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 40rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.empty-btn {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 44rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: #1890ff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
</style>
|
||||
66
insurance_mini_program/src/static/README.md
Normal file
66
insurance_mini_program/src/static/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 静态资源说明
|
||||
|
||||
## 图片资源目录结构
|
||||
|
||||
```
|
||||
static/images/
|
||||
├── icons/ # 图标类
|
||||
│ ├── tab-home.png # 首页tab图标
|
||||
│ ├── tab-home-active.png # 首页tab选中图标
|
||||
│ ├── tab-products.png # 产品tab图标
|
||||
│ ├── tab-products-active.png
|
||||
│ ├── tab-my.png # 我的tab图标
|
||||
│ └── tab-my-active.png
|
||||
├── banners/ # 轮播图
|
||||
│ ├── banner-1.jpg
|
||||
│ ├── banner-2.jpg
|
||||
│ └── banner-3.jpg
|
||||
├── products/ # 产品相关
|
||||
│ ├── default-product.png # 默认产品图标
|
||||
│ ├── life-insurance.png # 人寿保险图标
|
||||
│ ├── health-insurance.png # 健康保险图标
|
||||
│ └── car-insurance.png # 车险图标
|
||||
├── avatars/ # 头像类
|
||||
│ └── default-avatar.png # 默认头像
|
||||
├── empty/ # 空状态图
|
||||
│ ├── empty-products.png
|
||||
│ ├── empty-policies.png
|
||||
│ └── empty-claims.png
|
||||
└── common/ # 通用图标
|
||||
├── logo.png # 应用Logo
|
||||
├── search-icon.png # 搜索图标
|
||||
├── wx-icon.png # 微信图标
|
||||
├── checkbox-checked.png # 选中状态
|
||||
└── checkbox-unchecked.png # 未选中状态
|
||||
```
|
||||
|
||||
## 资源使用规范
|
||||
|
||||
1. **图标规格**
|
||||
- Tab图标:60px × 60px
|
||||
- 普通图标:48px × 48px
|
||||
- 产品图标:120px × 120px
|
||||
|
||||
2. **图片格式**
|
||||
- 图标类:PNG格式,支持透明
|
||||
- 照片类:JPG格式
|
||||
- 需要透明:PNG格式
|
||||
|
||||
3. **命名规范**
|
||||
- 使用小写字母和连字符
|
||||
- 语义化命名
|
||||
- 状态类图标加后缀(如:-active、-disabled)
|
||||
|
||||
4. **大小优化**
|
||||
- 图标文件大小控制在20KB以内
|
||||
- 轮播图控制在100KB以内
|
||||
- 使用适当的压缩率
|
||||
|
||||
## 注意事项
|
||||
|
||||
由于这是演示项目,实际开发中需要:
|
||||
|
||||
1. 准备真实的图片资源
|
||||
2. 根据设计稿确定图标样式
|
||||
3. 进行图片压缩优化
|
||||
4. 考虑不同分辨率的适配
|
||||
6
insurance_mini_program/src/store/index.js
Normal file
6
insurance_mini_program/src/store/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// store/index.js - Pinia Store 入口
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
export default store
|
||||
375
insurance_mini_program/src/store/insurance.js
Normal file
375
insurance_mini_program/src/store/insurance.js
Normal file
@@ -0,0 +1,375 @@
|
||||
// store/insurance.js - 保险业务状态管理
|
||||
import { defineStore } from 'pinia'
|
||||
import { insuranceAPI, policyAPI, claimAPI } from '@/utils/api'
|
||||
import { APPLICATION_STATUS, POLICY_STATUS, CLAIM_STATUS } from '@/utils/constants'
|
||||
|
||||
export const useInsuranceStore = defineStore('insurance', {
|
||||
state: () => ({
|
||||
// 保险产品
|
||||
insuranceTypes: [],
|
||||
hotProducts: [],
|
||||
currentProduct: null,
|
||||
productsLoading: false,
|
||||
|
||||
// 保险申请
|
||||
applications: [],
|
||||
currentApplication: null,
|
||||
applicationsLoading: false,
|
||||
applicationStats: {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
approved: 0,
|
||||
rejected: 0
|
||||
},
|
||||
|
||||
// 保单
|
||||
policies: [],
|
||||
currentPolicy: null,
|
||||
policiesLoading: false,
|
||||
|
||||
// 理赔
|
||||
claims: [],
|
||||
currentClaim: null,
|
||||
claimsLoading: false,
|
||||
|
||||
// 分页信息
|
||||
pagination: {
|
||||
products: { page: 1, limit: 10, total: 0 },
|
||||
applications: { page: 1, limit: 10, total: 0 },
|
||||
policies: { page: 1, limit: 10, total: 0 },
|
||||
claims: { page: 1, limit: 10, total: 0 }
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 获取热门产品
|
||||
getHotProducts: (state) => state.hotProducts.slice(0, 6),
|
||||
|
||||
// 根据状态获取申请
|
||||
getApplicationsByStatus: (state) => (status) => {
|
||||
return state.applications.filter(app => app.status === status)
|
||||
},
|
||||
|
||||
// 根据状态获取保单
|
||||
getPoliciesByStatus: (state) => (status) => {
|
||||
return state.policies.filter(policy => policy.status === status)
|
||||
},
|
||||
|
||||
// 根据状态获取理赔
|
||||
getClaimsByStatus: (state) => (status) => {
|
||||
return state.claims.filter(claim => claim.status === status)
|
||||
},
|
||||
|
||||
// 有效保单数量
|
||||
activePoliciesCount: (state) => {
|
||||
return state.policies.filter(policy => policy.status === POLICY_STATUS.ACTIVE).length
|
||||
},
|
||||
|
||||
// 待处理理赔数量
|
||||
pendingClaimsCount: (state) => {
|
||||
return state.claims.filter(claim => claim.status === CLAIM_STATUS.PENDING).length
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 获取保险产品列表(动态调用保险端后端)
|
||||
async fetchInsuranceTypes(params = {}) {
|
||||
try {
|
||||
this.productsLoading = true
|
||||
|
||||
// 动态调用 /api/insurance-types
|
||||
const res = await insuranceAPI.getTypes({
|
||||
page: this.pagination.products.page,
|
||||
limit: this.pagination.products.limit,
|
||||
...params
|
||||
})
|
||||
|
||||
if (params.page === 1) {
|
||||
this.insuranceTypes = res.data
|
||||
} else {
|
||||
this.insuranceTypes.push(...res.data)
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
if (res.pagination) {
|
||||
this.pagination.products = {
|
||||
...this.pagination.products,
|
||||
...res.pagination
|
||||
}
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取保险产品失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
this.productsLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取热门产品(动态调用)
|
||||
async fetchHotProducts() {
|
||||
try {
|
||||
// 动态调用保险端接口获取热门产品
|
||||
const res = await insuranceAPI.getTypes({
|
||||
is_hot: true,
|
||||
status: 'active',
|
||||
limit: 8
|
||||
})
|
||||
|
||||
this.hotProducts = res.data || []
|
||||
return this.hotProducts
|
||||
} catch (error) {
|
||||
console.error('获取热门产品失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取产品详情(动态调用)
|
||||
async fetchProductDetail(id) {
|
||||
try {
|
||||
// 动态调用 /api/insurance-types/:id
|
||||
const res = await insuranceAPI.getTypeDetail(id)
|
||||
this.currentProduct = res.data
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取产品详情失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 提交保险申请(动态调用保险端后端)
|
||||
async submitApplication(applicationData) {
|
||||
try {
|
||||
// 动态调用 /api/insurance/applications
|
||||
const res = await insuranceAPI.submitApplication(applicationData)
|
||||
|
||||
// 添加到本地状态(如果需要)
|
||||
if (res.data) {
|
||||
this.applications.unshift(res.data)
|
||||
this.applicationStats.total += 1
|
||||
this.applicationStats.pending += 1
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('提交保险申请失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取我的申请列表(动态调用)
|
||||
async fetchMyApplications(params = {}) {
|
||||
try {
|
||||
this.applicationsLoading = true
|
||||
|
||||
// 动态调用后端扩展接口
|
||||
const res = await insuranceAPI.getMyApplications({
|
||||
page: this.pagination.applications.page,
|
||||
limit: this.pagination.applications.limit,
|
||||
...params
|
||||
})
|
||||
|
||||
if (params.page === 1) {
|
||||
this.applications = res.data
|
||||
} else {
|
||||
this.applications.push(...res.data)
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
if (res.pagination) {
|
||||
this.pagination.applications = {
|
||||
...this.pagination.applications,
|
||||
...res.pagination
|
||||
}
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取我的申请失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
this.applicationsLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取申请详情(动态调用)
|
||||
async fetchApplicationDetail(id) {
|
||||
try {
|
||||
// 动态调用 /api/insurance/applications/:id
|
||||
const res = await insuranceAPI.getApplicationDetail(id)
|
||||
this.currentApplication = res.data
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取申请详情失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取我的保单列表(动态调用)
|
||||
async fetchMyPolicies(params = {}) {
|
||||
try {
|
||||
this.policiesLoading = true
|
||||
|
||||
// 动态调用后端扩展接口
|
||||
const res = await policyAPI.getMyPolicies({
|
||||
page: this.pagination.policies.page,
|
||||
limit: this.pagination.policies.limit,
|
||||
...params
|
||||
})
|
||||
|
||||
if (params.page === 1) {
|
||||
this.policies = res.data
|
||||
} else {
|
||||
this.policies.push(...res.data)
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
if (res.pagination) {
|
||||
this.pagination.policies = {
|
||||
...this.pagination.policies,
|
||||
...res.pagination
|
||||
}
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取我的保单失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
this.policiesLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取保单详情(动态调用)
|
||||
async fetchPolicyDetail(id) {
|
||||
try {
|
||||
// 动态调用 /api/policies/:id
|
||||
const res = await policyAPI.getPolicyDetail(id)
|
||||
this.currentPolicy = res.data
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取保单详情失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 提交理赔申请(动态调用)
|
||||
async submitClaim(claimData) {
|
||||
try {
|
||||
// 动态调用 /api/claims
|
||||
const res = await claimAPI.submitClaim(claimData)
|
||||
|
||||
// 添加到本地状态
|
||||
if (res.data) {
|
||||
this.claims.unshift(res.data)
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('提交理赔申请失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取我的理赔列表(动态调用)
|
||||
async fetchMyClaims(params = {}) {
|
||||
try {
|
||||
this.claimsLoading = true
|
||||
|
||||
// 动态调用后端扩展接口
|
||||
const res = await claimAPI.getMyClaims({
|
||||
page: this.pagination.claims.page,
|
||||
limit: this.pagination.claims.limit,
|
||||
...params
|
||||
})
|
||||
|
||||
if (params.page === 1) {
|
||||
this.claims = res.data
|
||||
} else {
|
||||
this.claims.push(...res.data)
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
if (res.pagination) {
|
||||
this.pagination.claims = {
|
||||
...this.pagination.claims,
|
||||
...res.pagination
|
||||
}
|
||||
}
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取我的理赔失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
this.claimsLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取理赔详情(动态调用)
|
||||
async fetchClaimDetail(id) {
|
||||
try {
|
||||
// 动态调用 /api/claims/:id
|
||||
const res = await claimAPI.getClaimDetail(id)
|
||||
this.currentClaim = res.data
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取理赔详情失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 上传理赔材料(动态调用)
|
||||
async uploadClaimDocument(filePath, claimId) {
|
||||
try {
|
||||
// 动态调用上传接口
|
||||
const res = await claimAPI.uploadClaimDocument(filePath, claimId)
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('上传理赔材料失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 获取申请统计(动态调用)
|
||||
async fetchApplicationStats() {
|
||||
try {
|
||||
const res = await insuranceAPI.getApplicationStats()
|
||||
this.applicationStats = {
|
||||
...this.applicationStats,
|
||||
...res.data
|
||||
}
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取申请统计失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 重置分页
|
||||
resetPagination(type) {
|
||||
if (this.pagination[type]) {
|
||||
this.pagination[type].page = 1
|
||||
}
|
||||
},
|
||||
|
||||
// 清理数据
|
||||
clearData() {
|
||||
this.insuranceTypes = []
|
||||
this.hotProducts = []
|
||||
this.currentProduct = null
|
||||
this.applications = []
|
||||
this.currentApplication = null
|
||||
this.policies = []
|
||||
this.currentPolicy = null
|
||||
this.claims = []
|
||||
this.currentClaim = null
|
||||
|
||||
// 重置分页
|
||||
Object.keys(this.pagination).forEach(key => {
|
||||
this.pagination[key].page = 1
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
227
insurance_mini_program/src/store/user.js
Normal file
227
insurance_mini_program/src/store/user.js
Normal file
@@ -0,0 +1,227 @@
|
||||
// store/user.js - 基于Pinia的用户状态管理
|
||||
import { defineStore } from 'pinia'
|
||||
import { authAPI } from '@/utils/api'
|
||||
import { STORAGE_KEYS } from '@/utils/constants'
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: '',
|
||||
userInfo: null,
|
||||
isLoggedIn: false,
|
||||
loginTime: null
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 是否已认证
|
||||
isAuthenticated: (state) => !!state.token && !!state.userInfo,
|
||||
|
||||
// 用户昵称
|
||||
nickname: (state) => state.userInfo?.nickname || '未登录',
|
||||
|
||||
// 用户头像
|
||||
avatar: (state) => state.userInfo?.avatar || '/static/images/default-avatar.png',
|
||||
|
||||
// 用户角色
|
||||
roleId: (state) => state.userInfo?.role_id || null,
|
||||
|
||||
// 是否为管理员
|
||||
isAdmin: (state) => {
|
||||
const roleId = state.userInfo?.role_id
|
||||
return roleId === 1 || roleId === 2
|
||||
},
|
||||
|
||||
// 是否为普通用户
|
||||
isUser: (state) => state.userInfo?.role_id === 3
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 微信登录(动态调用保险端后端认证接口)
|
||||
async wxLogin() {
|
||||
try {
|
||||
console.log('开始微信登录...')
|
||||
|
||||
// 1. 获取微信登录码
|
||||
const loginRes = await uni.login({
|
||||
provider: 'weixin'
|
||||
})
|
||||
|
||||
if (!loginRes[1]?.code) {
|
||||
throw new Error('获取登录码失败')
|
||||
}
|
||||
|
||||
console.log('获取到微信登录码:', loginRes[1].code)
|
||||
|
||||
// 2. 获取用户信息(微信小程序需要用户授权)
|
||||
const userInfoRes = await uni.getUserInfo({
|
||||
provider: 'weixin'
|
||||
})
|
||||
|
||||
console.log('获取到用户信息:', userInfoRes[1])
|
||||
|
||||
// 3. 发送到保险端后端验证(动态调用)
|
||||
const authRes = await authAPI.wxLogin({
|
||||
code: loginRes[1].code,
|
||||
userInfo: userInfoRes[1].userInfo,
|
||||
signature: userInfoRes[1].signature,
|
||||
rawData: userInfoRes[1].rawData
|
||||
})
|
||||
|
||||
console.log('后端认证成功:', authRes)
|
||||
|
||||
// 4. 保存 token 和用户信息
|
||||
this.token = authRes.data.token
|
||||
this.userInfo = authRes.data.userInfo
|
||||
this.isLoggedIn = true
|
||||
this.loginTime = new Date().toISOString()
|
||||
|
||||
// 持久化存储
|
||||
uni.setStorageSync(STORAGE_KEYS.TOKEN, this.token)
|
||||
uni.setStorageSync(STORAGE_KEYS.USER_INFO, this.userInfo)
|
||||
uni.setStorageSync(STORAGE_KEYS.LAST_LOGIN_TIME, this.loginTime)
|
||||
|
||||
console.log('登录状态保存成功')
|
||||
|
||||
return authRes.data
|
||||
} catch (error) {
|
||||
console.error('微信登录失败:', error)
|
||||
|
||||
// 登录失败时清理状态
|
||||
this.clearLoginState()
|
||||
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 普通账号密码登录(复用现有接口)
|
||||
async login(credentials) {
|
||||
try {
|
||||
console.log('开始账号密码登录...')
|
||||
|
||||
// 动态调用保险端登录接口
|
||||
const authRes = await authAPI.login(credentials)
|
||||
|
||||
// 保存登录状态
|
||||
this.token = authRes.data.token
|
||||
this.userInfo = authRes.data.userInfo
|
||||
this.isLoggedIn = true
|
||||
this.loginTime = new Date().toISOString()
|
||||
|
||||
// 持久化存储
|
||||
uni.setStorageSync(STORAGE_KEYS.TOKEN, this.token)
|
||||
uni.setStorageSync(STORAGE_KEYS.USER_INFO, this.userInfo)
|
||||
uni.setStorageSync(STORAGE_KEYS.LAST_LOGIN_TIME, this.loginTime)
|
||||
|
||||
return authRes.data
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
this.clearLoginState()
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkAuth() {
|
||||
try {
|
||||
const token = uni.getStorageSync(STORAGE_KEYS.TOKEN)
|
||||
const userInfo = uni.getStorageSync(STORAGE_KEYS.USER_INFO)
|
||||
const loginTime = uni.getStorageSync(STORAGE_KEYS.LAST_LOGIN_TIME)
|
||||
|
||||
if (token && userInfo) {
|
||||
this.token = token
|
||||
this.userInfo = userInfo
|
||||
this.isLoggedIn = true
|
||||
this.loginTime = loginTime
|
||||
|
||||
console.log('恢复登录状态成功:', userInfo)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('检查登录状态失败:', error)
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户信息(动态调用保险端接口)
|
||||
async getUserProfile() {
|
||||
try {
|
||||
const res = await authAPI.getProfile()
|
||||
this.userInfo = res.data
|
||||
|
||||
// 更新本地存储
|
||||
uni.setStorageSync(STORAGE_KEYS.USER_INFO, this.userInfo)
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 更新用户信息(动态调用保险端接口)
|
||||
async updateProfile(data) {
|
||||
try {
|
||||
const res = await authAPI.updateProfile(data)
|
||||
this.userInfo = { ...this.userInfo, ...res.data }
|
||||
|
||||
// 更新本地存储
|
||||
uni.setStorageSync(STORAGE_KEYS.USER_INFO, this.userInfo)
|
||||
|
||||
return res.data
|
||||
} catch (error) {
|
||||
console.error('更新用户信息失败:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
async logout() {
|
||||
try {
|
||||
// 调用后端退出接口
|
||||
await authAPI.logout()
|
||||
} catch (error) {
|
||||
console.warn('后端退出接口调用失败:', error)
|
||||
} finally {
|
||||
// 无论后端接口是否成功,都清理本地状态
|
||||
this.clearLoginState()
|
||||
|
||||
// 跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 清理登录状态
|
||||
clearLoginState() {
|
||||
this.token = ''
|
||||
this.userInfo = null
|
||||
this.isLoggedIn = false
|
||||
this.loginTime = null
|
||||
|
||||
// 清理本地存储
|
||||
uni.removeStorageSync(STORAGE_KEYS.TOKEN)
|
||||
uni.removeStorageSync(STORAGE_KEYS.USER_INFO)
|
||||
uni.removeStorageSync(STORAGE_KEYS.LAST_LOGIN_TIME)
|
||||
|
||||
console.log('登录状态已清理')
|
||||
},
|
||||
|
||||
// 刷新token
|
||||
async refreshToken() {
|
||||
try {
|
||||
// 如果后端支持token刷新
|
||||
const res = await authAPI.refreshToken()
|
||||
this.token = res.data.token
|
||||
uni.setStorageSync(STORAGE_KEYS.TOKEN, this.token)
|
||||
return res.data.token
|
||||
} catch (error) {
|
||||
console.error('刷新token失败:', error)
|
||||
// token刷新失败,需要重新登录
|
||||
this.logout()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
271
insurance_mini_program/src/stores/app.js
Normal file
271
insurance_mini_program/src/stores/app.js
Normal file
@@ -0,0 +1,271 @@
|
||||
// stores/app.js - 应用全局状态管理
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
// 系统信息
|
||||
systemInfo: {},
|
||||
|
||||
// 网络状态
|
||||
networkType: 'unknown',
|
||||
isOnline: true,
|
||||
|
||||
// 应用配置
|
||||
config: {
|
||||
theme: 'light',
|
||||
language: 'zh-CN'
|
||||
},
|
||||
|
||||
// 加载状态
|
||||
globalLoading: false,
|
||||
|
||||
// 错误信息
|
||||
errors: [],
|
||||
|
||||
// 通知消息
|
||||
notifications: [],
|
||||
|
||||
// 搜索历史
|
||||
searchHistory: []
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 是否为暗黑主题
|
||||
isDarkTheme: (state) => state.config.theme === 'dark',
|
||||
|
||||
// 未读通知数量
|
||||
unreadNotificationCount: (state) => state.notifications.filter(n => !n.read).length,
|
||||
|
||||
// 最近搜索记录(前10条)
|
||||
recentSearches: (state) => state.searchHistory.slice(0, 10)
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
async initApp() {
|
||||
try {
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.systemInfo = systemInfo
|
||||
|
||||
// 获取网络类型
|
||||
const networkInfo = await uni.getNetworkType()
|
||||
this.networkType = networkInfo.networkType
|
||||
this.isOnline = networkInfo.networkType !== 'none'
|
||||
|
||||
// 加载本地配置
|
||||
this.loadConfig()
|
||||
|
||||
// 加载搜索历史
|
||||
this.loadSearchHistory()
|
||||
|
||||
console.log('应用初始化完成:', {
|
||||
systemInfo: this.systemInfo,
|
||||
networkType: this.networkType,
|
||||
config: this.config
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('应用初始化失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
loadConfig() {
|
||||
try {
|
||||
const config = uni.getStorageSync('app_config')
|
||||
if (config) {
|
||||
this.config = { ...this.config, ...config }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
saveConfig() {
|
||||
try {
|
||||
uni.setStorageSync('app_config', this.config)
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
*/
|
||||
setTheme(theme) {
|
||||
this.config.theme = theme
|
||||
this.saveConfig()
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
*/
|
||||
setLanguage(language) {
|
||||
this.config.language = language
|
||||
this.saveConfig()
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置全局加载状态
|
||||
*/
|
||||
setGlobalLoading(loading) {
|
||||
this.globalLoading = loading
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加错误信息
|
||||
*/
|
||||
addError(error) {
|
||||
const errorItem = {
|
||||
id: Date.now(),
|
||||
message: error.message || error,
|
||||
timestamp: new Date(),
|
||||
type: error.type || 'error'
|
||||
}
|
||||
this.errors.unshift(errorItem)
|
||||
|
||||
// 只保留最近50条错误
|
||||
if (this.errors.length > 50) {
|
||||
this.errors = this.errors.slice(0, 50)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除错误信息
|
||||
*/
|
||||
clearErrors() {
|
||||
this.errors = []
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加通知
|
||||
*/
|
||||
addNotification(notification) {
|
||||
const notificationItem = {
|
||||
id: Date.now(),
|
||||
title: notification.title,
|
||||
message: notification.message,
|
||||
type: notification.type || 'info',
|
||||
read: false,
|
||||
timestamp: new Date()
|
||||
}
|
||||
this.notifications.unshift(notificationItem)
|
||||
|
||||
// 只保留最近100条通知
|
||||
if (this.notifications.length > 100) {
|
||||
this.notifications = this.notifications.slice(0, 100)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 标记通知为已读
|
||||
*/
|
||||
markNotificationAsRead(id) {
|
||||
const notification = this.notifications.find(n => n.id === id)
|
||||
if (notification) {
|
||||
notification.read = true
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 标记所有通知为已读
|
||||
*/
|
||||
markAllNotificationsAsRead() {
|
||||
this.notifications.forEach(n => n.read = true)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除通知
|
||||
*/
|
||||
clearNotifications() {
|
||||
this.notifications = []
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载搜索历史
|
||||
*/
|
||||
loadSearchHistory() {
|
||||
try {
|
||||
const history = uni.getStorageSync('search_history')
|
||||
if (history) {
|
||||
this.searchHistory = history
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索历史失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存搜索历史
|
||||
*/
|
||||
saveSearchHistory() {
|
||||
try {
|
||||
uni.setStorageSync('search_history', this.searchHistory)
|
||||
} catch (error) {
|
||||
console.error('保存搜索历史失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加搜索记录
|
||||
*/
|
||||
addSearchHistory(keyword) {
|
||||
if (!keyword || !keyword.trim()) return
|
||||
|
||||
keyword = keyword.trim()
|
||||
|
||||
// 移除已存在的相同关键词
|
||||
this.searchHistory = this.searchHistory.filter(item => item !== keyword)
|
||||
|
||||
// 添加到开头
|
||||
this.searchHistory.unshift(keyword)
|
||||
|
||||
// 只保留最近20条
|
||||
if (this.searchHistory.length > 20) {
|
||||
this.searchHistory = this.searchHistory.slice(0, 20)
|
||||
}
|
||||
|
||||
this.saveSearchHistory()
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除搜索历史
|
||||
*/
|
||||
clearSearchHistory() {
|
||||
this.searchHistory = []
|
||||
this.saveSearchHistory()
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新网络状态
|
||||
*/
|
||||
updateNetworkStatus(networkType) {
|
||||
this.networkType = networkType
|
||||
this.isOnline = networkType !== 'none'
|
||||
|
||||
if (!this.isOnline) {
|
||||
this.addNotification({
|
||||
title: '网络连接',
|
||||
message: '网络连接已断开,请检查网络设置',
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 监听网络状态变化
|
||||
*/
|
||||
watchNetworkStatus() {
|
||||
uni.onNetworkStatusChange((res) => {
|
||||
this.updateNetworkStatus(res.networkType)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
12
insurance_mini_program/src/stores/index.js
Normal file
12
insurance_mini_program/src/stores/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// stores/index.js - Pinia Store入口文件
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
// 创建Pinia实例
|
||||
const pinia = createPinia()
|
||||
|
||||
export default pinia
|
||||
|
||||
// 导出所有store
|
||||
export { useUserStore } from './user'
|
||||
export { useInsuranceStore } from './insurance'
|
||||
export { useAppStore } from './app'
|
||||
346
insurance_mini_program/src/styles/base.scss
Normal file
346
insurance_mini_program/src/styles/base.scss
Normal file
@@ -0,0 +1,346 @@
|
||||
/* styles/base.scss - 基础样式 */
|
||||
|
||||
/* 重置样式 */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
page {
|
||||
background-color: $background-color;
|
||||
font-size: $font-size-md;
|
||||
line-height: $line-height-normal;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
/* 通用布局 */
|
||||
.container {
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background-color: $background-color;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: $background-white;
|
||||
border-radius: $border-radius-large;
|
||||
padding: $spacing-md;
|
||||
margin-bottom: $spacing-md;
|
||||
box-shadow: $shadow-light;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: $spacing-sm;
|
||||
|
||||
.card-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: $text-secondary;
|
||||
line-height: $line-height-large;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-radius: $border-radius-normal;
|
||||
font-size: $font-size-md;
|
||||
text-align: center;
|
||||
transition: all $animation-duration-normal;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.btn-block {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.btn-large {
|
||||
padding: 32rpx 48rpx;
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
|
||||
&.btn-small {
|
||||
padding: 16rpx 24rpx;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background-color: darken($primary-color, 10%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: $border-color;
|
||||
color: $text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-secondary {
|
||||
background-color: $background-light;
|
||||
color: $text-primary;
|
||||
border: 1rpx solid $border-color;
|
||||
|
||||
&:active {
|
||||
background-color: $border-light;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-success {
|
||||
background-color: $success-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background-color: darken($success-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-warning {
|
||||
background-color: $warning-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background-color: darken($warning-color, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-error {
|
||||
background-color: $error-color;
|
||||
color: white;
|
||||
|
||||
&:active {
|
||||
background-color: darken($error-color, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-group {
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: $spacing-xs;
|
||||
font-size: $font-size-md;
|
||||
color: $text-primary;
|
||||
|
||||
&.required::after {
|
||||
content: ' *';
|
||||
color: $error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
border: 1rpx solid $border-color;
|
||||
border-radius: $border-radius-normal;
|
||||
font-size: $font-size-md;
|
||||
background-color: $background-white;
|
||||
|
||||
&:focus {
|
||||
border-color: $primary-color;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: $background-light;
|
||||
color: $text-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
@extend .form-input;
|
||||
height: 120rpx;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@extend .form-input;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10rpx solid transparent;
|
||||
border-right: 10rpx solid transparent;
|
||||
border-top: 10rpx solid $text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.list {
|
||||
background: $background-white;
|
||||
border-radius: $border-radius-large;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: $spacing-md;
|
||||
border-bottom: 1rpx solid $border-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $background-light;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-item-title {
|
||||
font-size: $font-size-md;
|
||||
color: $text-primary;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.list-item-subtitle {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.list-item-extra {
|
||||
margin-left: $spacing-sm;
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: $font-size-xs;
|
||||
font-weight: bold;
|
||||
|
||||
&.status-pending {
|
||||
background-color: lighten($warning-color, 35%);
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
&.status-approved {
|
||||
background-color: lighten($success-color, 35%);
|
||||
color: $success-color;
|
||||
}
|
||||
|
||||
&.status-rejected {
|
||||
background-color: lighten($error-color, 35%);
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
&.status-reviewing {
|
||||
background-color: lighten($primary-color, 35%);
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* 工具类 */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: $success-color;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 间距工具类 */
|
||||
.mt-10 { margin-top: 10rpx; }
|
||||
.mt-20 { margin-top: 20rpx; }
|
||||
.mt-30 { margin-top: 30rpx; }
|
||||
.mb-10 { margin-bottom: 10rpx; }
|
||||
.mb-20 { margin-bottom: 20rpx; }
|
||||
.mb-30 { margin-bottom: 30rpx; }
|
||||
.ml-10 { margin-left: 10rpx; }
|
||||
.ml-20 { margin-left: 20rpx; }
|
||||
.mr-10 { margin-right: 10rpx; }
|
||||
.mr-20 { margin-right: 20rpx; }
|
||||
|
||||
.p-10 { padding: 10rpx; }
|
||||
.p-20 { padding: 20rpx; }
|
||||
.p-30 { padding: 30rpx; }
|
||||
.pt-10 { padding-top: 10rpx; }
|
||||
.pt-20 { padding-top: 20rpx; }
|
||||
.pb-10 { padding-bottom: 10rpx; }
|
||||
.pb-20 { padding-bottom: 20rpx; }
|
||||
.pl-10 { padding-left: 10rpx; }
|
||||
.pl-20 { padding-left: 20rpx; }
|
||||
.pr-10 { padding-right: 10rpx; }
|
||||
.pr-20 { padding-right: 20rpx; }
|
||||
56
insurance_mini_program/src/styles/variables.scss
Normal file
56
insurance_mini_program/src/styles/variables.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
/* styles/variables.scss - SCSS变量定义 */
|
||||
|
||||
/* 主题颜色 */
|
||||
$primary-color: #1890ff;
|
||||
$success-color: #52c41a;
|
||||
$warning-color: #faad14;
|
||||
$error-color: #f5222d;
|
||||
$info-color: #722ed1;
|
||||
|
||||
/* 文本颜色 */
|
||||
$text-primary: #262626;
|
||||
$text-secondary: #8c8c8c;
|
||||
$text-disabled: #bfbfbf;
|
||||
|
||||
/* 背景颜色 */
|
||||
$background-color: #f5f5f5;
|
||||
$background-light: #fafafa;
|
||||
$background-white: #ffffff;
|
||||
|
||||
/* 边框颜色 */
|
||||
$border-color: #e8e8e8;
|
||||
$border-light: #f0f0f0;
|
||||
|
||||
/* 阴影 */
|
||||
$shadow-light: 0 2rpx 8rpx 0 rgba(0, 0, 0, 0.06);
|
||||
$shadow-normal: 0 2rpx 12rpx 0 rgba(0, 0, 0, 0.1);
|
||||
$shadow-deep: 0 4rpx 16rpx 0 rgba(0, 0, 0, 0.12);
|
||||
|
||||
/* 圆角 */
|
||||
$border-radius-small: 4rpx;
|
||||
$border-radius-normal: 8rpx;
|
||||
$border-radius-large: 12rpx;
|
||||
|
||||
/* 间距 */
|
||||
$spacing-xs: 10rpx;
|
||||
$spacing-sm: 20rpx;
|
||||
$spacing-md: 30rpx;
|
||||
$spacing-lg: 40rpx;
|
||||
$spacing-xl: 60rpx;
|
||||
|
||||
/* 字体大小 */
|
||||
$font-size-xs: 20rpx;
|
||||
$font-size-sm: 24rpx;
|
||||
$font-size-md: 28rpx;
|
||||
$font-size-lg: 32rpx;
|
||||
$font-size-xl: 36rpx;
|
||||
$font-size-xxl: 40rpx;
|
||||
|
||||
/* 行高 */
|
||||
$line-height-normal: 1.5;
|
||||
$line-height-large: 1.8;
|
||||
|
||||
/* 动画时间 */
|
||||
$animation-duration-fast: 0.2s;
|
||||
$animation-duration-normal: 0.3s;
|
||||
$animation-duration-slow: 0.5s;
|
||||
216
insurance_mini_program/src/utils/api.js
Normal file
216
insurance_mini_program/src/utils/api.js
Normal file
@@ -0,0 +1,216 @@
|
||||
// utils/api.js - 全部动态调用现有保险端insurance_backend的API
|
||||
import request from './request'
|
||||
|
||||
// 认证相关 API(扩展支持微信登录)
|
||||
export const authAPI = {
|
||||
// 微信登录(新增)
|
||||
wxLogin: (data) => request({
|
||||
url: '/auth/wx-login',
|
||||
method: 'POST',
|
||||
data
|
||||
}),
|
||||
|
||||
// 复用现有登录接口
|
||||
login: (data) => request({
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data
|
||||
}),
|
||||
|
||||
// 获取用户信息(动态调用)
|
||||
getProfile: () => request({
|
||||
url: '/auth/profile'
|
||||
}),
|
||||
|
||||
// 更新用户信息(动态调用)
|
||||
updateProfile: (data) => request({
|
||||
url: '/auth/profile',
|
||||
method: 'PUT',
|
||||
data
|
||||
}),
|
||||
|
||||
// 退出登录
|
||||
logout: () => request({
|
||||
url: '/auth/logout',
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
// 保险产品相关 API(全部动态调用现有接口)
|
||||
export const insuranceAPI = {
|
||||
// 获取保险产品列表(动态调用 /api/insurance-types)
|
||||
getTypes: (params) => request({
|
||||
url: '/insurance-types',
|
||||
data: params
|
||||
}),
|
||||
|
||||
// 获取产品详情(动态调用)
|
||||
getTypeDetail: (id) => request({
|
||||
url: `/insurance-types/${id}`
|
||||
}),
|
||||
|
||||
// 提交保险申请(动态调用 /api/insurance/applications)
|
||||
submitApplication: (data) => request({
|
||||
url: '/insurance/applications',
|
||||
method: 'POST',
|
||||
data
|
||||
}),
|
||||
|
||||
// 获取我的申请列表(动态调用,需后端扩展)
|
||||
getMyApplications: (params) => request({
|
||||
url: '/miniprogram/my-applications',
|
||||
data: params
|
||||
}),
|
||||
|
||||
// 获取申请详情(动态调用)
|
||||
getApplicationDetail: (id) => request({
|
||||
url: `/insurance/applications/${id}`
|
||||
}),
|
||||
|
||||
// 获取申请统计(动态调用)
|
||||
getApplicationStats: () => request({
|
||||
url: '/insurance/applications/stats'
|
||||
})
|
||||
}
|
||||
|
||||
// 保单相关 API(全部动态调用现有接口)
|
||||
export const policyAPI = {
|
||||
// 获取我的保单列表(动态调用,需后端扩展)
|
||||
getMyPolicies: (params) => request({
|
||||
url: '/miniprogram/my-policies',
|
||||
data: params
|
||||
}),
|
||||
|
||||
// 获取保单详情(动态调用 /api/policies/:id)
|
||||
getPolicyDetail: (id) => request({
|
||||
url: `/policies/${id}`
|
||||
}),
|
||||
|
||||
// 获取保单列表(管理端接口)
|
||||
getList: (params) => request({
|
||||
url: '/policies',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 理赔相关 API(全部动态调用现有接口)
|
||||
export const claimAPI = {
|
||||
// 提交理赔申请(动态调用 /api/claims)
|
||||
submitClaim: (data) => request({
|
||||
url: '/claims',
|
||||
method: 'POST',
|
||||
data
|
||||
}),
|
||||
|
||||
// 获取我的理赔列表(动态调用,需后端扩展)
|
||||
getMyClaims: (params) => request({
|
||||
url: '/miniprogram/my-claims',
|
||||
data: params
|
||||
}),
|
||||
|
||||
// 获取理赔详情(动态调用 /api/claims/:id)
|
||||
getClaimDetail: (id) => request({
|
||||
url: `/claims/${id}`
|
||||
}),
|
||||
|
||||
// 更新理赔状态(动态调用)
|
||||
updateClaimStatus: (id, data) => request({
|
||||
url: `/claims/${id}/status`,
|
||||
method: 'PUT',
|
||||
data
|
||||
}),
|
||||
|
||||
// 上传理赔材料(动态调用)
|
||||
uploadClaimDocument: (filePath, claimId) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = uni.getStorageSync('token')
|
||||
|
||||
uni.uploadFile({
|
||||
url: `${BASE_URL}/claims/upload-document`,
|
||||
filePath,
|
||||
name: 'file',
|
||||
formData: {
|
||||
claim_id: claimId
|
||||
},
|
||||
header: {
|
||||
'Authorization': token ? `Bearer ${token}` : ''
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
if (data.status === 'success') {
|
||||
resolve(data)
|
||||
} else {
|
||||
reject(data)
|
||||
}
|
||||
} catch (error) {
|
||||
reject({ message: '响应解析失败' })
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 小程序专用API(动态调用后端扩展接口)
|
||||
export const miniprogramAPI = {
|
||||
// 获取小程序首页数据
|
||||
getHomeData: () => request({
|
||||
url: '/miniprogram/home'
|
||||
}),
|
||||
|
||||
// 获取产品列表(小程序版本)
|
||||
getProducts: (params) => request({
|
||||
url: '/miniprogram/products',
|
||||
data: params
|
||||
}),
|
||||
|
||||
// 获取用户统计信息
|
||||
getUserStats: () => request({
|
||||
url: '/miniprogram/user-stats'
|
||||
})
|
||||
}
|
||||
|
||||
// 统计数据 API(动态调用 /api/system)
|
||||
export const dashboardAPI = {
|
||||
getStats: () => request({
|
||||
url: '/system/stats'
|
||||
}),
|
||||
getRecentActivities: () => request({
|
||||
url: '/system/logs',
|
||||
data: { limit: 10 }
|
||||
})
|
||||
}
|
||||
|
||||
// 用户管理 API(动态调用现有接口)
|
||||
export const userAPI = {
|
||||
getList: (params) => request({
|
||||
url: '/users',
|
||||
data: params
|
||||
}),
|
||||
create: (data) => request({
|
||||
url: '/users',
|
||||
method: 'POST',
|
||||
data
|
||||
}),
|
||||
update: (id, data) => request({
|
||||
url: `/users/${id}`,
|
||||
method: 'PUT',
|
||||
data
|
||||
}),
|
||||
delete: (id) => request({
|
||||
url: `/users/${id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
authAPI,
|
||||
insuranceAPI,
|
||||
policyAPI,
|
||||
claimAPI,
|
||||
miniprogramAPI,
|
||||
dashboardAPI,
|
||||
userAPI
|
||||
}
|
||||
109
insurance_mini_program/src/utils/auth.js
Normal file
109
insurance_mini_program/src/utils/auth.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// utils/auth.js - Vue.js 认证工具
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
export const auth = {
|
||||
// 快速检查登录状态
|
||||
checkAuth() {
|
||||
const userStore = useUserStore()
|
||||
return userStore.checkAuth()
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
const userStore = useUserStore()
|
||||
return userStore.userInfo
|
||||
},
|
||||
|
||||
// 获取Token
|
||||
getToken() {
|
||||
const userStore = useUserStore()
|
||||
return userStore.token
|
||||
},
|
||||
|
||||
// 登录拦截器(页面级别使用)
|
||||
requireAuth() {
|
||||
if (!this.checkAuth()) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
logout() {
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
},
|
||||
|
||||
// 角色权限检查
|
||||
hasRole(roleId) {
|
||||
const userInfo = this.getUserInfo()
|
||||
return userInfo && userInfo.role_id === roleId
|
||||
},
|
||||
|
||||
// 是否为管理员
|
||||
isAdmin() {
|
||||
return this.hasRole(1) || this.hasRole(2)
|
||||
},
|
||||
|
||||
// 是否为普通用户
|
||||
isUser() {
|
||||
return this.hasRole(3)
|
||||
}
|
||||
}
|
||||
|
||||
// 路由拦截器工具
|
||||
export const routeGuard = {
|
||||
// 检查页面访问权限
|
||||
checkPageAuth(pagePath) {
|
||||
// 定义需要登录的页面
|
||||
const authRequiredPages = [
|
||||
'/pages/application/application',
|
||||
'/pages/policies/policies',
|
||||
'/pages/claims/claims',
|
||||
'/pages/my/my'
|
||||
]
|
||||
|
||||
if (authRequiredPages.includes(pagePath)) {
|
||||
return auth.requireAuth()
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
// 管理员页面权限检查
|
||||
checkAdminAuth() {
|
||||
if (!auth.checkAuth()) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (!auth.isAdmin()) {
|
||||
uni.showToast({
|
||||
title: '权限不足',
|
||||
icon: 'none'
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export default auth
|
||||
288
insurance_mini_program/src/utils/constants.js
Normal file
288
insurance_mini_program/src/utils/constants.js
Normal file
@@ -0,0 +1,288 @@
|
||||
// utils/constants.js - 常量定义
|
||||
/**
|
||||
* API 基础配置
|
||||
*/
|
||||
export const API_CONFIG = {
|
||||
// 动态获取保险端后端地址
|
||||
BASE_URL: process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:3000/api' // 本地开发环境
|
||||
: 'https://your-insurance-backend.com/api', // 生产环境
|
||||
|
||||
TIMEOUT: 10000, // 请求超时时间
|
||||
|
||||
// 缓存控制(避免304状态码)
|
||||
CACHE_HEADERS: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储键名
|
||||
*/
|
||||
export const STORAGE_KEYS = {
|
||||
TOKEN: 'token',
|
||||
USER_INFO: 'userInfo',
|
||||
SEARCH_HISTORY: 'searchHistory',
|
||||
CART: 'cart',
|
||||
SETTINGS: 'settings'
|
||||
}
|
||||
|
||||
/**
|
||||
* 保险产品类型
|
||||
*/
|
||||
export const INSURANCE_TYPES = {
|
||||
LIFE: 'life', // 寿险
|
||||
HEALTH: 'health', // 健康险
|
||||
ACCIDENT: 'accident', // 意外险
|
||||
VEHICLE: 'vehicle', // 车险
|
||||
PROPERTY: 'property', // 财产险
|
||||
TRAVEL: 'travel', // 旅行险
|
||||
PET: 'pet' // 宠物险
|
||||
}
|
||||
|
||||
/**
|
||||
* 保险产品类型中文映射
|
||||
*/
|
||||
export const INSURANCE_TYPE_LABELS = {
|
||||
[INSURANCE_TYPES.LIFE]: '寿险',
|
||||
[INSURANCE_TYPES.HEALTH]: '健康险',
|
||||
[INSURANCE_TYPES.ACCIDENT]: '意外险',
|
||||
[INSURANCE_TYPES.VEHICLE]: '车险',
|
||||
[INSURANCE_TYPES.PROPERTY]: '财产险',
|
||||
[INSURANCE_TYPES.TRAVEL]: '旅行险',
|
||||
[INSURANCE_TYPES.PET]: '宠物险'
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请状态
|
||||
*/
|
||||
export const APPLICATION_STATUS = {
|
||||
PENDING: 'pending', // 待审核
|
||||
UNDER_REVIEW: 'under_review', // 审核中
|
||||
APPROVED: 'approved', // 已批准
|
||||
REJECTED: 'rejected' // 已拒绝
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请状态中文映射
|
||||
*/
|
||||
export const APPLICATION_STATUS_LABELS = {
|
||||
[APPLICATION_STATUS.PENDING]: '待审核',
|
||||
[APPLICATION_STATUS.UNDER_REVIEW]: '审核中',
|
||||
[APPLICATION_STATUS.APPROVED]: '已批准',
|
||||
[APPLICATION_STATUS.REJECTED]: '已拒绝'
|
||||
}
|
||||
|
||||
/**
|
||||
* 保单状态
|
||||
*/
|
||||
export const POLICY_STATUS = {
|
||||
ACTIVE: 'active', // 有效
|
||||
EXPIRED: 'expired', // 已过期
|
||||
CANCELLED: 'cancelled', // 已取消
|
||||
SUSPENDED: 'suspended' // 暂停
|
||||
}
|
||||
|
||||
/**
|
||||
* 保单状态中文映射
|
||||
*/
|
||||
export const POLICY_STATUS_LABELS = {
|
||||
[POLICY_STATUS.ACTIVE]: '有效',
|
||||
[POLICY_STATUS.EXPIRED]: '已过期',
|
||||
[POLICY_STATUS.CANCELLED]: '已取消',
|
||||
[POLICY_STATUS.SUSPENDED]: '暂停'
|
||||
}
|
||||
|
||||
/**
|
||||
* 理赔状态
|
||||
*/
|
||||
export const CLAIM_STATUS = {
|
||||
PENDING: 'pending', // 待处理
|
||||
REVIEWING: 'reviewing', // 审核中
|
||||
APPROVED: 'approved', // 已批准
|
||||
REJECTED: 'rejected', // 已拒绝
|
||||
PAID: 'paid' // 已赔付
|
||||
}
|
||||
|
||||
/**
|
||||
* 理赔状态中文映射
|
||||
*/
|
||||
export const CLAIM_STATUS_LABELS = {
|
||||
[CLAIM_STATUS.PENDING]: '待处理',
|
||||
[CLAIM_STATUS.REVIEWING]: '审核中',
|
||||
[CLAIM_STATUS.APPROVED]: '已批准',
|
||||
[CLAIM_STATUS.REJECTED]: '已拒绝',
|
||||
[CLAIM_STATUS.PAID]: '已赔付'
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户角色
|
||||
*/
|
||||
export const USER_ROLES = {
|
||||
ADMIN: 1, // 管理员
|
||||
REVIEWER: 2, // 审核员
|
||||
CUSTOMER: 3 // 客户
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户角色中文映射
|
||||
*/
|
||||
export const USER_ROLE_LABELS = {
|
||||
[USER_ROLES.ADMIN]: '管理员',
|
||||
[USER_ROLES.REVIEWER]: '审核员',
|
||||
[USER_ROLES.CUSTOMER]: '客户'
|
||||
}
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
export const GENDER = {
|
||||
UNKNOWN: 0, // 未知
|
||||
MALE: 1, // 男
|
||||
FEMALE: 2 // 女
|
||||
}
|
||||
|
||||
/**
|
||||
* 性别中文映射
|
||||
*/
|
||||
export const GENDER_LABELS = {
|
||||
[GENDER.UNKNOWN]: '未知',
|
||||
[GENDER.MALE]: '男',
|
||||
[GENDER.FEMALE]: '女'
|
||||
}
|
||||
|
||||
/**
|
||||
* 与受益人关系
|
||||
*/
|
||||
export const BENEFICIARY_RELATIONS = [
|
||||
{ value: 'spouse', label: '配偶' },
|
||||
{ value: 'parent', label: '父母' },
|
||||
{ value: 'child', label: '子女' },
|
||||
{ value: 'sibling', label: '兄弟姐妹' },
|
||||
{ value: 'other', label: '其他' }
|
||||
]
|
||||
|
||||
/**
|
||||
* 文件类型限制
|
||||
*/
|
||||
export const FILE_TYPES = {
|
||||
IMAGE: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
|
||||
DOCUMENT: ['pdf', 'doc', 'docx', 'txt'],
|
||||
ALL: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'txt']
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件大小限制(字节)
|
||||
*/
|
||||
export const FILE_SIZE_LIMITS = {
|
||||
IMAGE: 5 * 1024 * 1024, // 5MB
|
||||
DOCUMENT: 10 * 1024 * 1024, // 10MB
|
||||
DEFAULT: 5 * 1024 * 1024 // 5MB
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页配置
|
||||
*/
|
||||
export const PAGINATION = {
|
||||
DEFAULT_PAGE_SIZE: 10,
|
||||
DEFAULT_PAGE: 1,
|
||||
MAX_PAGE_SIZE: 100
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单验证规则
|
||||
*/
|
||||
export const VALIDATION_RULES = {
|
||||
PHONE: /^1[3-9]\d{9}$/,
|
||||
ID_CARD: /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/,
|
||||
EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||
PASSWORD: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误码映射
|
||||
*/
|
||||
export const ERROR_CODES = {
|
||||
NETWORK_ERROR: 'NETWORK_ERROR',
|
||||
TIMEOUT: 'TIMEOUT',
|
||||
UNAUTHORIZED: 'UNAUTHORIZED',
|
||||
FORBIDDEN: 'FORBIDDEN',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
||||
SERVER_ERROR: 'SERVER_ERROR'
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息映射
|
||||
*/
|
||||
export const ERROR_MESSAGES = {
|
||||
[ERROR_CODES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
|
||||
[ERROR_CODES.TIMEOUT]: '请求超时,请稍后重试',
|
||||
[ERROR_CODES.UNAUTHORIZED]: '登录已过期,请重新登录',
|
||||
[ERROR_CODES.FORBIDDEN]: '权限不足,无法访问',
|
||||
[ERROR_CODES.NOT_FOUND]: '请求的资源不存在',
|
||||
[ERROR_CODES.VALIDATION_ERROR]: '数据验证失败',
|
||||
[ERROR_CODES.SERVER_ERROR]: '服务器内部错误,请稍后重试'
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题颜色
|
||||
*/
|
||||
export const THEME_COLORS = {
|
||||
PRIMARY: '#1890ff',
|
||||
SUCCESS: '#52c41a',
|
||||
WARNING: '#faad14',
|
||||
ERROR: '#f5222d',
|
||||
INFO: '#722ed1',
|
||||
TEXT_PRIMARY: '#262626',
|
||||
TEXT_SECONDARY: '#8c8c8c',
|
||||
BORDER: '#e8e8e8',
|
||||
BACKGROUND: '#f5f5f5'
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画持续时间
|
||||
*/
|
||||
export const ANIMATION_DURATION = {
|
||||
FAST: 200,
|
||||
NORMAL: 300,
|
||||
SLOW: 500
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小程序限制
|
||||
*/
|
||||
export const WECHAT_LIMITS = {
|
||||
MAX_REQUEST_SIZE: 1048576, // 1MB
|
||||
MAX_UPLOAD_SIZE: 10485760, // 10MB
|
||||
MAX_CONCURRENT_REQUESTS: 10, // 最大并发请求数
|
||||
MAX_WEBSOCKET_CONNECTIONS: 5 // 最大WebSocket连接数
|
||||
}
|
||||
|
||||
export default {
|
||||
API_CONFIG,
|
||||
STORAGE_KEYS,
|
||||
INSURANCE_TYPES,
|
||||
INSURANCE_TYPE_LABELS,
|
||||
APPLICATION_STATUS,
|
||||
APPLICATION_STATUS_LABELS,
|
||||
POLICY_STATUS,
|
||||
POLICY_STATUS_LABELS,
|
||||
CLAIM_STATUS,
|
||||
CLAIM_STATUS_LABELS,
|
||||
USER_ROLES,
|
||||
USER_ROLE_LABELS,
|
||||
GENDER,
|
||||
GENDER_LABELS,
|
||||
BENEFICIARY_RELATIONS,
|
||||
FILE_TYPES,
|
||||
FILE_SIZE_LIMITS,
|
||||
PAGINATION,
|
||||
VALIDATION_RULES,
|
||||
ERROR_CODES,
|
||||
ERROR_MESSAGES,
|
||||
THEME_COLORS,
|
||||
ANIMATION_DURATION,
|
||||
WECHAT_LIMITS
|
||||
}
|
||||
73
insurance_mini_program/src/utils/request.js
Normal file
73
insurance_mini_program/src/utils/request.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// utils/request.js - 统一请求封装,兼容Vue.js和小程序的请求封装
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
// 动态获取保险端后端地址
|
||||
const BASE_URL = process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:3000/api' // 本地开发环境,对应 c:\nxxmdata\insurance_backend
|
||||
: 'https://your-insurance-backend.com/api' // 生产环境
|
||||
|
||||
// 统一请求封装,支持Vue.js和小程序
|
||||
export function request(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const userStore = useUserStore()
|
||||
const token = userStore.token || uni.getStorageSync('token')
|
||||
|
||||
// 统一请求配置
|
||||
const requestConfig = {
|
||||
url: BASE_URL + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
// 根据记忆添加缓存控制头,避免304问题
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
...options.header
|
||||
},
|
||||
timeout: 10000,
|
||||
success: (res) => {
|
||||
console.log('API请求成功:', options.url, res)
|
||||
|
||||
// 统一响应处理(兼容现有保险端格式)
|
||||
if (res.statusCode === 200) {
|
||||
if (res.data.status === 'success') {
|
||||
resolve(res.data)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message || '请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(res.data)
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
// token 过期,清理登录状态
|
||||
console.warn('Token过期,清理登录状态')
|
||||
userStore.logout()
|
||||
uni.removeStorageSync('token')
|
||||
uni.redirectTo({ url: '/pages/login/login' })
|
||||
reject(res.data)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(res.data)
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('API请求失败:', options.url, error)
|
||||
uni.showToast({
|
||||
title: '网络连接失败',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 发起请求(兼容小程序和H5)
|
||||
uni.request(requestConfig)
|
||||
})
|
||||
}
|
||||
|
||||
export default request
|
||||
243
insurance_mini_program/src/utils/util.js
Normal file
243
insurance_mini_program/src/utils/util.js
Normal file
@@ -0,0 +1,243 @@
|
||||
// utils/util.js - 通用工具函数
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param {Date|string|number} date 日期
|
||||
* @param {string} format 格式 'YYYY-MM-DD HH:mm:ss'
|
||||
*/
|
||||
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||
if (!date) return ''
|
||||
|
||||
const d = new Date(date)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hours = String(d.getHours()).padStart(2, '0')
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化金额
|
||||
* @param {number|string} amount 金额
|
||||
* @param {number} decimals 小数位数
|
||||
*/
|
||||
export function formatMoney(amount, decimals = 2) {
|
||||
if (!amount && amount !== 0) return '0.00'
|
||||
|
||||
const num = parseFloat(amount)
|
||||
if (isNaN(num)) return '0.00'
|
||||
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func 要防抖的函数
|
||||
* @param {number} delay 延迟时间(毫秒)
|
||||
*/
|
||||
export function debounce(func, delay = 300) {
|
||||
let timeoutId
|
||||
return function (...args) {
|
||||
const context = this
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => func.apply(context, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} func 要节流的函数
|
||||
* @param {number} limit 限制时间(毫秒)
|
||||
*/
|
||||
export function throttle(func, limit = 300) {
|
||||
let inThrottle
|
||||
return function (...args) {
|
||||
const context = this
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args)
|
||||
inThrottle = true
|
||||
setTimeout(() => inThrottle = false, limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号脱敏
|
||||
* @param {string} phone 手机号
|
||||
*/
|
||||
export function maskPhone(phone) {
|
||||
if (!phone || phone.length !== 11) return phone
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份证号脱敏
|
||||
* @param {string} idCard 身份证号
|
||||
*/
|
||||
export function maskIdCard(idCard) {
|
||||
if (!idCard || idCard.length < 6) return idCard
|
||||
return idCard.replace(/(\d{4})\d+(\d{4})/, '$1**********$2')
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号
|
||||
* @param {string} phone 手机号
|
||||
*/
|
||||
export function validatePhone(phone) {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
return phoneRegex.test(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号
|
||||
* @param {string} idCard 身份证号
|
||||
*/
|
||||
export function validateIdCard(idCard) {
|
||||
const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/
|
||||
return idCardRegex.test(idCard)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @param {string} filename 文件名
|
||||
*/
|
||||
export function getFileExtension(filename) {
|
||||
if (!filename) return ''
|
||||
const lastDot = filename.lastIndexOf('.')
|
||||
return lastDot === -1 ? '' : filename.substring(lastDot + 1).toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param {number} bytes 字节数
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串
|
||||
* @param {number} length 长度
|
||||
*/
|
||||
export function generateRandomString(length = 8) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝
|
||||
* @param {any} obj 要拷贝的对象
|
||||
*/
|
||||
export function deepClone(obj) {
|
||||
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 => deepClone(item))
|
||||
if (obj instanceof Object) {
|
||||
const clonedObj = {}
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
clonedObj[key] = deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询参数
|
||||
* @param {string} url URL地址
|
||||
*/
|
||||
export function getQueryParams(url) {
|
||||
const params = {}
|
||||
const queryString = url.split('?')[1]
|
||||
if (queryString) {
|
||||
queryString.split('&').forEach(param => {
|
||||
const [key, value] = param.split('=')
|
||||
params[decodeURIComponent(key)] = decodeURIComponent(value || '')
|
||||
})
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* 保险状态映射
|
||||
*/
|
||||
export const INSURANCE_STATUS = {
|
||||
pending: { text: '待审核', color: '#faad14' },
|
||||
approved: { text: '已通过', color: '#52c41a' },
|
||||
rejected: { text: '已拒绝', color: '#f5222d' },
|
||||
under_review: { text: '审核中', color: '#1890ff' }
|
||||
}
|
||||
|
||||
export const POLICY_STATUS = {
|
||||
active: { text: '有效', color: '#52c41a' },
|
||||
expired: { text: '已过期', color: '#8c8c8c' },
|
||||
cancelled: { text: '已取消', color: '#f5222d' },
|
||||
suspended: { text: '暂停', color: '#faad14' }
|
||||
}
|
||||
|
||||
export const CLAIM_STATUS = {
|
||||
pending: { text: '待处理', color: '#faad14' },
|
||||
reviewing: { text: '审核中', color: '#1890ff' },
|
||||
approved: { text: '已批准', color: '#52c41a' },
|
||||
rejected: { text: '已拒绝', color: '#f5222d' },
|
||||
paid: { text: '已赔付', color: '#722ed1' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态显示信息
|
||||
* @param {string} status 状态值
|
||||
* @param {string} type 类型 'insurance' | 'policy' | 'claim'
|
||||
*/
|
||||
export function getStatusInfo(status, type) {
|
||||
const statusMap = {
|
||||
insurance: INSURANCE_STATUS,
|
||||
policy: POLICY_STATUS,
|
||||
claim: CLAIM_STATUS
|
||||
}
|
||||
|
||||
return statusMap[type]?.[status] || { text: status, color: '#8c8c8c' }
|
||||
}
|
||||
|
||||
export default {
|
||||
formatDate,
|
||||
formatMoney,
|
||||
debounce,
|
||||
throttle,
|
||||
maskPhone,
|
||||
maskIdCard,
|
||||
validatePhone,
|
||||
validateIdCard,
|
||||
getFileExtension,
|
||||
formatFileSize,
|
||||
generateRandomString,
|
||||
deepClone,
|
||||
getQueryParams,
|
||||
getStatusInfo,
|
||||
INSURANCE_STATUS,
|
||||
POLICY_STATUS,
|
||||
CLAIM_STATUS
|
||||
}
|
||||
66
insurance_mini_program/uni.scss
Normal file
66
insurance_mini_program/uni.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
/* uni.scss - 全局样式变量和通用样式 */
|
||||
|
||||
/* 颜色变量 */
|
||||
$primary-color: #1890ff;
|
||||
$success-color: #52c41a;
|
||||
$warning-color: #faad14;
|
||||
$error-color: #f5222d;
|
||||
$text-color: #333333;
|
||||
$text-color-light: #666666;
|
||||
$text-color-lighter: #999999;
|
||||
$border-color: #d9d9d9;
|
||||
$background-color: #f5f5f5;
|
||||
|
||||
/* 尺寸变量 */
|
||||
$border-radius: 8rpx;
|
||||
$border-radius-large: 12rpx;
|
||||
$spacing-small: 20rpx;
|
||||
$spacing-medium: 30rpx;
|
||||
$spacing-large: 40rpx;
|
||||
|
||||
/* 字体变量 */
|
||||
$font-size-small: 24rpx;
|
||||
$font-size-base: 28rpx;
|
||||
$font-size-medium: 30rpx;
|
||||
$font-size-large: 32rpx;
|
||||
$font-size-xl: 36rpx;
|
||||
|
||||
/* 通用类样式 */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-overflow {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-overflow-2 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* 间距类 */
|
||||
.p-0 { padding: 0 !important; }
|
||||
.p-1 { padding: 10rpx !important; }
|
||||
.p-2 { padding: 20rpx !important; }
|
||||
.p-3 { padding: 30rpx !important; }
|
||||
.p-4 { padding: 40rpx !important; }
|
||||
|
||||
.m-0 { margin: 0 !important; }
|
||||
.m-1 { margin: 10rpx !important; }
|
||||
.m-2 { margin: 20rpx !important; }
|
||||
.m-3 { margin: 30rpx !important; }
|
||||
.m-4 { margin: 40rpx !important; }
|
||||
37
insurance_mini_program/vite.config.js
Normal file
37
insurance_mini_program/vite.config.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import uni from '@dcloudio/vite-plugin-uni'
|
||||
import { resolve } from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [uni()],
|
||||
base: '/insurance/',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, '.'),
|
||||
'~': resolve(__dirname)
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "./styles/variables.scss";`
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: false
|
||||
},
|
||||
build: {
|
||||
target: 'es6',
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'uni-app': ['@dcloudio/uni-app']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user