page Done

main
王文龙 2023-12-11 17:29:23 +08:00
commit f28cc3108f
618 changed files with 19799 additions and 0 deletions

8
.eslintignore Executable file
View File

@ -0,0 +1,8 @@
/index.html
node_modules
public
dist
shime-uni.d.ts
uni_modules
ua-markdown
src

10
.eslintrc.js Executable file
View File

@ -0,0 +1,10 @@
module.exports = {
extends: ['@viarotel-org'],
rules: {
'no-unused-vars': 'off',
'eqeqeq': 'off',
'no-undef': 'off',
'prefer-promise-reject-errors': 'off',
'antfu/top-level-function': 'off',
},
}

28
.gitignore vendored Executable file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#user
.hbuilderx
uni-pages.d.ts
pnpm-lock.yaml
yarn.lock
package-lock.json

2
.npmrc Executable file
View File

@ -0,0 +1,2 @@
registry=https://registry.npmmirror.com/
shamefully-hoist=true

4
.yarnrc Executable file
View File

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
registry "https://registry.npmmirror.com/"

152
CHANGELOG.md Executable file
View File

@ -0,0 +1,152 @@
# Changelog
## [2.5.5](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.5.4...v2.5.5) (2023-10-18)
### Bug Fixes
* 🚀 修复在 app 端环境下路由插件系统使用异常的问题 ([894ca30](https://github.com/viarotel-org/vite-uniapp-template/commit/894ca308d236fce5846e8348590cfbe1c01838c6))
## [2.5.4](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.5.3...v2.5.4) (2023-09-21)
### Bug Fixes
* 🔧 修复小程序端某些样式不生效的问题 ([e532395](https://github.com/viarotel-org/vite-uniapp-template/commit/e5323955809cf57d733064dfa2ebc14cc6f8f37f))
## [2.5.3](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.5.2...v2.5.3) (2023-09-14)
### Bug Fixes
* 🐛 修复小程序环境打包失败的问题 ([14c9645](https://github.com/viarotel-org/vite-uniapp-template/commit/14c9645d3c4b3abd248816087b3edd16e9973fc1))
## [2.5.2](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.5.1...v2.5.2) (2023-09-09)
### Bug Fixes
* 🔧 修复 uni-network 格式化参数的行为与 axios不一致的问题 ([00af3bf](https://github.com/viarotel-org/vite-uniapp-template/commit/00af3bfc50844ae45d5e860d86bf9aeabf3791d1))
* 🔧 修复打包后由于方法名混淆导致路由中间件无法匹配触发的问题 ([37515aa](https://github.com/viarotel-org/vite-uniapp-template/commit/37515aa0f526ae3810a979a347cc997443061fe4))
* 🔧 固定 qs 版本以解决不兼容微信小程序的问题 ([95f733e](https://github.com/viarotel-org/vite-uniapp-template/commit/95f733e8492e13688054739e6144b4fb39544696))
## [2.5.1](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.5.0...v2.5.1) (2023-09-09)
### Bug Fixes
* 🗑️ 去除登录页面冗余的空白顶栏 ([25bc3bd](https://github.com/viarotel-org/vite-uniapp-template/commit/25bc3bda88e8da69f8122e7b3e75422560f0b23e))
## [2.5.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.4.0...v2.5.0) (2023-09-09)
### Features
* 🚀 添加跳转外部网页功能 ([8d062a9](https://github.com/viarotel-org/vite-uniapp-template/commit/8d062a9d86126980181fb6e9ab0ca289f93b8c66))
## [2.4.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.3.3...v2.4.0) (2023-09-08)
### Features
* 🚀 对部分页面样式进行改进并去除冗余的控制台输出 ([e73ba19](https://github.com/viarotel-org/vite-uniapp-template/commit/e73ba1933c594ecc5e8ad0317d6d35345f9e972f))
## [2.3.3](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.3.2...v2.3.3) (2023-09-08)
### Bug Fixes
* 📈 修复入口页面模块引用格式不统一的问题 ([bd72731](https://github.com/viarotel-org/vite-uniapp-template/commit/bd72731cf866940aa4a4e1d84795bc035be05b8c))
## [2.3.2](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.3.1...v2.3.2) (2023-09-08)
### Bug Fixes
* 🔧 修复同步脚本错误的问题 ([73d802a](https://github.com/viarotel-org/vite-uniapp-template/commit/73d802abf100f853ae1c1f41a650090a483bfa3c))
## [2.3.1](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.3.0...v2.3.1) (2023-09-07)
### Bug Fixes
* 📝 修复文档描述错误 ([3325235](https://github.com/viarotel-org/vite-uniapp-template/commit/3325235dac5f0dac5301cbbafff111c1509548de))
## [2.3.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.2.1...v2.3.0) (2023-09-05)
### Features
* 🎨 主题色定制相关配置功能更新 ([bcadbfa](https://github.com/viarotel-org/vite-uniapp-template/commit/bcadbfaf583a283804bd1ebdd6b5846ae11f0fb0))
## [2.2.1](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.2.0...v2.2.1) (2023-09-01)
### Bug Fixes
* 🔧 修复使用 yarn 作为包管理器时启动项目报错的问题 ([1778bb9](https://github.com/viarotel-org/vite-uniapp-template/commit/1778bb9c4b56e097ba5cadc1ae6e37fd89357ca8))
## [2.2.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.1.1...v2.2.0) (2023-08-29)
### Features
* 🚀 登录示例页面调整 ([a0c1688](https://github.com/viarotel-org/vite-uniapp-template/commit/a0c16881e36e836ee7d9215dfec4615e1984a2bb))
## [2.1.1](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.1.0...v2.1.1) (2023-08-29)
### Bug Fixes
* bugfix ([9757722](https://github.com/viarotel-org/vite-uniapp-template/commit/97577229d7999f10a9efdcb3ef08efa8a3328cde))
### Performance Improvements
* 🚀 去除冗余的默认导出以降低生产包大小 ([88a529a](https://github.com/viarotel-org/vite-uniapp-template/commit/88a529a51541210c6a030fbb56ebc044173c0c28))
## [2.1.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.0.1...v2.1.0) (2023-08-28)
### Miscellaneous Chores
* release 2.1.0 ([ccc4cb1](https://github.com/viarotel-org/vite-uniapp-template/commit/ccc4cb19295420b632f61ae4e5424809e55dc7b8))
## [2.0.1](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.0.0...v2.0.1) (2023-08-14)
### Performance Improvements
* 🔧 调整路由导出方式以简化路由中间件定义方式 ([d0f9015](https://github.com/viarotel-org/vite-uniapp-template/commit/d0f901526adeed8ab60898a18d5ade046f14ceeb))
## [2.0.0](https://github.com/viarotel-org/vite-uniapp-template/compare/v2.0.0...v2.0.0) (2023-08-09)
### Features
* ✨ 样式优化及调整 ([c3d4a56](https://github.com/viarotel-org/vite-uniapp-template/commit/c3d4a56e836f4af4cc1d929d16f9b46319c69617))
* feat ([19894dc](https://github.com/viarotel-org/vite-uniapp-template/commit/19894dcbd075ba5181372df19c4e6c2387afa120))
### Bug Fixes
* :bug: bugfix ([2bf4391](https://github.com/viarotel-org/vite-uniapp-template/commit/2bf4391b96bd0b2e94acfcf7ee36a757d02808aa))
* :bug: 修复 uni-vite-plugin-h5-prod-effect 导致 h5 打包后无法正常运行的问题 ([cfe09f6](https://github.com/viarotel-org/vite-uniapp-template/commit/cfe09f65eecc4d9e1f7867a4280e2a54feb10158))
* 🐛 bugfix ([deaa1ec](https://github.com/viarotel-org/vite-uniapp-template/commit/deaa1ec2f283d9d54eb63e852ae1e30edb454dc1))
* 📝 bugfix ([91ed0eb](https://github.com/viarotel-org/vite-uniapp-template/commit/91ed0eb09ee84dd62769a00293667ea9d6ce5622))
* 📝 修复路由文件缺失导致报错的问题 ([4d51094](https://github.com/viarotel-org/vite-uniapp-template/commit/4d5109473f16c24530b797cd9aa6e48f36d862e9))
### Performance Improvements
* :ambulance: 减少生产环境下包大小 ([e1b67c6](https://github.com/viarotel-org/vite-uniapp-template/commit/e1b67c6fff1a2add11f498a0eba7bff70f794e4c))
* :memo: ([dd8dcf5](https://github.com/viarotel-org/vite-uniapp-template/commit/dd8dcf598dec5721e27c3e953535f33af1a627c1))
* 🎉 路由相关代码逻辑优化 ([101f828](https://github.com/viarotel-org/vite-uniapp-template/commit/101f828fcfdc8cdb04e29f6320d775fe84a2bfac))
* 📝 去除冗余代码 ([6cc02e9](https://github.com/viarotel-org/vite-uniapp-template/commit/6cc02e9baa3933695215a11457253b9cdcf2e2bd))
* 📝 去除冗余依赖 ([c98f33e](https://github.com/viarotel-org/vite-uniapp-template/commit/c98f33ef7e897640fa0b08fd1fda9dc9d5ed61e1))
* perf ([a48a7fc](https://github.com/viarotel-org/vite-uniapp-template/commit/a48a7fc7875f81a1e4d299004c69b7dafde29b99))
* 图标组件性能优化 ([1d2d89d](https://github.com/viarotel-org/vite-uniapp-template/commit/1d2d89d3708a72989a7fb4795a7c10d4ea076987))
### Miscellaneous Chores
* release 2.0.0 ([9990344](https://github.com/viarotel-org/vite-uniapp-template/commit/9990344751ab75dc77d2a1d7b00873b02148e656))

21
LICENSE Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 viarotel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

274
README.md Executable file
View File

@ -0,0 +1,274 @@
## 特点
- 💪 Assets: 提供了全局静态资源加载工具,无感切换加载本地静态资源/远程静态资源,解决小程序环境下包大小限制问题。
- 📦 SubPackages: 符合心智模型的分包风格,合理的 pages 目录结构,与分包配置轻松实现功能分包。
- 🛣 Router: 使用 [uniapp-router-next](https://gitee.com/wen-jason/uni-router/tree/main/packages/uniapp-router-next)并通过优化封装API 同 VueRouter 类似,扩展了拦截器、中间件、路由别名功能。
- 📊 Store: 使用 [Pinia](https://pinia.vuejs.org/zh/) 强力驱动,轻松实现应用状态管理。
- ⚡️ Request: 请求库使用 [uni-network](https://github.com/uni-helper/uni-network) 完全基于 uniapp API 编写的高性能请求库, API 同 axios。
- 👇 Z-paging: 内置了高性能且易于使用的业务常用下拉分页组件模块,轻松实现下拉刷新、上拉加载等功能。
- 💅 Unocss: 使用原子化 CSS 引擎,小程序环境下依然完美支持原子化的 class 命名书写规则。
- 🎨 UI-libs: 预设了 [uivew-plus](https://uiadmin.net/uview-plus/) 和 [uni-ui](https://uniapp.dcloud.net.cn/component/uniui/uni-ui.html) 两者相辅相成,轻松满足绝大多数业务场景,并支持主题色定制功能。
- 📝 NoTs: 只使用 JavaScript在常规业务场景或人员水平差距过大情况下TypeScript 并不会提升开发体验。
## 使用方法
### 安装项目依赖
> 打开并进入克隆的项目根目录下执行以下命令
> 以下命令推荐 使用 pnpm 进行操作 不过你依然可以使用 npm/yarn
```shell
pnpm install
```
### 运行项目
#### 任意编辑器直接运行本项目
```shell
# h5端
pnpm dev:h5
# 微信小程序端
pnpm dev:mp-weixin
# 安卓端
pnpm dev:app-android
#... 更多端请参阅 package.json/script
```
#### 在 HBuilder 中运行本项目
1. 将项目拖动到 HBuilder 中
2. 使用 pnpm install 安装好依赖
3. 点击项目 /src 目录中的任意文件
4. 点击编辑器上方点击运行选择需要运行的环境
### 功能示例
#### 静态资源处理
```js
// 使用远程静态资源
import { useAssets } from './utils/assets/remote'
// 使用本地静态资源
import { useAssets } from './utils/assets/local'
// 全局挂载
app.config.globalProperties.$assets = useAssets
// template中使用
// <img :src="$assets('/temp.png')" />
```
#### 全局主题色定制
> 由 [unocss-preset-shades](https://github.com/viarotel-org/packages/tree/main/packages/unocss-preset-shades#readme) 提供支持
```html
<!-- 设置文本色为主题色色调为 500 -->
<div class="text-primary-500"></div>
<!-- 设置背景色为主题色色调为 500 -->
<div class="bg-primary-500"></div>
<!-- 设置边框色为主题色色调为 500 -->
<div class="border border-primary-500"></div>
<!-- 更多使用方式请参阅 https://tailwindcss.com/docs -->
```
#### 路由间功能跳转
```js
// 跳转页面
const methods = {
routerDemo() {
this.$Router.push({
path: '/login',
query: {
id: 'id'
}
})
// 获取页面参数
this.$Route.query.id
// 关闭当前页面跳转到某个页面
this.$Router.replace('/login')
// 关闭所有打开的页面跳转到某个页面
this.$Router.replaceAll('/login')
}
}
// 为路由设置别名
// pages.config.js 中
const aliasConfig = {
path: 'pages/login/index',
// 通过添加 aliasPath 字段
aliasPath: '/login'
}
```
#### 使用路由守卫
> 位于 router/guards 中
```js
import store from '@/store/index.js'
const homePath = '/pages/index/index'
const loginPath = '/pages/login/index'
const whiteList = [loginPath]
export default (router) => {
const userStore = store.useUserStore()
const loginRoute = (to) => ({
path: loginPath,
navType: 'reLaunch',
force: true,
query: {
redirect: {
path: to.path,
query: to.query
}
}
})
router.beforeEach((to, from, next) => {
// console.log('permission.beforeEach.to', to)
// console.log('permission.beforeEach.from', from)
const token = userStore.token
const userId = userStore.userId
console.log('token', token)
console.log('userId', userId)
if (token) {
if (to.path === loginPath) {
next(homePath)
} else if (userId) {
next()
} else {
userStore
.getUserInfo()
.then(() => {
next()
})
.catch((error) => {
console.warn(error)
userStore.logout({ silenced: true })
next(loginRoute(to))
})
}
} else if (whiteList.includes(to.path)) {
next()
} else {
next(loginRoute(to))
}
})
router.afterEach(() => {})
}
```
#### 使用基于路由的中间件
> pages.config.js 中
```js
// 使用名为 realname 的中间件
const pageConfig = {
path: '/pages/personal/index',
aliasPath: '/personal',
meta: {
middleware: ['realname']
}
}
```
定义中间件
> router/guards/index.js 中
```js
// 使用 defineMiddleware 定义并包装为中间件
import realname from './realname/index.js'
import { defineMiddleware } from '$uni-router/middleware'
export default (app, router) => {
// 使用 defineMiddleware 定义了路由中间件
defineMiddleware(realname, { router, app })
}
```
编写路由中间件代码
> router/guards/realname/index.js 中
```js
import store from '@/store/index.js'
import { useDialog, useToast } from '@/utils/modals'
export default (router) => {
const userStore = store.useUserStore()
router.beforeEach((to, from, next) => {
console.log('realname.beforeEach.to', to)
console.log('realname.beforeEach.from', from)
const realStatus = userStore.userInfo.realStatus
switch (realStatus) {
case 3:
next()
break
case 2:
useToast('实名审核中, 请稍后再试').then(() => {
next(false)
})
break
case 4:
useDialog(`${userStore.userInfo.auditResult || '提交的实名信息不符'}`, {
title: '实名失败',
showCancelButton: true,
confirmText: '重新认证'
})
.then(() => {
next({ path: '/pages/realname/index' })
})
.catch(() => {
next(false)
})
break
default:
useDialog('请先进行实名认证', { showCancelButton: true })
.then(() => {
next({ path: '/pages/realname/index' })
})
.catch(() => {
next(false)
})
break
}
})
// router.afterEach(() => {})
}
```
### 主要使用的包
- vitejs
- uniapp
- pinia
- uview-plus
- uni-ui
- @uni-helper/uni-network
- uniapp-router-next
- z-paging
- unocss
- unocss-applet
### 常见问题
#### 无法正常安装依赖/无法启动
删除 pnpm-lock.yaml / yarn.lock / package-lock.json 文件后重新安装依赖

66
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,66 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}

24
index.html Executable file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport =
"CSS" in window &&
typeof CSS.supports === "function" &&
(CSS.supports("top: env(a)") || CSS.supports("top: constant(a)"));
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ", viewport-fit=cover" : "") +
'" />'
);
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

12
jsconfig.json Executable file
View File

@ -0,0 +1,12 @@
{
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": ".",
"lib": ["ESNext", "DOM"],
"paths": {
"@/*": ["src/*"]
},
"types": ["@dcloudio/types", "miniprogram-api-typings", "mini-types"]
},
}

100
package.json Executable file
View File

@ -0,0 +1,100 @@
{
"name": "escort",
"version": "1.0.0",
"scripts": {
"lint": "pnpm lint:js",
"lint:js": "eslint . --ext .vue,.js,.ts,.jsx,.tsx,.md --ignore-path .eslintignore --fix",
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3090820231124001",
"@dcloudio/uni-app-plus": "3.0.0-3090820231124001",
"@dcloudio/uni-components": "3.0.0-3090820231124001",
"@dcloudio/uni-h5": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-alipay": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-baidu": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-jd": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-lark": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-qq": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090820231124001",
"@dcloudio/uni-mp-xhs": "3.0.0-3090820231124001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090820231124001",
"@dcloudio/uni-ui": "^1.4.28",
"@uni-helper/uni-env": "^0.0.3",
"@uni-helper/uni-network": "^0.16.1",
"@uni-helper/uni-promises": "^0.2.0",
"@viarotel-org/unocss-preset-shades": "^0.7.0",
"color": "^4.2.3",
"dayjs": "^1.11.9",
"js-cookie": "^3.0.5",
"lodash-es": "^4.17.21",
"pinia": "2.0.33",
"qs": "6.9.7",
"uniapp-router-next": "^1.2.4",
"uview-plus": "^3.1.41",
"vue": "3.2.47",
"vue-i18n": "^9.1.9",
"z-paging": "^2.5.8"
},
"devDependencies": {
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-3090820231124001",
"@dcloudio/uni-cli-shared": "3.0.0-3090820231124001",
"@dcloudio/uni-stacktracey": "3.0.0-3090820231124001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090820231124001",
"@uni-helper/vite-plugin-uni-pages": "^0.2.9",
"@unocss/transformer-directives": "0.55.3",
"@viarotel-org/eslint-config": "^0.7.0",
"@vue/runtime-core": "^3.2.45",
"eslint": "^8.48.0",
"mini-types": "^0.1.7",
"miniprogram-api-typings": "^3.10.0",
"postcss": "^8.4.24",
"postcss-import": "^15.1.0",
"postcss-nested": "^6.0.1",
"postcss-remove-inline-comments": "^0.0.2",
"postcss-scss": "^4.0.6",
"sass": "^1.63.4",
"typescript": "5.1.6",
"unocss": "0.55.3",
"unocss-applet": "0.5.5",
"unplugin-auto-import": "^0.17.2",
"vite": "4.0.3",
"vite-plugin-eslint": "^1.8.1"
}
}

221
pages.config.js Executable file
View File

@ -0,0 +1,221 @@
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
export default defineUniPages({
easycom: {
autoscan: true,
custom: {
'^u-(.*)': 'uview-plus/components/u-$1/u-$1.vue',
'^uni-(.*)': '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
'z-paging/components/z-paging$1/z-paging$1.vue',
},
},
pages: [
{
path: 'pages/index/home/index',
aliasPath: '/',
meta: {
key: 'home',
},
style: {
navigationBarTitleText: '',
},
},
{
path: 'pages/index/order/index',
aliasPath: '/order',
meta: {
key: 'order',
},
style: {
navigationBarTitleText: '订单',
},
},
{
path: 'pages/index/personal/index',
aliasPath: '/personal',
meta: {
key: 'personal',
},
style: {
navigationBarTitleText: '我的',
},
},
{
path: 'pages/login/index',
aliasPath: '/login',
style: {
// navigationStyle: 'custom',
navigationBarTitleText: '登录',
},
},
{
path: 'pages/errors/404/index',
aliasPath: '/404',
style: {
navigationBarTitleText: '404',
},
},
],
subPackages: [
{
root: 'pages/webview',
pages: [
{
path: 'index',
aliasPath: '/webview',
style: {
navigationBarTitleText: 'webview',
},
},
],
},
{
root: 'pages/form',
pages: [
{
path: 'index',
aliasPath: '/form',
style: {
navigationBarTitleText: '填写订单',
},
},
{
path: 'complete',
aliasPath: '/complete',
style: {
navigationBarTitleText: '订单完成',
},
},
],
},
{
root: 'pages/other',
pages: [
{
path: 'income-details',
aliasPath: '/income-details',
style: {
navigationBarTitleText: '收入详情',
},
},
{
path: 'edit-userinfo',
aliasPath: '/edit-userinfo',
style: {
navigationBarTitleText: '编辑资料',
},
},
{
path: 'switch',
aliasPath: '/switch',
style: {
navigationBarTitleText: '成为陪诊师',
},
},
],
},
// {
// root: 'pages/middleware',
// pages: [
// {
// path: 'index',
// aliasPath: '/middleware',
// meta: {
// middleware: ['realname'],
// },
// style: {
// navigationBarTitleText: '路由中间件',
// },
// },
// ],
// },
{
root: 'pages/statement',
pages: [
{
path: 'index',
aliasPath: '/statement',
style: {
navigationBarTitleText: '产品服务协议',
},
},
],
},
{
root: 'pages/realname',
pages: [
{
path: 'index',
aliasPath: '/realname',
style: {
navigationBarTitleText: '实名认证',
},
},
],
},
{
root: 'pages/contact',
pages: [
{
path: 'index',
aliasPath: '/contact',
style: {
navigationBarTitleText: '联系我们',
},
},
],
},
],
tabBar: {
// color: '#999999',
// selectedColor: '#018d71',
// backgroundColor: '#F8F8F8',
list: [
{
// iconPath: 'static/tabbar/tab-home.png',
// selectedIconPath: 'static/tabbar/tab-home-active.png',
pagePath: 'pages/index/home/index',
// text: '主页',
},
{
// iconPath: 'static/tabbar/tab-example.png',
// selectedIconPath: 'static/tabbar/tab-example-active.png',
pagePath: 'pages/index/order/index',
// text: '示例',
},
{
// iconPath: 'static/tabbar/tab-personal.png',
// selectedIconPath: 'static/tabbar/tab-personal-active.png',
pagePath: 'pages/index/personal/index',
// text: '我的',
},
],
},
globalStyle: {
navigationBarTextStyle: 'white',
navigationBarTitleText: '',
navigationBarBackgroundColor: '#FF8CA6',
backgroundColor: '#ffffff',
// navigationStyle: "custom",
"usingComponents": {
// "van-cell": "/wxcomponents/vant-weapp/cell",
// "van-checkbox": "/wxcomponents/vant-weapp/checkbox",
// "van-cell-group": "/wxcomponents/vant-weapp/cell-group",
// "van-checkbox-group": "/wxcomponents/vant-weapp/checkbox-group",
"van-rate": "/wxcomponents/vant-weapp/rate",
"van-icon": "/wxcomponents/vant-weapp/icon"
// "van-tabbar": "/wxcomponents/@vant/weapp/tabbar/index",
// "van-tabbar-item": "/wxcomponents/@vant/weapp/tabbar-item/index"
}
},
// condition: {
// current: 0,
// list: [
// {
// name: 'pages/contact/index',
// path: 'pages/contact/index',
// },
// ],
// },
})

8
postcss.config.js Executable file
View File

@ -0,0 +1,8 @@
import nested from 'postcss-nested'
import removeInlineComments from 'postcss-remove-inline-comments'
import postcssScss from 'postcss-scss'
export default {
parser: postcssScss,
plugins: [removeInlineComments, nested],
}

23
src/App.vue Executable file
View File

@ -0,0 +1,23 @@
<script>
export default {
onLaunch() {
// console.log('App Launch')
this.$store.app.getSystemInfo()
},
async onShow() {
// console.log('App Show')
},
onHide() {
// console.log('App Hide')
}
}
</script>
<style lang="scss">
@import './styles/css/index.css';
@import 'uview-plus/index.scss';
/* #ifdef MP-WEIXIN */
@import '@/wxcomponents/vant-weapp/common/index.wxss';
/* #endif */
</style>

76
src/api/base/index.js Executable file
View File

@ -0,0 +1,76 @@
import request from '@/utils/request/index.js'
import { baseURL, responseSuccessCode } from '@/configs/request.js'
/**
* @description 模拟接口
* @param mockData 想要返回的模拟数据
*/
export function mock ({ mockData = {}, delay = 500 } = {}) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code: responseSuccessCode,
success: true,
data: mockData,
})
}, delay)
})
}
/**
* @desc 获取基础地址
*/
export const getBaseURL = () => baseURL
/**
* 获取字典数据
*/
export function getDictList (dictType) {
return mock({
dictType,
mockData: [],
})
}
/**
* @desc 获取上传地址
*/
export const getUploadURL = () => `${baseURL}/oss/upload`
/**
* @desc 下载文件
*/
export function downloadFile (id) {
return window.open(`${baseURL}/downloadFile?id=${id}`)
}
/**
* @desc 获取站点配置
*/
export const getSiteConfig = () => mock({ mockData: {} })
/**
* @desc 上传文件
*/
export const uploadFile = params =>
request.upload({
url: '/oss/upload',
dataType: 'json',
headers: {
'content-type': 'multipart/form-data',
},
...(params || {}),
})
/**
* @desc 服务项目列表
*/
export const fetchPzServerList = params =>
request.get('/pzserver/list', params)
/**
* @desc 医院列表
*/
export const fetchPzHospitalList = params =>
request.get('/pzhospital/list', params)

2
src/api/home/index.js Executable file
View File

@ -0,0 +1,2 @@
// import request from '@/utils/request/index.js'
// import { mock } from '../base/index.js'

17
src/api/index.js Executable file
View File

@ -0,0 +1,17 @@
import * as base from './base/index.js'
import * as user from './user/index.js'
import * as home from './home/index.js'
import * as realname from './realname/index.js'
const api = {
...base,
...user,
...home,
...realname,
}
export default {
install(app) {
app.config.globalProperties.$api = api
},
}

27
src/api/realname/index.js Executable file
View File

@ -0,0 +1,27 @@
// import request from '@/utils/request/index.js'
import { mock } from '../base/index.js'
/**
* 获取手机号
*/
export const phoneNumber = data => mock(data)
/**
* 获取可供选择的户籍地区
*/
export const wxdepartTree = data => mock(data)
/**
* 验证用户信息在数据库中是否已存在
*/
export const checkUserinfo = data => mock({ ...data, mockData: true })
/**
* 数据库中已存在 直接绑定该用户
*/
export const bindUserinfo = data => mock({ ...data, mockData: true })
/**
* 数据库中不存在 提交实名认证信息
*/
export const wxrealnameAuth = data => mock(data)

62
src/api/user/index.js Executable file
View File

@ -0,0 +1,62 @@
import { mock } from '../base/index.js'
import request from '@/utils/request/index.js'
/**
* @description 用户登录
*/
export const userLogin = data =>
// mock({ ...data, mockData: { token: 'mock-token' } })
request.post('/wxlogin', data)
/**
* @description 保存用户信息
*/
export const userSave = data =>
request.post('/register', data)
/**
* @description 获取当前登录用户手机号
*/
export const getPhone = params =>
// mock({ ...data, mockData: { token: 'mock-token' } })
request.get('/getPhone', params)
/**
* @description 获取当前登录用户信息
*/
export const getUser = params =>
// mock({ ...data, mockData: { token: 'mock-token' } })
request.get('/wxUser/info', params)
/**
* @description 获取当前登录用户信息
* @param realStatus 1-未实名 2-实名中 3--已经实名 4-实名失败
*/
export const getUserInfo = data =>
mock({
...data,
mockData: {
id: 'mock-id',
username: 'viarotel',
realStatus: '1',
},
})
/**
* @description 获取当前登录用户菜单
*/
export const getUserMenus = data => mock({ ...data, mockData: [] })
/**
* @description 用户修改密码
*/
export const updatePassword = data => mock(data)
/**
* @description 上传用户头像
*/
export const userHeadimg = data => mock(data)
/**
* @description 退出登录
*/
export const logout = () => mock()

BIN
src/assets/avatar.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

BIN
src/assets/fan.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/home-icon/h-icon-1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
src/assets/home-icon/h-icon-2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
src/assets/home-icon/h-icon-3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
src/assets/home-icon/h-icon-4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
src/assets/home-icon/h-icon-5.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
src/assets/home-icon/h-icon-6.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
src/assets/home-icon/h-icon-7.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src/assets/home-icon/h-icon-8.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
src/assets/image-wechat.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/kefu.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/assets/order-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
src/assets/personal/guanyu.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
src/assets/personal/lianxi.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/assets/personal/qiehuan.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
src/assets/personal/shouru.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/assets/sou.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/step-active.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
src/assets/step.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
src/assets/tabbar/home-sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
src/assets/tabbar/home.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/assets/tabbar/my-sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
src/assets/tabbar/my.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
src/assets/tabbar/order-sel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/assets/tabbar/order.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/assets/tongzhi.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
src/assets/zheng.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

0
src/components/.gitkeep Executable file
View File

View File

@ -0,0 +1,69 @@
<template>
<view class="mt-18rpx flex items-center">
<u-checkbox-group
v-model="checked"
placement="row"
shape="circle"
iconPlacement="left"
labelColor="#666"
labelSize="24rpx"
@change="handleChange"
>
<u-checkbox activeColor="#FF8CA6" name="1"></u-checkbox>
<!-- label="我已阅读并同意" -->
</u-checkbox-group>
<view
class="text-24rpx opacity-70 ml-0 text-#666"
@click="handleAgreeCheck"
>
我已阅读并同意
<text
class="text-#FF8CA6 active:text-primary-700"
@click.stop="handleAgree"
>
服务条款
</text>
同意书
</view>
</view>
</template>
<script setup>
const prop = defineProps({
agreed: {
type: Boolean,
default: () => false
},
checkFunc: {
type: Function,
default: () => () => {}
}
})
const checked = ref([false])
checked.value[0] = prop.agreed ? '1' : ''
const emit = defineEmits(['update:agreed'])
const instance = getCurrentInstance()
const { $Router } = instance.proxy
const handleAgree = () => {
$Router.push({ path: '/pages/statement/index' })
}
const handleAgreeCheck = () => {
checked.value[0] = checked.value[0] === '1' ? '' : '1'
}
const handleChange = () => {
emit('update:agreed', !!checked.value[0])
}
</script>
<style lang="scss" scoped>
:v-deep {
.u-checkbox {
uni-text,
span {
font-size: 24rpx !important;
color: #666 !important;
}
}
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<view class="fixed right-0 bottom-20% px-40rpx py-20rpx inline-block">
<button
@contact="handleContact"
class="z-2 w-110rpx h-110rpx absolute left-30rpx bottom-18rpx bg-transparent border-transparent"
open-type="contact"
></button>
<image class="w-88rpx h-88rpx z-1" :src="$assets('/kefu.png')"></image>
</view>
</template>
<script setup>
const handleContact = (e) => {
console.log('联系我们', e)
}
</script>
<style lang="scss" scoped>
.button-hover {
background-color: transparent;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<button
open-type="chooseAvatar"
hover-class="none"
class="w-full h-full overflow-hidden !hover:bg-transparent !active:bg-transparent"
@chooseavatar="onChooseAvatar"
>
<slot :show-value="showValue">
<image
:src="showValue"
mode="scaleToFill"
class="w-full h-full flex-none rounded-full overflow-hidden"
/>
</slot>
</button>
</template>
<script>
import { getFileBaseURL } from '@/configs/request'
import defaultAvatar from '@/static/avatar.png'
export default {
props: {
defaultValue: {
type: String,
default: ''
}
},
data() {
return {
showValue: defaultAvatar,
url: ''
}
},
watch: {
defaultValue: {
handler(value) {
if (!value) {
return
}
this.showValue = value
},
immediate: true
}
},
methods: {
onChooseAvatar(event) {
const { avatarUrl } = event.detail
this.showValue = avatarUrl
this.uploadAvatar()
},
async uploadAvatar() {
const res = await this.$api.uploadFile({
name: 'file',
filePath: this.showValue
})
if (res.success) {
this.url = res.msg
this.$emit('changeAvatar', this.url)
// this.submitAvatar()
} else {
this.$toast('头像上传失败!')
}
},
async submitAvatar() {
const params = {
avatar: this.url
}
const res = await this.$api.userHeadimg(params)
if (res.success) {
await this.$toast('头像设置成功')
}
}
}
}
</script>
<style></style>

View File

@ -0,0 +1,112 @@
<template>
<picker
:range-key="labelKey"
:value="rawValue"
:disabled="disabled"
:range="scopedOptions"
@change="onChange"
@cancel="onCancel"
>
<slot
:show-value="showValue"
:value="modelValue"
>
<view class="flex items-center w-full">
<view class="flex-1 w-0">
<u-input
:model-value="showValue"
:placeholder="placeholder"
border="none"
readonly
></u-input>
</view>
<view class="flex-none">
<u-icon
name="arrow-right"
></u-icon>
</view>
</view>
</slot>
</picker>
</template>
<script>
import * as dicts from '@/configs/dict/index.js'
export default {
components: {},
props: {
modelValue: {
type: [Number, String, Object],
default: null,
},
disabled: {
type: Boolean,
default: false,
},
dictType: {
type: String,
required: true,
},
options: {
type: Array,
default: () => [],
},
placeholder: {
type: String,
default: '请选择',
},
},
emits: ['update:model-value'],
data() {
return {
labelKey: 'dictLabel',
valueKey: 'dictValue',
rawValue: 0,
}
},
computed: {
scopedOptions() {
return dicts[this.dictType] || this.options || []
},
showValue() {
return (
this.modelValue?.[this.labelKey]
|| this.$showDictLabel(this.scopedOptions, this.modelValue)
|| ''
)
},
},
watch: {
modelValue: {
handler(value) {
console.log('modelValue.value', value)
const index = this.scopedOptions.findIndex(
item => item[this.valueKey] === (value?.[this.valueKey] || value),
)
if (index !== -1) {
this.rawValue = index
}
},
},
},
created() {},
methods: {
onChange(event) {
console.log('onChange.event', event)
const { value: index } = event.detail
this.rawValue = Number(index)
const modelValue = this.scopedOptions[index]
console.log('modelValue', modelValue)
this.$emit('update:model-value', modelValue)
},
onCancel(event) {
console.log('onCancel.event', event)
this.rawValue = 0
this.$emit('update:model-value', null)
},
},
}
</script>

View File

@ -0,0 +1,62 @@
<template>
<u-checkbox-group
v-model="checked"
@change="checkboxChange"
:borderBottom="true"
placement="column"
iconPlacement="right"
>
<u-checkbox
:customStyle="{ marginBottom: '16px' }"
v-for="(item, index) in list"
:key="index"
:label="item.name"
:name="item.name"
activeColor="#FF8CA6"
>
</u-checkbox>
</u-checkbox-group>
</template>
<script setup>
const emits = defineEmits(['change'])
const props = defineProps({
list: {
type: Array,
default: () => {
return [
{
name: '汽车',
disabled: false
},
{
name: '蒸汽机',
disabled: false
},
{
name: '猪肉',
disabled: false
},
{
name: '抄手',
disabled: false
}
]
}
},
checked: {
type: Array,
default: () => []
}
})
const checked = toRefs(props.checked)
const checkboxChange = (n) => {
emits('change', n)
}
</script>
<style lang="scss" scoped>
.button-hover {
background-color: transparent;
}
</style>

View File

@ -0,0 +1,81 @@
<template>
<u-tabbar
activeColor="#000"
inactiveColor="#000"
:value="activeItem"
:safeAreaInsetBottom="true"
:fixed="true"
:border="false"
>
<block v-for="item in tabbarList" :key="item.path">
<u-tabbar-item
:text="item.text"
:name="item.name"
@click="$Router.push(item.path)"
v-if="item.show"
>
<template #inactive-icon>
<!-- <u--image width="48rpx" height="48rpx" :src="$assets(item.icon)" /> -->
<image class="u-tabbar-item__icon" :src="$assets(item.icon)"></image>
</template>
<template #active-icon>
<!-- <u--image width="48rpx" height="48rpx" :src="$assets(item.activeIcon)" /> -->
<image
class="u-tabbar-item__icon"
:src="$assets(item.activeIcon)"
></image>
</template>
</u-tabbar-item>
</block>
</u-tabbar>
</template>
<script setup>
import store from '@/store/index.js'
import { useRoute } from '@/utils/uni-router'
uni.hideTabBar()
const route = useRoute()
const instance = getCurrentInstance()
const { $assets } = instance.proxy
const userStore = store.useUserStore()
const userInfo = computed(() => userStore.userInfo)
const tabbarList = ref([
{
text: '首页',
icon: '/tabbar/home.png',
activeIcon: '/tabbar/home-sel.png',
path: '/pages/index/home/index',
name: 'home',
show: true
},
{
text: '订单',
icon: '/tabbar/order.png',
activeIcon: '/tabbar/order-sel.png',
path: '/pages/index/order/index',
name: 'order',
show: true
},
{
text: '我的',
icon: '/tabbar/my.png',
activeIcon: '/tabbar/my-sel.png',
path: '/pages/index/personal/index',
name: 'personal',
show: true
}
])
const activeItem = computed(() => {
return route.meta.key
})
</script>
<style lang="scss" scoped>
.u-tabbar-item__icon {
width: 48rpx;
height: 48rpx;
object-fit: contain;
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<u-upload
@afterRead="afterRead"
@delete="deletePic"
accept="image"
uploadIconColor="#999"
v-bind="omit($attrs, ['disabled', 'maxCount', 'maxSize'])"
:fileList="fileList"
:deletable="!$attrs.disabled"
:maxSize="10 * 1024 * 1024"
:maxCount="$attrs.disabled ? fileList.length : $attrs.maxCount"
/>
</template>
<script setup>
import { omit } from 'lodash-es'
const instance = getCurrentInstance()
const { $api } = instance.proxy
const emits = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: Array,
default: () => []
}
})
const fileList = ref(props.modelValue)
//
const deletePic = (event) => {
fileList.value.splice(event.index, 1)
emits('update:modelValue', fileList.value)
}
//
const afterRead = async (event) => {
// mutiple true , file
let lists = [].concat(event?.file || [])
let fileListLen = fileList.value.length
lists.map((item) => {
fileList.value.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
let item = fileList.value[fileListLen]
const result = await $api.uploadFile({
name: 'file',
filePath: lists[i].url
})
result.code === 200
? fileList.value.splice(fileListLen, 1, {
...item,
status: 'success',
message: '',
url: result.msg
})
: fileList.value.splice(fileListLen, 1)
fileListLen++
}
emits('update:modelValue', fileList.value)
}
</script>
<style lang="scss" scoped>
:deep {
.u-upload__button {
background-color: #f2f2f2;
border-radius: 12rpx;
}
.u-icon__icon {
font-size: 80rpx !important;
}
}
</style>

9
src/configs/devServer.js Executable file
View File

@ -0,0 +1,9 @@
export {
useProxy,
requestURL as proxyURL,
proxyPath,
proxyPort,
appBasePath,
requestPath,
requestFilePath,
} from './index'

14
src/configs/dict/index.js Executable file
View File

@ -0,0 +1,14 @@
export const approveStatus = [
{
dictLabel: '待审核',
dictValue: 1,
},
{
dictLabel: '审核通过',
dictValue: 2,
},
{
dictLabel: '审核拒绝',
dictValue: 3,
},
]

28
src/configs/index.js Executable file
View File

@ -0,0 +1,28 @@
import { isH5 } from '@uni-helper/uni-env'
const isProduction = process.env.NODE_ENV === 'production'
export const appName = 'Escort'
// 项目主题色
export const primaryColor = '#FF8CA6'
// 项目基础路径
export const appBasePath = isProduction ? './' : './'
// 请求地址
export const requestURL = 'http://192.168.2.155:7788/hospital/app'
export const requestPath = ''
export const requestFilePath = '/file'
// 是否开启代理
export const useProxy = isH5
// 代理路径
export const proxyPath = '/proxy'
// 代理端口号
export const proxyPort = 1996
// 是否开启加密
export const useEncrypt = false
// 是否使用远程导航菜单
export const useRemoteMenu = true
// 主页面路径
export const homePage = 'pages/index/home/index'

9
src/configs/menu/index.js Executable file
View File

@ -0,0 +1,9 @@
export default [
{
parentId: '/',
id: '/',
path: '/',
meta: { title: '首页', icon: '' },
children: [],
},
]

44
src/configs/request.js Executable file
View File

@ -0,0 +1,44 @@
import {
proxyPath,
requestFilePath,
requestPath,
requestURL,
useProxy,
} from './index'
const isDevelopment = process.env.NODE_ENV === 'development'
// 请求基础域名
export function getBaseURL () {
let tempURL = ''
if (useProxy) {
tempURL = isDevelopment ? proxyPath : window.location.origin + requestPath
}
else {
tempURL = requestURL + requestPath
}
return tempURL
}
// 文件基础域名
export function getFileBaseURL () {
let tempURL = ''
if (useProxy) {
tempURL = isDevelopment
? requestFilePath
: window.location.origin + requestFilePath
}
else {
tempURL = requestURL + requestFilePath
}
return tempURL
}
// 请求域名
export const baseURL = getBaseURL()
// 响应成功code值
export const responseSuccessCode = 200
// 超时时间
export const timeout = 20 * 1000
// 是否开启加密
export { useEncrypt } from './index'

View File

@ -0,0 +1,31 @@
<template>
<i class="iconfont inline-block" :class="[deepClass, iconName]">
<slot />
</i>
</template>
<script>
import '@/icons/fonts/iconfont.css'
export default {
props: {
prefix: {
type: String,
default: 'icon-',
},
name: {
type: String,
required: true,
},
deepClass: {
type: [String, Object, Array],
default: () => [],
},
},
computed: {
iconName() {
return `${this.prefix}${this.name}`
},
},
}
</script>

50
src/icons/fonts/iconfont.css Executable file
View File

@ -0,0 +1,50 @@
@font-face {
font-family: "iconfont"; /* Project id 3854221 */
src:
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAeUAAsAAAAADdgAAAdHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACEAAqNJIp+ATYCJAMoCxYABCAFhGcHgR4b2AtRVHCqyH4kVLdVMI95iXryNL07cjVxNxNmRFDfDaUkiNbK6pmFoxCSiyRQBOrzQoVQAWkgoeNQkYpF9Rjhb7Lu/dRbanvJc2GdjGTB5mX7CRxOPok8/elgzpaAt3gF9+0FvoL/8Ap2Khv28xQ8ueO1+Zm2ZyKI6/+/n8sTi1v6dXKHWS/3ze/M34aJtEUah9IWEiYWOYTK4XQaoXRsXt1iPCtJM2OW8lxS41YhlV/DTQRaVivmurO6sQUE9AxSFbDc2XODpH/0CNwgYKSQokEdGt6KumMhHihJ99Jl3GffH8PWBkNSZvZh9p2vGgFn3ho/J7vN7CLyL4F5vEm4ckKJjOUri8TN5uRlbhEtZ65W1V8VDgA6GpKib/Xbhe+uvDv4OXm8S1/K23kZu90zdIw03gkAPUYoVWqNcf/xmkSFc3VAdtHZPEmLl0uiLzWXgr55uZT0zWdGqb4FkEtN38JcGvRfySUz0JFL8D85pAmS1tDxgElAmgWeLyDbNbaWFZJwPjSPDmLcp3m77y8PCQ3y958fHRIcElbrF3gieCxsh8NUBz2B2MBwZD2KMunxRIfDVY0ahzOAGbQgPSYNTTSgRNDCGhyangvVGgqXTXHmLxyC+uk2oGEwtqsOYkPWoJabm3EaL9vTWNGaprknKw53hrtVTyTydPXo6ZxkSweeTlJBlMpTM11EciF3ISeKnNyZkYKUgl8jRSblUyVCoKJ3rpJUBy/HFS5XHCovawWzFYcdikUdy2QVs2JG22oZW4EUm7dERAhImfkzRGSjFYeC5yfPrEwOZKIBB5kxHzxwR70rSnK7ELq1N9B6Gt8qjG7vkpTeu+qdxFF8p9ZJxM1uHcZlWSi7nFTmJudUnNoaT7E6F0tiELCl7oQkCDg82yQFWEMn9iiW79wxQVXu3hWIdPu2gMRbt44YEuKI10rYQCvigmw00eP4M8F2nF0ik0wOCbIRXT3zYiXOjHoGcYd6CkOLMmK5oEBrDNeB5TlUzwyNqqVaWLy1ZUYdgOq7SW8jDLWhh5oyRLelIO6q3vT2qqpb290VFK5C1TSpnF4bLL1L6xIJrlbhmqiyllSba/kCHihDwOlBqr4xssMzscXbR9xbrLMVzBdWY/NggS9MBVv7kbwxlGln4xNswCuv5+dgMtnJOy/xM6/N4NcGui9mTl4kLyozvMEvtlA/mC7mEfNYj8VGDIYdnVATJXid1V5YuBfvgm4KnMVQUeZlavs452NbHLK00sCZjdHyvR8WfqDyjOAVVTOn5ixVWTGg/HFPZeA1pV4CtXZxa+B8N1RcizKhU47ePeP/33wqmIBwbPn9Eb0cGG1Cr29sgZcJBPAS8mP9ODMYrZcbcX+5QJGaFoIA8uZ90fVeApQP8jIVxPpyFgha+Hzlpzn9JiyatMH758prFJ86O3FlMLvw5t3rIE76T2SYuzvW7C5cQ0bXFO62BCGFmQts9GLu+LmgamrGwEDmtEUZ0wJ//vzxM3Dd1MUdH/wsUGlKrI1PqG1PqAtqj6rq2uNrYZBrzMOt2VsfZpWNPI5+BfW75zuwzfqkohzn4J07cXn5E6uVaygObnX5Ut6Ed+6sPVBTxRerDR84x/c5uu3Lltm7HX3XDQM3nvP3Oa5XbARmXOnOvBTSmX5pRsun2E++0cOA8VNCH4LND56K4iXzJepywrzIfLnRpsvvKpD7qI4zpTGGLTSfEMXL5svNS4JHO2a0XEoTQx92Zw7W+ejX4ICHV1rmAcJAfhwPPbs/q7czuztHgpJj4oHXoi76mThLqYXdVb2LZZtFr7Z4dPVsrI1vSGyH7Q0ngowct6yO/j9bQnr5lOShkq0TlrGmr+VfTeEZu9oCVp1LbOxJKYsrtcQ97illWzNb2WD06U42cyZ0ialKb8KCGR8Auv/yu8wwbXJ+kg90issb88i4/M7VwiVfg/Q/B+RnqQ2CabiqTzchv8i7v/kr+XNt+1+fwMI/TPvXoP7fv6M7uMnjtTYkyObvedXRAnTt2YowamkVH4vJHqNMX7SMM0rLGzXfmoCWU0EXK8Z6OrZnHNnyh+IZl0BSmwZZw1xRIVsKpba1UGnYAS3LVFGbW13nJoomFpoDEEY7BMlI7yEb7ZyokN2D0kTvoDLaf2g5Ev6ONg4QwxUEDVI0AocmQWFYU7F5KUu3CXGGPEjKGtsQcQqoh2nJqfn2GqQiMsQmzjEunVIMMdEUWC2cDsmyBnWiiWiYJvOU6vkpKbhqk+RhTQEVCGQQDbKOgIZMKikYplHh1DT785ogHINsECGK2jjh5EyYPpQmWSpBVONSSdOuZKjTGE46ihyDHhFsU0DVEoJksUUD6dVNImRYTybjezTp8qU4qTDVlTy/R3nUtt+QGeytW5EiRxFlVFFHI5q/EAdTX0iSjUmawRCkTuaRynmNCN40AQleo+xEw7wROEiINiFJRpgmDWnyiH82ZOiitcExMlwqSINqicDxbbu7SwgbAAA=') format('woff2'),
url('//at.alicdn.com/t/c/font_3854221_ct02qwtu5gl.ttf?t=1691462687498') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
/* font-size: 16px; */
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-m-luyou:before {
content: "\e858";
}
.icon-renzheng:before {
content: "\e62f";
}
.icon-dingwei:before {
content: "\e7cd";
}
.icon-qingchu:before {
content: "\e630";
}
.icon-arrow-left-bold:before {
content: "\e685";
}
.icon-arrow-up-bold:before {
content: "\e686";
}
.icon-arrow-down-bold:before {
content: "\e687";
}
.icon-arrow-right-bold:before {
content: "\e688";
}
.icon-kefu:before {
content: "\ec2e";
}

7
src/icons/index.js Executable file
View File

@ -0,0 +1,7 @@
import ViaIcon from './components/ViaIcon/index.vue'
export default {
install(app) {
app.component('ViaIcon', ViaIcon)
},
}

51
src/main.js Executable file
View File

@ -0,0 +1,51 @@
import { createSSRApp } from 'vue'
import 'virtual:uno.css'
import App from './App.vue'
import store from './store/index.js'
import router from './router/index.js'
// import routerGuards from './router/guards/index.js'
import api from './api/index.js'
import plugins from './plugins/index.js'
import mixins from './mixins/index.js'
import ViaIcon from './icons/components/ViaIcon/index.vue'
import { useDialog, useLoading, useToast } from './utils/modals/index.js'
import _showDictLabel from './utils/showDictLabel.js'
// 为 remote 时使用远程静态资源 常用于小程序
// 为 local 时使用本地静态资源
// import { useAssets } from './utils/assets/remote.js'
import { useAssets } from './utils/assets/local.js'
export function createApp () {
const app = createSSRApp(App)
app.use(store)
app.use(router)
// app.use(routerGuards, router)
app.use(api)
app.use(plugins)
app.use(mixins)
app.component('ViaIcon', ViaIcon)
app.config.globalProperties.$dialog = useDialog
app.config.globalProperties.$toast = useToast
app.config.globalProperties.$loading = useLoading
app.config.globalProperties.$showDictLabel = _showDictLabel
// 静态资源加载工具
app.config.globalProperties.$assets = useAssets
return {
app,
Pinia: store.Pinia,
}
}

72
src/manifest.json Executable file
View File

@ -0,0 +1,72 @@
{
"name": "vite-uniapp-template",
"appid": "",
"description": "",
"versionName": "2.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {},
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"ios": {},
"sdkConfigs": {}
}
},
"quickapp": {},
"mp-weixin": {
"appid": "wx55ff808ba0e28b1d",
"setting": {
"urlCheck": false,
"ignoreUploadUnusedFiles": false,
"ignoreDevUnusedFiles": false
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
},
"requiredPrivateInfos": ["getLocation", "chooseLocation"]
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3"
}

7
src/mixins/index.js Executable file
View File

@ -0,0 +1,7 @@
import share from './share/index.js'
export default {
install(app) {
app.mixin(share)
},
}

24
src/mixins/share/index.js Executable file
View File

@ -0,0 +1,24 @@
import { appName } from '@/configs/index'
export default {
data() {
return {
shareAppMessageProps: {},
shareTimelineProps: {},
}
},
onShareAppMessage() {
return {
title: appName,
path: '/pages/index/index',
...this.shareAppMessageProps,
}
},
onShareTimeline() {
return {
title: appName,
query: '',
...this.shareTimelineProps,
}
},
}

175
src/pages.json Executable file
View File

@ -0,0 +1,175 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
}
},
"pages": [
{
"path": "pages/index/home/index",
"aliasPath": "/",
"meta": {
"key": "home"
},
"style": {
"navigationBarTitleText": ""
},
"type": "home"
},
{
"path": "pages/index/order/index",
"aliasPath": "/order",
"meta": {
"key": "order"
},
"style": {
"navigationBarTitleText": "订单"
}
},
{
"path": "pages/index/personal/index",
"aliasPath": "/personal",
"meta": {
"key": "personal"
},
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/login/index",
"aliasPath": "/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/errors/404/index",
"aliasPath": "/404",
"style": {
"navigationBarTitleText": "404"
}
}
],
"subPackages": [
{
"root": "pages/webview",
"pages": [
{
"path": "index",
"aliasPath": "/webview",
"style": {
"navigationBarTitleText": "webview"
}
}
]
},
{
"root": "pages/form",
"pages": [
{
"path": "index",
"aliasPath": "/form",
"style": {
"navigationBarTitleText": "填写订单"
}
},
{
"path": "complete",
"aliasPath": "/complete",
"style": {
"navigationBarTitleText": "订单完成"
}
}
]
},
{
"root": "pages/other",
"pages": [
{
"path": "income-details",
"aliasPath": "/income-details",
"style": {
"navigationBarTitleText": "收入详情"
}
},
{
"path": "edit-userinfo",
"aliasPath": "/edit-userinfo",
"style": {
"navigationBarTitleText": "编辑资料"
}
},
{
"path": "switch",
"aliasPath": "/switch",
"style": {
"navigationBarTitleText": "成为陪诊师"
}
}
]
},
{
"root": "pages/statement",
"pages": [
{
"path": "index",
"aliasPath": "/statement",
"style": {
"navigationBarTitleText": "产品服务协议"
}
}
]
},
{
"root": "pages/realname",
"pages": [
{
"path": "index",
"aliasPath": "/realname",
"style": {
"navigationBarTitleText": "实名认证"
}
}
]
},
{
"root": "pages/contact",
"pages": [
{
"path": "index",
"aliasPath": "/contact",
"style": {
"navigationBarTitleText": "联系我们"
}
}
]
}
],
"tabBar": {
"list": [
{
"pagePath": "pages/index/home/index"
},
{
"pagePath": "pages/index/order/index"
},
{
"pagePath": "pages/index/personal/index"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#FF8CA6",
"backgroundColor": "#ffffff",
"usingComponents": {
"van-rate": "/wxcomponents/vant-weapp/rate",
"van-icon": "/wxcomponents/vant-weapp/icon"
}
}
}

27
src/pages/contact/index.vue Executable file
View File

@ -0,0 +1,27 @@
<template>
<view class="h-full flex flex-col px-4 py-3 bg-gray-50">
<view class="relative flex-none">
<image
:src="$assets('/images/contact/bg-tel.jpg')"
mode="widthFix"
class="w-full"
/>
</view>
</view>
</template>
<script>
import wechatImage from '@/assets/image-wechat.png'
export default {
data() {
return {
wechatImage
}
},
computed: {},
methods: {}
}
</script>
<style></style>

12
src/pages/errors/404/index.vue Executable file
View File

@ -0,0 +1,12 @@
<template>
<view class="h-full flex items-center justify-center -mt-12">
<u-empty mode="page" text="404">
</u-empty>
</view>
</template>
<script>
export default {}
</script>
<style></style>

View File

@ -0,0 +1,39 @@
<template>
<view class="h-full bg-white px-24rpx">
<view class="mt-20rpx mb-20rpx text-34rpx">上传工作记录</view>
<upload-img v-model:fileList="fileList" multiple :maxCount="10" />
<view class="text-24rpx text-#666 mt-20rpx"
>请上传陪诊工作中的照片建议一个环节一张照片</view
>
<view class="mt-40rpx mb-20rpx text-34rpx">填写备注</view>
<u--textarea
v-model="remark"
placeholder=""
maxlength="300"
count
:autoHeight="false"
/>
<view
class="fixed bottom-8 left-0 right-0 text-white pt-12rpx border-t-1px border-solid border-#E5E5E5"
><view
class="bg-#FF8CA6 rounded-24rpx w-660rpx h-80rpx text-center leading-80rpx hover:shadow-md mx-auto"
>订单已完成提交工作记录</view
></view
>
</view>
</template>
<script setup>
const fileList = ref([])
const remark = ref('')
</script>
<style lang="scss" scoped>
:deep {
.u-textarea,
.u-textarea__count {
background-color: #f2f2f2 !important;
}
}
</style>

536
src/pages/form/index.vue Normal file
View File

@ -0,0 +1,536 @@
<template>
<view>
<view class="px-24rpx py-20rpx bg-[f2f2f2] min-h-[calc(100vh-230rpx)]">
<view class="rounded-16rpx bg-#fff pt-24rpx pb-16rpx">
<view class="flex items-center">
<view
class="relative flex-1 flex-col items-center flex"
v-for="item in list"
:key="item.id"
>
<image
:src="
current >= item.id
? $assets('/step-active.png')
: $assets('/step.png')
"
class="w-76rpx h-76rpx"
/>
<text class="text-white absolute top-18rpx z-10 text-28rpx">{{
item.id
}}</text>
<view
:class="current >= item.id ? 'text-#FF8CA6' : 'text-#999'"
class="text-24rpx mt-12rpx"
>{{ item.label }}</view
>
<view
v-if="item.id < list.length"
:class="current >= item.id ? 'bg-#FF8CA6' : 'bg-#999'"
class="step-line absolute left-77% top-30% h-4rpx w-46% rounded-md"
></view>
</view>
</view>
</view>
<view
@click="handleShowPicker"
class="rounded-16rpx bg-#fff pt-24rpx pr-16rpx pl-30rpx pt-36rpx pb-34rpx my-20rpx flex items-center justify-between"
>
<text class="text-34rpx font-bold">服务项目</text>
<u-icon
:label="typeVal"
space="20rpx"
labelColor="#FF8CA6"
labelSize="28rpx"
labelPos="left"
name="arrow-right"
width="48rpx"
height="48rpx"
color="#666"
bold
/>
</view>
<view class="rounded-16rpx overflow-hidden bg-#fff px-20rpx pb-20rpx">
<u--form
labelPosition="left"
:model="model"
:rules="rules"
ref="formRef"
borderBottom
errorType="border-bottom"
labelWidth="auto"
>
<u-form-item
v-for="item in formList"
:key="item.key"
:required="item.required"
:label="item.label"
:prop="item.key"
>
<u--input
v-if="item.type === 'string'"
v-model="model[item.key]"
border="none"
inputAlign="right"
:placeholder="item.placeholder"
:readonly="current > 1"
maxlength="200"
/>
<!-- model[item.key] ? item.type === 'date' ? $u.timeFormat(model[item.key], 'yyyy-mm-dd') : model[item.key] : item.placeholder -->
<view
v-else
@click="item.show = true"
class="w-full text-right u-line-1 pl-2"
:class="item.val ? '' : 'text-#C0C4CC'"
>{{ item.val || item.placeholder }}
</view>
</u-form-item>
</u--form>
<view
v-if="[4, 5].includes(curType)"
class="mt-32rpx mb-10rpx text-28rpx"
>
<view
>{{ curType === 5 ? '挂号' : '取报告' }}凭证<text
class="u-form-item__body__left__content__required text-#f56c6c ml-4rpx"
>*</text
>
</view>
<view class="mb-10rpx text-24rpx text-#999"
>请勿上传与医保卡相关图片</view
>
<upload-img
v-model="fileList"
disabled
multiple
:maxCount="10"
></upload-img>
</view>
<view class="mt-22rpx mb-20rpx text-28rpx">备注</view>
<u--textarea
v-model="model.remark"
placeholder=""
maxlength="300"
count
:autoHeight="false"
/>
<view class="text-28rpx mt-20rpx mb-8rpx">注意事项</view>
<view class="text-24rpx text-#999">
1. 服务项目为必填项
<br />
2. 服务时间为必填项
<br />
3. 服务地点为必填项
<br />
</view>
<agreed-comp v-model:agreed="agreed" />
</view>
<u-picker
:show="show"
:columns="columns"
keyName="serveName"
valueName="id"
@confirm="onPickerConfirm"
@cancel="show = false"
@close="handleShowPicker"
cancelText="取消"
confirmColor="#FF8CA6"
closeOnClickOverlay
></u-picker>
<block v-for="item in dateAndSelectFormList" :key="item.key">
<u-datetime-picker
v-if="item.type === 'date' || item.type === 'time'"
v-model="model[item.key]"
:show="item.show"
:mode="item.type"
:minDate="item.type === 'date' ? new Date().getTime() : 0"
:minHour="item.type === 'time' ? new Date().getHours() : 0"
:minMinute="item.type === 'time' ? new Date().getMinutes() : 0"
@confirm="onDatePickerConfirm($event, item)"
@cancel="item.show = false"
@close="item.show = false"
confirmColor="#FF8CA6"
closeOnClickOverlay
/>
<u-picker
v-if="item.type === 'select'"
:show="item.show"
:columns="item.options || columns"
keyName="label"
@confirm="onSelectPickerConfirm($event, item)"
@cancel="item.show = false"
@close="item.show = false"
cancelText="取消"
confirmColor="#FF8CA6"
closeOnClickOverlay
></u-picker>
<u-popup
v-if="item.type === 'checkbox'"
class="overflow-hidden"
:show="item.show"
round="16rpx"
@close="item.show = false"
closeable
>
<view class="h-700rpx pt-90rpx relative">
<view
class="text-#000 absolute left-50% translate-x-[-50%] top-30rpx"
>护理项目</view
>
<view
class="h-full w-full overflow-y-auto py-4 px-30rpx rounded-16rpx overflow-hidden"
>
<popup-checkbox @change="onCheckboxChange($event, item)" />
</view>
</view>
</u-popup>
</block>
</view>
<button-contact />
<view
class="bg-#fff h-150rpx w-full sticky bottom-0 flex justify-between items-center pr-24rpx pl-40rpx pb-48rpx rounded-sm"
>
<block v-if="true">
<view>
<text class="text-32rpx"></text>
<text class="font-bold text-54rpx">{{
typeSelItem.price || 198
}}</text>
</view>
<view
@click="onSubmit"
class="text-white bg-#FF8CA6 rounded-24rpx w-240rpx h-80rpx text-center leading-80rpx hover:shadow-md"
>立即下单</view
>
</block>
<view
v-if="false"
class="text-white bg-#FF8CA6 rounded-24rpx w-full mx-30rpx h-80rpx text-center leading-80rpx hover:shadow-md"
>{{ true ? '开始服务' : '订单已完成,提交工作记录' }}</view
>
</view>
</view>
</template>
<script setup>
import { useRoute } from '@/utils/uni-router'
const route = useRoute()
const instance = getCurrentInstance()
const { $u, $api, $Router, $Dialog } = instance.proxy
// 线
const list = ref([
{ label: '填写订单', id: 1 },
{ label: '在线支付', id: 2 },
{ label: '服务中', id: 3 },
{ label: '服务完成', id: 4 }
])
const current = ref(1)
const show = ref(false)
const fileList = ref([
{
message: '',
size: 20042,
// status: 'success',
thumb: 'http://tmp/Y2K3DucvbS0Aa23b6ba2697baa86a0eb579485736a6a.jpeg',
type: 'image',
url: 'https://xjl559.oss-cn-shanghai.aliyuncs.com/2023/12/08/3d4a506c74a145ba_Y2K3DucvbS0Aa23b6ba2697baa86a0eb579485736a6a.jpeg'
}
])
//
const columns = reactive([
[
{ label: '半天陪诊', id: 1 },
{ label: '全天陪诊', id: 2 },
{ label: '代办约号', id: 3 },
{ label: '取送报告', id: 4 },
{ label: '代取送药', id: 5 },
{ label: '上门护理', id: 6 }
]
])
// , , ,,, , ,
const formItems = [
{
label: '收货地址',
placeholder: '请输入收货地址',
key: 'deliveryAddress',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写收货地址',
show: true,
cur: [4, 5],
sort: 1
},
{
label: '就诊医院',
placeholder: '请输入',
key: 'hospitalId',
required: true,
type: 'select',
trigger: ['blur', 'change'],
message: '请填写就诊医院',
show: false,
cur: [1, 2, 3, 4, 5],
sort: 2
},
{
label: '上门护理',
placeholder: '请选择上门护理类型',
key: 'hospitalId',
required: true,
type: 'checkbox',
trigger: ['blur', 'change'],
message: '请选择上门护理类型',
show: false,
cur: [6],
sort: 1
},
{
label: '服务时间',
placeholder: '请选择服务时间',
key: 'serviceTime',
required: true,
type: 'date',
trigger: ['blur', 'change'],
message: '请选择服务时间',
show: false,
sort: 3,
cur: [4, 5]
},
{
label: '报告类型',
placeholder: '请选择报告类型',
key: 'reportType',
required: true,
type: 'select',
trigger: ['blur', 'change'],
message: '请选择报告类型',
show: false,
sort: 3,
cur: [4]
},
{
label: '预约就诊日期',
placeholder: '请选择就诊日期',
key: 'clinicDate',
required: true,
type: 'date',
trigger: ['blur', 'change'],
message: '请选择预约就诊日期',
show: false,
sort: 3,
cur: [1, 2]
},
{
label: '预约就诊时间',
placeholder: '请选择就诊时间',
key: 'clinicTime',
required: true,
type: 'time',
message: '请选择预约就诊时间',
trigger: ['blur', 'change'],
show: false,
sort: 4,
cur: [1, 2]
},
{
label: '上门日期',
placeholder: '请选择上门日期',
key: 'visitDate',
required: true,
type: 'date',
trigger: ['blur', 'change'],
message: '请选择上门日期',
show: false,
sort: 3,
cur: [6]
},
{
label: '上门时间',
placeholder: '请选择上门时间',
key: 'visitTime',
required: true,
type: 'time',
message: '请选择上门时间',
trigger: ['blur', 'change'],
show: false,
sort: 4,
cur: [6]
},
{
label: '就诊人',
placeholder: '请填写就诊人',
key: 'clinicPerson',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写就诊人',
show: true,
sort: 5,
cur: [1, 2]
},
{
label: '就诊人',
placeholder: '请填写就诊人',
key: 'treatmenter',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写就诊人',
show: true,
sort: 5,
cur: [5]
},
{
label: '联系人',
placeholder: '请填写联系人',
key: 'contact',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写联系人',
show: true,
sort: 6,
cur: [1, 2, 6]
},
{
label: '联系人手机号',
placeholder: '请填写联系人手机号',
key: 'contactPhone',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写联系人手机号',
show: true,
sort: 7,
cur: [1, 2, 4, 5, 6]
},
{
label: '挂号科室',
placeholder: '请选择挂号科室',
key: 'registrationDept',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请选择挂号科室',
show: true,
sort: 8,
cur: [1, 2]
}
// {
// label: '',
// placeholder: '',
// key: 'remark',
// type: 'textarea',
// required: false,
// show: true
// }
]
const dateAndSelectFormList = ref()
const formList = ref()
const model = ref({})
const typeSelItem = ref({}) //
const curType = ref(route.query.cur ? +route.query.cur : columns[0][0].id)
const typeVal = ref('')
const rules = ref({})
const agreed = ref(true)
const hospitalList = ref([])
const getPzList = async () => {
const res = await $api.fetchPzServerList()
columns[0] = res?.page?.list || []
}
const getPzHospitalList = async () => {
const res = await $api.fetchPzHospitalList()
hospitalList.value = (res?.page?.list || []).map((item) => ({
label: item.name,
id: item.id
}))
}
getPzList()
getPzHospitalList()
const onSubmit = () => {
console.log('submit', fileList.value)
}
watchEffect(() => {
const item = columns[0].find((item) => item.id === curType.value)
typeVal.value = item?.serveName
dateAndSelectFormList.value = []
formList.value = []
formItems.forEach((item) => {
const { required, key, message, trigger, type } = item
if (item.cur.includes(curType.value)) {
if (item.key === 'hospitalId') item.options = [hospitalList.value]
formList.value.push(item)
// if (['date', 'time', 'select'].includes(type)) {
if (item.type !== 'string') {
dateAndSelectFormList.value.push(item)
}
}
rules.value[item.key] = { required, message, trigger }
model.value[key] = ''
})
})
const handleShowPicker = () => {
if (current.value > 1) {
return
}
show.value = !show.value
}
const onPickerConfirm = ({ value: [val] }) => {
show.value = false
typeVal.value = val.serveName
curType.value = val.id
typeSelItem.value = val
}
//
const onSelectPickerConfirm = ({ value: [val] } = { value: [] }, item) => {
item.show = false
model.value[item.key] = val?.id
item.val = val?.label
}
const onDatePickerConfirm = ({ value }, item) => {
item.show = false
if (item.type === 'date') {
item.val = model.value[item.key] = $u.timeFormat(value, 'yyyy-mm-dd')
} else item.val = model.value[item.key] = value
}
//
const onCheckboxChange = (val, item) => {
model.value[item.key] = val
item.val = val.join(',')
}
</script>
<style lang="scss" scoped>
:deep {
.u-form-item__body {
border-bottom: 2rpx solid #f2f2f2;
padding: 32rpx 0 !important;
}
.u-form-item__body__left__content__required {
// margin-left: 6rpx;
font-size: 24rpx !important;
top: 0rpx !important;
right: -6rpx !important;
left: auto !important;
}
.u-popup__content {
border-radius: 16rpx 16rpx 0 0;
}
.u-textarea,
.u-textarea__count {
background-color: #f2f2f2 !important;
}
.u-popup__content {
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<view class="">
<view
v-for="(item, index) of exampleModel"
:key="index"
class="py-4 px-4 border-b border-gray-200 bg-white"
@click="handleInfo(item)"
>
<view class="flex items-center">
<view class="w-10 flex-none truncate text-primary-500 text-2xl">
<via-icon :name="item.icon" :deep-class="item.iconClass"></via-icon>
</view>
<view class="flex-1 w-0 leading-none">
{{ item.text }}
</view>
<view class="flex-none w-10 text-right text-gray-500">
<via-icon name="arrow-right-bold"></via-icon>
</view>
</view>
<view class="text-sm text-gray-500 pl-10">
{{ item.desc }}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
exampleModel: [
{
icon: 'm-luyou',
iconClass: '',
text: '路由中间件',
desc: '进入此页面将触发实名认证路由中间件演示',
route: {
path: '/pages/middleware/index',
},
},
],
}
},
created() {},
methods: {
handleInfo(row) {
this.$Router.push(row.route)
},
},
}
</script>
<style></style>

186
src/pages/index/home/index.vue Executable file
View File

@ -0,0 +1,186 @@
<template>
<view class="px-24rpx bg-#fff">
<u-search
v-model="searchVal"
shape="square"
margin="20rpx 0rpx 30rpx 0rpx"
height="80rpx"
searchIconSize="48rpx"
:showAction="false"
:searchIcon="$assets('/sou.png')"
placeholderColor="#666"
placeholder="搜索您需要陪诊的医院"
clearabled
/>
<u-swiper
:list="swiperList"
height="200"
radius="24rpx"
keyName="image"
indicatorMode="dot"
indicator
circular
/>
<view class="relative mt-40rpx mb-10rpx">
<u-grid :border="false" col="4">
<u-grid-item
@click="handleGridClick(listItem.path, listItem.query)"
v-for="(listItem, listIndex) in gridList"
:key="listIndex"
:name="listItem.path"
>
<u--image
width="100rpx"
height="100rpx"
:duration="100"
:src="$assets(listItem.icon)"
/>
<text class="text-28rpx !font-normal mt-12rpx mb-34rpx text-#333">{{
listItem.title
}}</text>
</u-grid-item>
</u-grid>
<button
class="absolute right-30rpx bottom-40rpx bg-transparent border-transparent z-2 w-120rpx h-150rpx"
open-type="contact"
></button>
</view>
<u--image
width="702rpx"
height="180rpx"
mode="aspectFit"
radius="16rpx"
:src="$assets('/tongzhi.png')"
:duration="300"
lazy-load
/>
<view class="mt-30rpx flex items-center h-180rpx mb-20rpx">
<u--image
:src="$assets('/tongzhi.png')"
:duration="300"
width="180rpx"
height="180rpx"
radius="24rpx"
lazy-load
/>
<view class="flex flex-1 justify-between items-center h-full">
<view class="flex flex-col justify-between h-full py-8rpx ml-20rpx">
<view class="text-34rpx">电子医保常见问题</view>
<view class="text-24rpx text-#333">全国通用看病更省心</view>
</view>
<u-icon name="arrow-right" width="48rpx" height="48rpx" />
</view>
</view>
</view>
<tabbar></tabbar>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue'
const { $Router, $api } = getCurrentInstance().proxy
const searchVal = ref('')
const swiperList = ref([
{
image: 'https://cdn.uviewui.com/uview/swiper/1.jpg',
title: '昨夜西风凋碧树'
},
{
image: 'https://cdn.uviewui.com/uview/swiper/2.jpg',
title: '行色匆匆'
},
{
image: 'https://cdn.uviewui.com/uview/swiper/3.jpg',
title: '更上一层楼'
}
])
// 线
const gridList = ref([
{
title: '半天陪诊',
icon: '/home-icon/h-icon-1.png',
path: '/pages/form/index',
query: {
cur: 1
}
},
{
title: '全天陪诊',
icon: '/home-icon/h-icon-2.png',
path: '/pages/form/index',
query: {
cur: 2
}
},
{
title: '遵享服务',
icon: '/home-icon/h-icon-3.png',
path: '/pages/form/index',
query: {
cur: ''
}
},
{
title: '代办约号',
icon: '/home-icon/h-icon-4.png',
path: '/pages/form/index',
query: {
cur: 3
}
},
{
title: '取送报告',
icon: '/home-icon/h-icon-5.png',
path: '/pages/form/index',
query: {
cur: 4
}
},
{
title: '代取送药',
icon: '/home-icon/h-icon-6.png',
path: '/pages/form/index',
query: {
cur: 5
}
},
{
title: '上门检测护理',
icon: '/home-icon/h-icon-7.png',
path: '/pages/form/index',
query: {
cur: 6
}
},
{
title: '在线咨询',
icon: '/home-icon/h-icon-8.png',
path: ''
}
])
const handleGridClick = (path, query = {}) => {
if (path) {
$Router.push({
path,
query
})
}
}
</script>
<style lang="scss" scoped>
:deep {
.u-search__content {
border-radius: 16rpx !important;
}
}
uni-page-wrapper > uni-page-body,
page {
border-top-width: 0;
background-color: #fff;
}
.button-hover {
background-color: transparent;
}
</style>

215
src/pages/index/order/index.vue Executable file
View File

@ -0,0 +1,215 @@
<template>
<view class="min-h-600rpx">
<u-sticky bgColor="#fff">
<view v-if="true" class="px-24rpx pt-20rpx relative">
<u--image
width="100%"
height="200rpx"
:fade="false"
:src="$assets('/order-bg.png')"
/>
<view
class="text-white flex items-center justify-between px-130rpx pt-20rpx absolute top-0 left-0 w-full h-full"
>
<view class="flex flex-col items-center">
<view class="text-64rpx">11</view>
<view class="text-34rpx">今日接单</view>
</view>
<view class="flex flex-col items-center">
<view class="text-64rpx">12323</view>
<view class="text-34rpx">今日收入</view>
</view>
</view>
</view>
<u-tabs
@change="handleTabChange"
class="w-full"
:list="list"
key="id"
lineWidth="30"
lineColor="#FF8CA6"
:activeStyle="{
color: '#303133',
fontWeight: 'bold',
transform: 'scale(1.05)'
}"
:inactiveStyle="{
color: '#606266',
transform: 'scale(1)'
}"
itemStyle="width: 20%; height: 100rpx;"
></u-tabs>
</u-sticky>
<view class="px-24rpx pt-20rpx">
<view
v-for="item in 2"
:key="item"
class="min-h-410rpx bg-white rounded-16rpx px-20rpx py-30rpx mb-20rpx shadow-current"
>
<view class="flex-row-between">
<text class="text-#999 text-24rpx">订单号22323232323</text>
<view
class="w-124rpx h-48rpx leading-48rpx text-center rounded-sm text-24rpx"
:class="`${stateColorList[status].bg} ${stateColorList[status].color}`"
>{{ stateColorList[status].text }}</view
>
</view>
<view class="flex-row-between text-28rpx text-#999 mt-20rpx">
<view>服务项目<text class="text-#000">服务项目</text></view>
<view class="text-#FF3939 font-bold">预计收入100</view>
</view>
<view
class="mt-10rpx mb-24rpx flex text-24rpx bg-#f2f2f2 w-full min-h-140rpx rounded-sm px-34rpx pt-20rpx pb-10rpx"
>
<view class="flex flex-col text-#666">
<text>就诊人</text>
<text class="text-#000 mt-8rpx">就诊人</text>
</view>
<view class="flex flex-col flex-1 pl-40rpx pr-20rpx text-#666">
<text>医院</text>
<text class="text-#000 mt-8rpx">医院医院医院医院医院</text>
</view>
<view class="flex flex-col text-#666">
<text>预约时间</text>
<text class="text-#000 mt-8rpx">2023/02/02</text>
<text class="text-#000">10:00</text>
</view>
</view>
<view class="flex justify-end items-center">
<view
@click="handleClick(item)"
class="btn text-30rpx px-30rpx py-14rpx ml-30rpx border-#40A0E4 text-#40A0E4"
>开始服务</view
>
<view
@click="handleClick(item)"
class="btn text-30rpx px-30rpx py-14rpx ml-30rpx border-#FF8CA6 text-#FF8CA6"
>查看详情</view
>
<view
@click="handleClick(item)"
class="btn text-30rpx px-30rpx py-14rpx ml-30rpx border-#5F5F5F text-#5F5F5F"
>取消订单</view
>
</view>
</view>
</view>
</view>
<u-popup
class="overflow-hidden"
:show="popupShow"
round="16rpx"
@close="popupShow = false"
closeable
>
<view class="min-h-800rpx pt-90rpx relative">
<view class="text-#000 absolute left-50rpx top-30rpx"
>请选择取消理由</view
>
<view
class="h-full w-full overflow-y-auto py-4 px-40rpx rounded-16rpx overflow-hidden"
>
<u-radio-group v-model="value" placement="row" @change="groupChange">
<u-radio
class="!mb-30rpx"
labelSize="16"
iconSize="20"
v-for="(item, index) in list"
activeColor="#FF8CA6"
:label="'思悠悠,恨悠悠,恨到归时方始休' + index"
:name="index"
></u-radio>
</u-radio-group>
<view class="mt-20rpx mb-20rpx text-28rpx">其他理由请填写</view>
<u--textarea
v-model="remark"
placeholder=""
maxlength="300"
count
:autoHeight="false"
/>
<view
class="text-white bg-#FF8CA6 rounded-24rpx w-full mx-auto mt-30rpx h-80rpx text-center leading-80rpx hover:shadow-md"
>提交</view
>
</view>
</view>
</u-popup>
<button-contact :class="`!bottom-16%`" />
<tabbar />
</template>
<script setup>
//
const list = ref([
{ name: '全部', id: 0 },
{ name: '待服务', id: 1 },
{ name: '进行中', id: 2 },
{ name: '已完成', id: 3 },
{ name: '已取消', id: 4 }
])
const popupShow = ref(false)
const value = ref(0)
const remark = ref('')
const status = ref(Math.floor(Math.random() * 4))
const stateColorList = [
{
text: '待服务',
color: 'text-#01BB88',
bg: 'bg-#d4f0e7'
},
{
text: '进行中',
color: 'text-#FEA60A',
bg: 'bg-#fceed1'
},
{
text: '已完成',
color: 'text-#40A0E4',
bg: 'bg-#dceaf8'
},
{
text: '已取消',
color: 'text-#666',
bg: 'bg-#ccc'
}
]
const handleTabChange = (item) => {
status.value = item.id - 1 < 0 ? 0 : item.id - 1
}
const handleClick = (item) => {
console.log('🚀 ~ file: index.vue:133 ~ item:', item)
popupShow.value = true
}
const groupChange = (value) => {
console.log('🚀 ~ file: index.vue:133 ~ value:', value)
}
</script>
<style lang="scss" scoped>
page {
background-color: #f2f2f2;
}
.flex-row-center {
@apply flex flex-row justify-center items-center;
}
.flex-row-between {
@apply flex flex-row justify-between items-center;
}
.btn {
@apply rounded-md border-solid border-1;
}
:deep {
.u-popup__content {
border-radius: 16rpx 16rpx 0 0;
}
.u-textarea,
.u-textarea__count {
background-color: #f2f2f2 !important;
}
.u-popup__content {
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,210 @@
<template>
<view class="px-44rpx">
<view class="flex items-center pt-80rpx pl-16rpx" @click="handleLogin">
<view class="overflow-hidden rounded-full w-20 h-20">
<image
:src="userInfo.avatar ? userInfo.avatar : defaultAvatar"
alt=""
class="w-full h-full"
/>
</view>
<view class="ml-4">
<block v-if="isLogin">
<view class="text-34rpx"> {{ userInfo.nickName }}</view>
<view class="text-24rpx text-primary-500 mt-20rpx flex items-center">
<image
class="w-28rpx mr-4rpx"
:src="$assets('/personal/edit.jpg')"
mode="widthFix"
></image>
<text>编辑资料</text></view
>
</block>
<view v-else claas="text-34rpx text-primary">登录</view>
</view>
</view>
<view class="flex items-center pl-16rpx my-40rpx">
<view class="flex flex-1 flex-col">
<text class="text-34rpx">10</text>
<text class="text-28rpx text-#999">累计接单</text>
</view>
<view class="flex flex-1 flex-col">
<text class="text-34rpx">10</text>
<text class="text-28rpx text-#999">累计收入</text>
</view>
<view class="flex flex-1 flex-col">
<text class="text-34rpx">10</text>
<text class="text-28rpx text-#999">可提现</text>
</view>
<view
@click="showWithdraw = true"
class="w-120rpx text-primary-500 text-30rpx"
>
立即提现
</view>
</view>
<view class="relative">
<view
v-for="(item, index) of listModel"
:key="index"
class="flex items-center py-4"
@click="handleInfo(item)"
>
<!-- <view class="w-10 flex-none truncate text-primary-500 text-2xl">
<via-icon :name="item.icon"></via-icon>
</view> -->
<view class="flex-1 flex items-center">
<image :src="$assets(item.icon)" alt="" class="w-42rpx h-42rpx" />
<text class="ml-40rpx">{{ item.text }}</text>
</view>
<view class="flex-none w-10 text-right text-gray-500">
<via-icon name="arrow-right-bold"></via-icon>
</view>
</view>
<button
class="absolute bottom-224rpx bg-transparent border-transparent z-2 w-660rpx h-90rpx"
open-type="contact"
></button>
</view>
<u-modal
v-model:show="showWithdraw"
title="提现"
@cancel="showWithdraw = false"
@confirm="handleWithdraw"
confirmText="立即提现"
confirmColor="#FF8CA6"
:show-cancel-button="true"
:zoom="false"
closeOnClickOverlay
>
<view class="w-full">
<view class="flex items-center justify-between mb-36rpx">
<view class="">可提现余额999</view>
<view
class="flex justify-center items-center border-primary-500 border-1px w-150rpx h-54rpx rounded-12rpx text-primary"
>全部提现</view
>
</view>
<view class="bg-#f2f2f2 rounded-15rpx h-80rpx">
<u-input
:customStyle="{ height: '80rpx' }"
v-model="amount"
type="number"
placeholder="请输入金额"
border="0"
fontSize="28rpx"
:focus="false"
>
<template #prefix>
<u-text
text="金额"
margin="0 5px 0 0"
type="tips"
color="#000000"
></u-text>
</template>
</u-input>
</view>
<view class="mt-12rpx text-24rpx text-#c1c1c1">
提现手续费每笔X元
</view>
</view>
</u-modal>
</view>
<tabbar />
</template>
<script setup>
import defaultAvatar from '@/static/avatar.png'
const instance = getCurrentInstance()
const { $api, $store, $Router, $loading } = instance.proxy
const userStore = $store.user
const isLogin = computed(() => !!userStore.token)
const userInfo = computed(() => userStore.userInfo)
const showWithdraw = ref(false)
const amount = ref('')
const listModel = [
{
icon: '/personal/person.png',
text: '就诊人管理',
path: ''
},
{
icon: '/personal/shouru.png',
text: '收入详情',
path: '/pages/other/income-details'
},
{
icon: '/personal/qiehuan.png',
text: '切换身份',
path: '/pages/other/switch'
},
{
icon: '/personal/lianxi.png',
text: '联系客服',
path: '/pages/contact/index'
},
{
icon: '/personal/guanyu.png',
text: '关于我们',
path: '/pages/contact/index'
},
{
icon: '/personal/lianxi.png',
text: '帮助中心',
path: '/pages/contact/index'
}
]
function handleInfo(item) {
$Router.push({
path: item.path,
query: item.query || {}
})
}
//
const handleWithdraw = () => {}
async function handleLogin() {
if (!isLogin.value) {
$loading(true)
userStore.login().then((user) => {
$loading(false)
!user.id &&
$Router.push({
path: '/pages/other/edit-userinfo'
})
})
} else
$Router.push({
path: '/pages/other/edit-userinfo'
})
}
// await userStore.logout()
// await $toast('退', { type: 'success' })
const getUserInfo = async () => {
if (isLogin.value) {
$loading(true)
const res = await $api.getUser()
if (res.success) {
$loading(false)
userStore.setUserInfo(res.wxUser || {})
} else userStore.logout()
} else userStore.removeUserInfo()
}
getUserInfo()
</script>
<style lang="scss" scoped>
page {
background-color: #fff;
}
.button-hover {
background-color: transparent;
}
</style>

55
src/pages/login/index.vue Executable file
View File

@ -0,0 +1,55 @@
<template>
<div class="h-full pt-200rpx bg-white px-50rpx">
<u-button type="primary" shape="circle" ripple @click="handleLogin">
微信快捷登录
</u-button>
</div>
</template>
<script setup>
// import { login } from '@uni-helper/uni-promises'
const instance = getCurrentInstance()
const { $toast, $api, $store } = instance.proxy
const handleLogin = () => {
$store.user.login()
}
function getUserInfo() {
uni.getUserInfo({
provider: 'weixin',
success: function (res) {
//
console.log('用户信息:', res.userInfo)
//
sendUserInfoToServer(res.userInfo)
},
fail: function (err) {
//
console.error('获取用户信息失败:', err)
//
if (err.errMsg === 'getUserInfo:fail auth deny') {
openAuthSetting()
}
}
})
}
function openAuthSetting() {
uni.openSetting({
success: function (res) {
if (res.authSetting['scope.userInfo']) {
// `uni.getUserInfo`
getUserInfo()
}
}
})
}
function sendUserInfoToServer(userInfo) {
console.log('🚀 ~ file: index.vue:61 ~ userInfo:', userInfo)
// code
// openid session_key
//
}
</script>
<style></style>

110
src/pages/login/phone/index.vue Executable file
View File

@ -0,0 +1,110 @@
<template>
<view class="bg-white h-full overflow-hidden pt-24">
<!-- <view class="mt-8">
<image src="/static/logo.png" alt="" class="w-20 h-20 block mx-auto" />
<view class="text-2xl text-center mt-4 font-bold">
{{ appName }}
</view>
</view> -->
<view class="px-4 mt-18">
<u-button type="primary" shape="circle" ripple @click="handleLogin">
手机号快捷登录
</u-button>
<view class="text-center mt-4 flex items-center justify-center">
<checkbox :checked="agreed" class=""> </checkbox>
<text>
我已阅读并同意
<text
class="text-primary active:text-primary-700"
@click.stop="handleAgree"
>
产品服务协议
</text>
</text>
</view>
</view>
</view>
</template>
<script>
import { version } from '/package.json'
import { appName } from '@/configs/index'
export default {
data() {
return {
appName,
version,
agreed: false,
show: false,
tel: 123123123123,
name: '张三',
value: 2,
title: 'xxxsxs'
}
},
onShow() {},
methods: {
onChange(value) {
this.value = value.detail
this.title += this.value
// console.log('onChange', value.detail)
},
onEdit() {
console.log('edit')
},
handleInfo() {
this.$Router.push({
path: '/webview',
query: {
title: 'viarotel',
src: 'https://viarotel.eu.org/'
}
})
},
handleAgree() {
this.$Router.push({ path: '/pages/statement/index' })
},
async handleLogin() {
if (!this.agreed) {
try {
await this.$dialog('是否已阅读并同意《产品服务协议》?', {
showCancelButton: true,
confirmButtonText: '同意',
cancelButtonText: '取消'
})
this.agreed = true
} catch (error) {
console.log('error', error)
return
}
}
const params = {}
await this.$store.user.login(params)
await this.$toast('登录成功', { type: 'success' })
this.handleSuccess()
},
async handleSuccess() {
const user = this.$store.user
try {
await user.getUserInfo()
const redirect = this.$Route.query.redirect
console.log('redirect', redirect)
if (redirect) {
this.$Router.replaceAll(JSON.parse(redirect))
} else {
this.$Router.replaceAll('/')
}
} catch (error) {
console.warn('error', error)
}
}
}
}
</script>
<style></style>

11
src/pages/middleware/index.vue Executable file
View File

@ -0,0 +1,11 @@
<template>
<view class="h-full flex justify-center items-center relative -mt-12">
进入此页面说明你已经实名认证成功
</view>
</template>
<script>
export default {}
</script>
<style></style>

View File

@ -0,0 +1,116 @@
<template>
<view class="px-40rpx">
<view
class="py-20rpx flex justify-between items-center border-b-#E5E5E5 border-b-1px"
>
<view>头像</view>
<view class="overflow-hidden rounded-full w-100rpx h-100rpx">
<choose-avatar
:defaultValue="userInfo.avatar"
@changeAvatar="(url) => (userInfo.avatar = url)"
/>
</view>
</view>
<view
class="relative h-130rpx flex justify-between items-center border-b-#E5E5E5 border-b-1px"
>
<view
>昵称 <text class="text-#999 text-24rpx">建议改为真实姓名</text></view
>
<u-input
v-model="userInfo.nickName"
type="nickname"
placeholder=""
inputAlign="right"
maxlength="20"
border="0"
></u-input>
</view>
<view
class="relative h-130rpx flex justify-between items-center border-b-#E5E5E5 border-b-1px"
>
<view>手机号</view>
<button
hover-class="none"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
class="absolute right-0 z-10 overflow-hidden !hover:bg-transparent !active:bg-transparent w-80% h-full rounded-md"
></button>
<u-input
v-model="userInfo.phone"
placeholder=""
type="number"
inputAlign="right"
maxlength="20"
border="0"
read-only
></u-input>
</view>
<view
@click="show = true"
class="h-130rpx flex justify-between items-center border-b-#E5E5E5 border-b-1px"
>
<view>性别</view>
<view>{{ columns[0][userInfo.gender] }}</view>
</view>
<u-picker
:show="show"
:columns="columns"
confirmColor="#FF8CA6"
@confirm="hadleConfirm"
@cancel="show = false"
@close="show = false"
closeOnClickOverlay
></u-picker>
<view
class="fixed bottom-8 left-0 right-0 text-white pt-12rpx border-t-1px border-solid border-#E5E5E5"
><view
@click="$u.throttle(handleSave, 800)"
class="bg-#FF8CA6 rounded-24rpx w-660rpx h-80rpx text-center leading-80rpx hover:shadow-md mx-auto"
>修改信息</view
></view
>
</view>
</template>
<script setup>
const instance = getCurrentInstance()
const { $api, $store, $Router, $u } = instance.proxy
const show = ref(false)
const columns = ref([['男', '女']])
const userInfo = ref($store.user.userInfo)
const isEdit = ref($Router.query?.edit || false)
const hadleConfirm = ({ indexs: [index] }) => {
userInfo.value.gender = index
show.value = false
}
const getPhoneNumber = async (e) => {
const { detail } = e
if (detail.errMsg === 'getPhoneNumber:ok') {
const res = await $api.getPhone({ code: detail.code })
if (res.success) {
userInfo.value.phone = res.msg
}
}
}
const handleSave = async () => {
const res = await $api.userSave(userInfo.value)
if (res.success) {
$u.toast('保存成功')
$store.user.setUserInfo(userInfo.value)
setTimeout(() => {
$Router.push({
path: '/personal'
})
}, 500)
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #fff;
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<view>
<view class="flex items-center justify-between px-50rpx pt-20rpx pb-40rpx">
<view
class="flex justify-center items-center w-150rpx h-64rpx rounded-36rpx bg-primary text-white"
>
全部
</view>
<view
@click="datetimeShow = true"
class="flex justify-center items-center w-250rpx h-64rpx rounded-36rpx bg-#f2f2f2 text-#000"
>
{{ $u.timeFormat(datetime, 'yyyy-mm') }}
</view>
</view>
<view class="px-34rpx">
<view
class="flex justify-between pb-20rpx border-b-#E5E5E5 border-b-1px mb-20rpx"
v-for="i in 10"
:key="i"
>
<view class="flex flex-col text-28rpx">
<text class="">陪诊sour</text>
<text class="text-24rpx text-#999">2023年3月1日</text>
</view>
<view class="text-#FF3939">+100</view>
<!-- <u-text
text="+100"
mode="price"
margin="0 5px 0 0"
type="tips"
color="#FF3939"
></u-text> -->
</view>
</view>
<u-datetime-picker
:show="datetimeShow"
v-model="datetime"
@confirm="handleDatePickerConfirm"
@cancel="datetimeShow = false"
:minDate="new Date('2023-01-01').getTime()"
:maxDate="new Date('2030-12-31').getTime()"
mode="year-month"
confirmColor="#FF8CA6"
@close="datetimeShow = false"
closeOnClickOverlay
></u-datetime-picker>
</view>
</template>
<script setup>
const instance = getCurrentInstance()
const { $u } = instance.proxy
const datetimeShow = ref(false)
const datetime = ref(new Date().getTime())
const handleDatePickerConfirm = ({ value }) => {
datetime.value = $u.timeFormat(value, 'yyyy-mm')
datetimeShow.value = false
}
</script>
<style lang="scss" scoped>
page {
background-color: #fff;
}
</style>

267
src/pages/other/switch.vue Normal file
View File

@ -0,0 +1,267 @@
<template>
<view class="bg-white px-34rpx">
<view class="h-full pb-160rpx">
<u--form
labelPosition="left"
:model="model"
:rules="rules"
ref="formRef"
borderBottom
errorType="toast"
labelWidth="auto"
>
<u-form-item
v-for="item in formList"
:key="item.key"
:required="item.required"
:label="item.label"
:prop="item.key"
>
<u--input
v-if="item.type === 'string'"
:type="item.key === 'age' ? 'number' : 'text'"
v-model="model[item.key]"
border="none"
inputAlign="right"
maxlength="300"
:placeholder="item.placeholder"
:readonly="false"
/>
<view class="w-full" v-else-if="item.type === 'radio'">
<u-radio-group
class="!justify-end"
v-model="model[item.key]"
activeColor="#FF8CA6"
shape="square"
placement="row"
>
<u-radio :name="0" label="男"></u-radio>
<u-radio :name="1" label="女" class="ml-50rpx"></u-radio>
</u-radio-group>
</view>
<view v-else class="w-full text-right u-line-1 pl-2 text-#C0C4CC"
>{{ item.placeholder }}
</view>
</u-form-item>
</u--form>
<u--textarea
v-model="model.remark"
placeholder=""
maxlength="300"
count
:autoHeight="false"
/>
<view class="mt-32rpx mb-20rpx text-28rpx">
<view class=""
>上传身份证
<text
class="u-form-item__body__left__content__required text-#f56c6c ml-[-4rpx]"
>*</text
>
</view>
<view class="flex justify-between mb-32rpx">
<view
@click="handleChooseImage"
class="relative w-330rpx bg-#f2f2f2 rounded-md px-52rpx pt-30rpx pb-20rpx"
>
<block v-if="!fileList[0]">
<image class="w-full h-146rpx" :src="$assets('/zheng.png')" />
<view class="text-24rpx mt-10rpx text-#999 w-full text-center"
>上传身份证人像面</view
>
</block>
</view>
<view
@click="handleChooseImage"
class="relative w-330rpx bg-#f2f2f2 rounded-md px-52rpx pt-30rpx pb-20rpx"
>
<image class="w-full h-146rpx" :src="$assets('/fan.png')" />
<view class="text-24rpx mt-10rpx text-#999 w-full text-center"
>上传身份证国徽面</view
>
</view>
</view>
<text class="text-24rpx text-#999"
>备注申请表提交后会有工作人员主动联系您请耐心等待</text
>
<agreed-comp v-model:agreed="agreed" />
</view>
<view
class="fixed bg-#fff bottom-0 pb-8 left-0 right-0 text-white pt-12rpx border-t-1px border-solid border-#E5E5E5"
><view
@click="handleSubmit"
class="bg-#FF8CA6 rounded-24rpx w-660rpx h-80rpx text-center leading-80rpx hover:shadow-md mx-auto"
>提交申请</view
></view
>
</view>
</view>
</template>
<script setup>
import { chooseImage } from '@uni-helper/uni-promises'
const instance = getCurrentInstance()
const { $api, $toast, $store } = instance.proxy
const userInfo = computed(() => $store.user.userInfo)
const fileList = ref([])
const agreed = ref(false)
const formRef = ref(null)
const formList = ref([])
const model = ref({})
const rules = ref({})
//
const formItems = [
{
label: '姓名',
placeholder: '请输入姓名',
key: 'name',
keyName: 'nickName',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请填写姓名'
},
{
label: '性别',
placeholder: '请选择性别',
key: 'sex',
keyName: 'gender',
required: true,
type: 'radio',
trigger: ['blur', 'change'],
message: '请选择性别'
},
{
label: '年龄',
placeholder: '请输入年龄',
key: 'age',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请输入年龄'
},
{
label: '手机号',
placeholder: '请输入手机号',
key: 'phone',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请输入手机号'
},
{
label: '微信号',
placeholder: '请输入微信号',
key: 'weNumber',
required: true,
type: 'string',
trigger: ['blur', 'change'],
message: '请输入微信号'
},
{
label: '常驻地点',
placeholder: '请输入常驻地点',
key: 'address',
type: 'string',
required: true,
trigger: ['blur', 'change'],
message: '请填写常驻地点'
},
{
label: '个人介绍',
placeholder: '如可服务时间、可服务范围',
key: 'intro',
type: 'textarea',
required: true,
trigger: ['blur', 'change'],
message: '请填写个人介绍'
}
// {
// label: '',
// placeholder: '',
// key: 'idCard',
// type: 'string',
// required: true,
// trigger: ['blur', 'change'],
// message: ''
// }
]
const init = () => {
formItems.forEach((item) => {
const { required, key, keyName, message, trigger } = item
rules.value[item.key] = [{ required, message, trigger }]
formList.value.push(item)
model.value[key] = userInfo.value[keyName || key] ?? null
})
}
const handleChooseImage = async () => {
let filePath = ''
try {
const ret = await chooseImage({
count: 1
})
filePath = ret.tempFilePaths[0]
} catch (error) {
console.warn('handleChooseImage.error', error)
return
}
if (!filePath) {
return
}
const res = await $api.uploadFile({
name: 'file',
filePath
})
if (res.success) {
console.log('res.data', res)
}
}
const handleSubmit = () => {
formRef.value?.validate().then(() => {
$toast('提交成功')
})
}
init()
</script>
<style lang="scss" scoped>
:deep {
.u-form-item__body {
border-bottom: 2rpx solid #f2f2f2;
padding: 32rpx 0 !important;
}
.u-form-item__body__left__content__required {
// margin-left: 6rpx;
font-size: 24rpx !important;
top: 0rpx !important;
right: -6rpx !important;
left: auto !important;
}
.u-popup__content {
border-radius: 16rpx 16rpx 0 0;
}
.u-textarea,
.u-textarea__count {
background-color: #f2f2f2 !important;
}
.u-popup__content {
overflow: hidden;
}
.u-upload__button {
width: 100% !important;
height: 100% !important;
background-color: transparent;
}
.u-radio-group {
width: 230rpx;
margin-left: auto;
justify-content: space-between;
}
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<view class="w-full">
<view
class="w-full"
@click="handleClick"
>
<slot :value="joinValue" />
</view>
<!-- :default-index="defaultIndex" -->
<u-picker
ref="uPicker"
v-bind="$attrs"
:show="show"
:columns="options"
close-on-click-overlay
:key-name="labelKey"
@cancel="onCancel"
@confirm="onConfirm"
@change="onChange"
@close="onClose"
></u-picker>
</view>
</template>
<script>
import { groupBy } from 'lodash-es'
import { tree2List } from '@/utils/treeSimple'
export default {
components: {},
props: {
modelValue: {
type: Array,
default: () => [],
},
},
emits: ['update:model-value'],
data() {
return {
show: false,
flatData: [],
options: [],
labelKey: 'deptName',
valueKey: 'qywechatDeptId',
}
},
computed: {
joinValue() {
return this.modelValue
.map(
item => item?.deptName
|| this.$showDictLabel(this.flatData, item, {
labelKey: this.labelKey,
valueKey: this.valueKey,
}),
)
.join('-')
},
},
created() {
this.getAreaData()
},
methods: {
async getAreaData() {
const res = await this.$api.wxdepartTree()
if (res.success) {
const data = res.data || []
this.flatData = tree2List(data, {
key: 'id',
parentKey: 'parentId',
children: 'childrenList',
})
console.log('flatData', this.flatData)
const {
1: county,
39: community,
...villageMap
} = groupBy(this.flatData, 'parentId')
const village = Object.values(villageMap).flat()
// console.log('county', county)
// console.log('community', community)
// console.log('village', village)
const filterCommunity = community.filter(
item => item.parentId === county[0]?.qywechatDeptId,
)
// console.log('filterCommunity', filterCommunity)
const filterVillage = village.filter(
item => item.parentId === filterCommunity[0]?.qywechatDeptId,
)
// console.log('filterVillage', filterVillage)
this.options = [county, filterCommunity, filterVillage]
}
},
onClose() {
this.show = false
},
onConfirm({ value }) {
console.log('onConfirm.value', value)
this.$emit('update:model-value', value)
this.show = false
},
onCancel() {
console.log('onCancel')
this.$emit('update:model-value', [])
this.show = false
},
handleClick() {
this.show = true
},
onChange(event) {
console.log('onChange.event', event)
const { columnIndex, value } = event
const current = value[columnIndex]
const subColumn = this.flatData.find(item => item.id === current.id)?.childrenList
|| []
// console.log('subColumn', subColumn)
if (columnIndex + 1 >= this.options.length) {
return
}
this.$refs.uPicker.setColumnValues(columnIndex + 1, subColumn)
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,219 @@
<template>
<view class="h-full px-4 flex flex-col">
<view class="font-bold pt-8 pb-4 flex-none">
个人基本信息
</view>
<view class="flex-1 h-0">
<u--form
ref="uForm"
label-position="left"
:model="formData"
:label-width="90"
>
<u-form-item label="性别" prop="userSex" border-bottom>
<u-radio-group
v-model="formData.userSex"
placement="row"
v-bind="{
activeColor: 'rgba(var(--color-primary), 1)',
}"
disabled
>
<view>
<u-radio label="男" :name="1"></u-radio>
</view>
<view class="pl-4">
<u-radio label="女" :name="2"></u-radio>
</view>
</u-radio-group>
</u-form-item>
<u-form-item label="户籍信息" prop="nativePlace" border-bottom>
<AreaPickerTrigger v-model="formData.nativePlace">
<template #default="{ value }">
<view class="flex items-center w-full">
<view class="flex-1 w-0">
<u--input
:model-value="value"
placeholder="请选择户籍信息"
border="none"
readonly
></u--input>
</view>
<view class="flex-none">
<u-icon name="arrow-right"></u-icon>
</view>
</view>
</template>
</AreaPickerTrigger>
</u-form-item>
<u-form-item label="户主" prop="realName" border-bottom>
<u--input
v-model="formData.realName"
placeholder="请输入户主姓名"
border="none"
></u--input>
</u-form-item>
<u-form-item label="手机号" prop="userPhone" border-bottom>
<view class="flex">
<u--input
v-model="formData.userPhone"
placeholder="请输入手机号"
border="none"
></u--input>
<view class="flex-none pl-1">
<u-button
type="primary"
size="small"
open-type="getPhoneNumber"
readonly
@getphonenumber="getPhoneNumber"
>
获取手机号
</u-button>
</view>
</view>
</u-form-item>
</u--form>
</view>
<view class="flex-none pb-4">
<u-button
type="primary"
text="确定"
shape="circle"
size="large"
@click="handleSubmit"
></u-button>
</view>
</view>
</template>
<script>
import AreaPickerTrigger from './AreaPicker/trigger.vue'
import { getSexText, sleep } from '@/utils'
export default {
components: {
AreaPickerTrigger,
},
props: {
handler: {
type: Function,
default: () => () => {},
},
params: {
type: Object,
default: () => ({}),
},
},
data() {
return {
formData: {
userSex: 1,
nativePlace: [],
realName: '',
userPhone: '',
},
formRules: {
nativePlace: {
validator: (rule, value, callback) => !!value.length,
message: '户籍信息不能为空',
trigger: ['blur'],
},
realName: {
required: true,
message: '户主姓名不能为空',
trigger: ['blur'],
},
userPhone: [
{
required: true,
message: '手机号不能为空',
trigger: ['blur'],
},
{
validator: (rule, value, callback) => uni.$u.test.mobile(value),
message: '手机号格式错误',
trigger: ['blur'],
},
],
},
}
},
computed: {
userInfo() {
return this.$store.user.userInfo
},
},
created() {
this.formData = {
...this.formData,
realName: this.params.realName || this.userInfo.realName,
userSex: this.getUserSex(),
nativePlace: this.userInfo.nativePlace
? [
this.userInfo.countyId,
this.userInfo.townId,
this.userInfo.villageId,
]
: [],
userPhone: this.userInfo.userPhone,
}
},
mounted() {
this.$refs.uForm.setRules(this.formRules)
},
methods: {
getUserSex() {
const idcardNo = this.params.idcardNo || this.userInfo.idcardNo
const value = getSexText(idcardNo) === '男' ? 1 : 2
return value
},
async getPhoneNumber(event) {
console.log('getPhoneNumber.event', event)
const params = {
wxCode: event.detail.code,
}
const res = await this.$api.phoneNumber(params)
if (res.success) {
this.formData.userPhone = res.data.phoneNumber
}
},
async handleSubmit() {
try {
await this.$refs.uForm.validate()
}
catch (error) {
console.warn('error', error)
return
}
const [county, town, village] = this.formData.nativePlace
const params = {
...this.formData,
nativePlace: this.formData.nativePlace
.map(item => item.deptName)
.join(''),
countyId: county.qywechatDeptId,
townId: town.qywechatDeptId,
villageId: village.qywechatDeptId,
}
console.log('params', params)
this.$loading(true)
await sleep()
this.$loading(false)
this.handler({
active: 2,
params,
})
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,179 @@
<template>
<view class="h-full pt-8 px-4 flex flex-col overflow-hidden">
<view class="overflow-auto flex-1 h-0">
<view
v-for="(item, index) of formModel"
:key="index"
class="pb-6"
>
<view class="font-bold">
{{ item.stepText }}请拍摄/上传您的身份证{{ item.keyword }}
</view>
<view class="text-gray-500 text-sm pt-2">
请保持身份证完整清晰
</view>
<view
class="relative mt-4 overflow-hidden h-[177px]"
@click="handleChooseImage(item.prop)"
>
<view
v-if="formData[item.prop]"
class="flex items-center justify-center absolute inset-0 h-full"
>
<image
:src="formData[item.prop]"
class="h-full"
mode="heightFix"
/>
</view>
<view
v-else
class="h-full"
>
<image
:src="item.imgURL"
mode="widthFix"
class="w-full"
/>
<view class="position-center text-center">
<image
:src="$assets('/images/realname/icon-camera.png')"
class="h-14"
mode="heightFix"
/>
<view class="pt-2">
拍摄/上传身份证{{ item.keyword }}
</view>
</view>
</view>
</view>
</view>
</view>
<view class="flex-none pb-4">
<u-button
type="primary"
size="large"
shape="circle"
text="确定"
@click="handleSubmit"
>
</u-button>
</view>
</view>
</template>
<script>
import { chooseImage } from '@uni-helper/uni-promises'
import { getFileBaseURL } from '@/configs/request'
export default {
props: {
handler: {
type: Function,
default: () => () => {},
},
params: {
type: Object,
default: () => ({}),
},
},
data() {
return {
formData: {
idCardFontUrl: '',
idCardBackUrl: '',
},
}
},
computed: {
formModel() {
return [
{
stepText: '第一步',
keyword: '人像面',
imgURL: this.$assets('/images/realname/bg-certificate-portrait.png'),
prop: 'idCardFontUrl',
},
{
stepText: '第二步',
keyword: '国徽面',
imgURL: this.$assets('/images/realname/bg-certificate-badge.png'),
prop: 'idCardBackUrl',
},
]
},
userInfo() {
return this.$store.user.userInfo
},
},
created() {
this.formData = {
...this.formData,
idCardFontUrl: this.userInfo.idCardFontUrl,
idCardBackUrl: this.userInfo.idCardBackUrl,
}
},
methods: {
async handleSubmit() {
console.log('this.formData', this.formData)
const verifyed = Object.values(this.formData).every(value => !!value)
if (!verifyed) {
this.$toast('请先上传身份证照片')
return
}
const params = {
...this.params,
...this.formData,
}
this.$loading(true)
const res = await this.$api.wxrealnameAuth(params)
this.$loading(false)
if (res.success) {
this.handler({
active: 3,
params: this.formData,
})
return
}
this.$toast('实名认证失败, 请联系客服')
},
async handleChooseImage(prop) {
let filePath = ''
try {
const ret = await chooseImage({
count: 1,
})
filePath = ret.tempFilePaths[0]
console.log('chooseImage.ret', ret)
}
catch (error) {
console.warn('handleChooseImage.error', error)
return
}
if (!filePath) {
return
}
const res = await this.$api.uploadFile({
name: 'file',
filePath,
})
if (res.success) {
const data = `${getFileBaseURL()}/${res.data}`
// console.log('data', data)
this.formData[prop] = data
}
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,154 @@
<template>
<view class="h-full px-4 flex flex-col">
<view class="font-bold pt-8 pb-4 flex-none">
个人身份信息
</view>
<view class="flex-1 h-0">
<u-form
ref="uForm"
:model="formData"
:label-width="90"
:border-bottom="true"
label-position="left"
>
<u-form-item label="姓名" prop="realName" :border-bottom="true">
<u-input
v-model="formData.realName"
placeholder="请输入姓名"
border="none"
></u-input>
</u-form-item>
<u-form-item label="身份证号" prop="idcardNo" :border-bottom="true">
<u-input
v-model="formData.idcardNo"
placeholder="请输入身份证号"
border="none"
></u-input>
</u-form-item>
</u-form>
<view class="text-red-500 text-xs pt-4">
注意: 此处仅作演示姓名和身份证可以不真实但格式需正确
</view>
</view>
<view class="flex-none pb-4">
<u-button
type="primary"
text="确定"
shape="circle"
size="large"
@click="handleSubmit"
></u-button>
</view>
</view>
</template>
<script>
export default {
props: {
handler: {
type: Function,
default: () => () => {},
},
},
data() {
return {
formData: {
realName: '',
idcardNo: '',
},
formRules: {
realName: {
type: 'string',
required: true,
message: '姓名不能为空',
trigger: ['blur'],
},
idcardNo: [
{
type: 'string',
required: true,
message: '身份证号不能为空',
trigger: ['blur'],
},
{
validator: (rule, value, callback) => uni.$u.test.idCard(value),
message: '身份证号格式错误',
trigger: ['blur'],
},
],
},
}
},
computed: {
userInfo() {
return this.$store.user.userInfo
},
},
created() {
this.formData = {
...this.formData,
realName: this.userInfo.realName,
idcardNo: this.userInfo.idcardNo,
}
},
mounted() {
this.$refs.uForm.setRules(this.formRules)
},
methods: {
async handleSubmit() {
try {
await this.$refs.uForm.validate()
}
catch (error) {
console.warn('error', error)
return
}
const params = {
...this.formData,
}
this.$loading(true)
const res = await this.$api.checkUserinfo(params)
this.$loading(false)
if (res.success) {
const imported = res.data
if (imported) {
this.handleBind()
return
}
this.handler({
active: 1,
params: this.formData,
})
}
},
async handleBind() {
const params = {
...this.formData,
}
this.$loading(true)
const res = await this.$api.bindUserinfo(params)
this.$loading(false)
if (res.success) {
const binded = res.data
if (binded) {
await this.$toast('该用户信息已存在, 自动绑定成功')
this.handler({
active: 3,
params: {
...this.formData,
binded,
},
})
return
}
this.$toast('绑定失败, 请联系客服')
}
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,50 @@
<template>
<view class="h-full flex flex-col overflow-hidden">
<view
class="flex-1 h-0 overflow-auto flex flex-col items-center justify-center"
>
<view class="">
<u-icon
name="checkmark-circle-fill"
color="#07c160"
size="160rpx"
></u-icon>
</view>
<view class="pt-4 pl-2">
实名认证已上传审核中
</view>
</view>
<view class="flex-none pb-4 px-4">
<u-button
type="primary"
text="确定"
shape="circle"
size="large"
@click="handleSubmit"
></u-button>
</view>
</view>
</template>
<script>
export default {
props: {
handler: {
type: Function,
default: () => () => {},
},
params: {
type: Object,
default: () => ({}),
},
},
methods: {
async handleSubmit() {
this.$store.user.userInfo.realStatus = 3
this.$Router.replaceAll('/')
},
},
}
</script>
<style></style>

135
src/pages/realname/index.vue Executable file
View File

@ -0,0 +1,135 @@
<template>
<view class="h-full flex flex-col overflow-hidden">
<view
class="px-4 rounded-lg bg-white shadow-lg shadow-primary-100 mx-4 mt-4 py-4 flex-none"
>
<view class="h-6 flex items-center">
<u-line-progress
v-bind="{
activeColor: 'rgba(var(--color-primary-500), 1)',
inactiveColor: 'rgba(var(--color-primary-50), 1)',
percentage: currentProgress.value,
}"
>
<view
class="w-6 h-6 bg-primary-200 rounded-full flex items-center justify-center flex-none"
>
<view class="w-3 h-3 bg-primary-500 rounded-full flex-none"></view>
</view>
</u-line-progress>
</view>
<view class="flex items-center justify-between text-sm">
<view class="">
个人信息
</view>
<view class="">
身份证上传
</view>
<view class="">
提交认证
</view>
</view>
</view>
<view class="flex-1 h-0 overflow-auto">
<IdentityInfo
v-if="currentProgress.component === 'IdentityInfo'"
v-bind="{
...currentProgress,
handler,
params: formData,
}"
/>
<BaseInfo
v-if="currentProgress.component === 'BaseInfo'"
v-bind="{
...currentProgress,
handler,
params: formData,
}"
/>
<Certificate
v-if="currentProgress.component === 'Certificate'"
v-bind="{
...currentProgress,
handler,
params: formData,
}"
/>
<SuccessInfo
v-if="currentProgress.component === 'SuccessInfo'"
v-bind="{
...currentProgress,
handler,
params: formData,
}"
/>
</view>
</view>
</template>
<script>
import IdentityInfo from './components/Identity/index.vue'
import BaseInfo from './components/Base/index.vue'
import Certificate from './components/Certificate/index.vue'
import SuccessInfo from './components/Success/index.vue'
export default {
components: {
IdentityInfo,
BaseInfo,
Certificate,
SuccessInfo,
},
data() {
return {
progressActive: 0,
progressModel: [
{
label: '个人身份信息',
value: 7,
component: 'IdentityInfo',
},
{
label: '个人基本信息',
value: 33,
component: 'BaseInfo',
},
{
label: '身份证上传',
value: 54,
component: 'Certificate',
},
{
label: '认证完成',
value: 100,
component: 'SuccessInfo',
},
],
formData: {},
}
},
computed: {
currentProgress() {
return this.progressModel[this.progressActive] || {}
},
},
methods: {
handler({ active = 0, params = {} } = {}) {
this.handleActive(active)
this.formData = {
...this.formData,
...params,
}
},
handleActive(value) {
this.progressActive = value
},
},
}
</script>
<style lang="postcss" scoped>
:deep(.u-line-progress) {
@apply !overflow-visible;
}
</style>

20
src/pages/statement/index.vue Executable file
View File

@ -0,0 +1,20 @@
<template>
<view class="h-full p-4">
<view class="space-y-4">
<view class=""> 项目 </view>
<view class=""> 使用本项目的用户需要遵守如下条款 </view>
<view class="">
特此授权免费得以任何目的的使用复制修改合并出版发行散布再授权及贩售软件及软件的副本及授予前述权利的许可无论是否为商业目的
</view>
<view class="">
上述软件是按原样提供作者不作任何明示或暗示的保证包括但不限于对适销性和特定目的的适用性的保证在任何情况下无论是在合同诉讼侵权行为或其它方面作者都不对因使用本软件或其中所包含的内容所产生的任何直接间接偶然特殊及后果性损害包括但不限于替代商品或服务的采购使用数据或利润损失或业务中断承担责任
</view>
</view>
</view>
</template>
<script>
export default {}
</script>
<style></style>

25
src/pages/webview/index.vue Executable file
View File

@ -0,0 +1,25 @@
<template>
<web-view class="h-full" :src="viewProps.src"></web-view>
</template>
<script>
export default {
data() {
return {
viewProps: {
src: '',
},
}
},
onLoad(params) {
if (params.title) {
uni.setNavigationBarTitle({
title: params.title,
})
}
this.viewProps = params
},
}
</script>
<style></style>

7
src/plugins/index.js Executable file
View File

@ -0,0 +1,7 @@
import UviewPlus from './uview-plus'
export default {
install(app) {
app.use(UviewPlus)
},
}

View File

@ -0,0 +1,7 @@
import uviewPlus from 'uview-plus'
export default {
install(app) {
app.use(uviewPlus)
},
}

Some files were not shown because too many files have changed in this diff Show More