初始版本

This commit is contained in:
贾祥聪
2025-08-19 14:16:51 +08:00
commit f937a1f9b9
4373 changed files with 359728 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# 请求域名
VITE_APP_BASE_URL=''

View File

@@ -0,0 +1,3 @@
# 请求域名
VITE_APP_BASE_URL=''

39
staff_uniapp/.eslintrc.js Normal file
View File

@@ -0,0 +1,39 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
ignorePatterns: ['src/uni_modules/'],
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
rules: {
'prettier/prettier': [
'warn',
{
semi: false,
singleQuote: true,
printWidth: 100,
proseWrap: 'preserve',
bracketSameLine: false,
endOfLine: 'auto',
tabWidth: 4,
useTabs: false,
trailingComma: 'none'
}
],
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'no-undef': 'off',
'vue/prefer-import-from-vue': 'off',
'no-prototype-builtins': 'off',
'prefer-spread': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off'
},
globals: {}
}

32
staff_uniapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.hbuilderx
# .env
.env.development
.env.production

View File

@@ -0,0 +1,5 @@
.vscode
.idea
dist/
node_modules/
src/uni_modules

11
staff_uniapp/.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"proseWrap": "preserve",
"bracketSameLine": false,
"endOfLine": "lf",
"tabWidth": 4,
"useTabs": false,
"trailingComma": "none"
}

3
staff_uniapp/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

11
staff_uniapp/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"editor.detectIndentation": false,
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"css.validate": false,
"less.validate": false,
"scss.validate": false
}

20
staff_uniapp/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!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.ts"></script>
</body>
</html>

12474
staff_uniapp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

89
staff_uniapp/package.json Normal file
View File

@@ -0,0 +1,89 @@
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev": "node scripts/develop.js",
"dev:app": "uni -p app",
"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-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:mp-weixin": "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": "node scripts/publish.js",
"build:app": "uni build -p app",
"build:custom": "uni build -p",
"build:h5": "uni build && node scripts/release.mjs",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"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",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3070920230324001",
"@dcloudio/uni-app-plus": "3.0.0-3070920230324001",
"@dcloudio/uni-components": "3.0.0-3070920230324001",
"@dcloudio/uni-h5": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-alipay": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-baidu": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-jd": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-lark": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-qq": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3070920230324001",
"@dcloudio/uni-mp-weixin": "3.0.0-3070920230324001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3070920230324001",
"@dcloudio/uni-webview-js": "0.0.3",
"css-color-function": "1.3.3",
"lodash-es": "4.17.21",
"pinia": "2.0.20",
"uniapp-router-next": "1.2.7",
"uniapp-router-next-zm": "1.0.1",
"vconsole": "3.14.6",
"vue": "3.2.45",
"vue-i18n": "9.1.9",
"weixin-js-sdk": "1.6.0",
"z-paging": "2.7.6"
},
"devDependencies": {
"@dcloudio/types": "3.3.2",
"@dcloudio/uni-automator": "3.0.0-3070920230324001",
"@dcloudio/uni-cli-shared": "3.0.0-3070920230324001",
"@dcloudio/uni-stacktracey": "3.0.0-3070920230324001",
"@dcloudio/vite-plugin-uni": "3.0.0-3070920230324001",
"@rushstack/eslint-patch": "1.1.4",
"@types/lodash-es": "4.17.6",
"@types/node": "18.7.16",
"@vue/eslint-config-prettier": "7.0.0",
"@vue/eslint-config-typescript": "11.0.0",
"autoprefixer": "10.4.8",
"eslint": "8.22.0",
"eslint-plugin-vue": "9.4.0",
"execa": "6.1.0",
"fs-extra": "10.1.0",
"postcss": "8.4.16",
"postcss-rem-to-responsive-pixel": "5.1.3",
"prettier": "2.7.1",
"sass": "1.54.5",
"tailwindcss": "3.3.2",
"typescript": "4.7.4",
"unplugin-uni-router": "1.2.7",
"vite": "4.1.4",
"weapp-tailwindcss-webpack-plugin": "1.12.8"
}
}

View File

@@ -0,0 +1,105 @@
const { spawn } = require('child_process')
const readline = require('readline')
class DevelopClientScript {
constructor() {
if (DevelopClientScript.instance) {
return DevelopClientScript.instance
}
DevelopClientScript.instance = this
}
promptUser(question) {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question(question, (res) => {
resolve(res)
rl.close()
})
})
}
async runClient() {
console.error('请选择你需要运行的客户端(回复数字后回车)')
console.error('0.取消')
console.error('1.微信小程序')
console.error('2.公众号或者H5')
const runClientRes = await this.promptUser('请输入运行的客户端:')
switch (runClientRes) {
case '0':
break
case '1':
await this.runNpmScript('dev:mp-weixin')
break
case '2':
await this.runNpmScript('dev:h5')
break
default:
await this.runClient()
break
}
}
runNpmScript(scriptName) {
return new Promise((resolve, reject) => {
const isWindows = process.platform === 'win32'
const command = isWindows ? 'cmd.exe' : 'npm'
const args = isWindows
? ['/c', 'npm', 'run', scriptName]
: ['run', scriptName]
const runProcess = spawn(command, args)
runProcess.stdout.on('data', (data) => {
console.log(data.toString())
})
runProcess.stderr.on('data', (data) => {
console.error(data.toString())
})
runProcess.on('close', (code) => {
if (code !== 0) {
reject(
new Error(
`运行错误,请查看以下报错信息寻找解决方法: ${error.message}`
)
)
} else {
resolve()
}
})
})
}
async run(targetVersion) {
const currentVersion = process.versions.node
if (currentVersion < targetVersion) {
throw new Error(
`你的当前node版本为(${currentVersion}),需要安装目标版本为 ${targetVersion} 以上!!`
)
}
await this.runClient()
}
static getInstance() {
if (!DevelopClientScript.instance) {
DevelopClientScript.instance = new DevelopClientScript()
}
return DevelopClientScript.instance
}
}
;(async () => {
const develop = DevelopClientScript.getInstance()
try {
await develop.run('16.16.0')
} catch (error) {
console.error(error.message)
}
})()

View File

@@ -0,0 +1 @@
const { spawn } = require('child_process')

View File

@@ -0,0 +1,35 @@
import path from 'path'
import fsExtra from 'fs-extra'
const { existsSync, remove, copy } = fsExtra
const cwd = process.cwd()
//打包发布路径,谨慎改动
const releaseRelativePath = '../server/public/coach'
const distPath = path.resolve(cwd, 'dist/build/h5')
const releasePath = path.resolve(cwd, releaseRelativePath)
async function build() {
if (existsSync(releasePath)) {
await remove(releasePath)
}
console.log(`文件正在复制 ==> ${releaseRelativePath}`)
try {
await copyFile(distPath, releasePath)
} catch (error) {
console.log(`\n ${error}`)
}
console.log(`文件已复制 ==> ${releaseRelativePath}`)
}
function copyFile(sourceDir, targetDir) {
return new Promise((resolve, reject) => {
copy(sourceDir, targetDir, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
build()

62
staff_uniapp/src/App.vue Normal file
View File

@@ -0,0 +1,62 @@
<script setup lang="ts">
import { onLaunch } from '@dcloudio/uni-app'
import { useAppStore } from './stores/app'
import { useUserStore } from './stores/user'
import { useThemeStore } from './stores/theme'
import { useLocation } from '@/hooks/useLocation'
import { useRoute, useRouter } from 'uniapp-router-next'
const appStore = useAppStore()
const { getUser } = useUserStore()
const { getTheme } = useThemeStore()
const router = useRouter()
const route = useRoute()
const { getLocationData } = useLocation()
//#ifdef H5
const setH5WebIcon = () => {
const config = appStore.getWebsiteConfig
let favicon: HTMLLinkElement = document.querySelector('link[rel="icon"]')!
if (favicon) {
favicon.href = config.h5_favicon
return
}
favicon = document.createElement('link')
favicon.rel = 'icon'
favicon.href = config.h5_favicon
document.head.appendChild(favicon)
}
//#endif
const getConfig = async () => {
await appStore.getConfig()
//#ifdef H5
setH5WebIcon()
//#endif
// const { status, page_status, page_url } = appStore.getH5Config
if (route.meta.webview) return
//处理关闭h5渠道
//#ifdef H5
// if (status == 0) {
// if (page_status == 1) return (location.href = page_url)
// router.reLaunch('/pages/empty/empty')
// }
//#endif
}
onLaunch(async () => {
getTheme()
getConfig()
getLocationData()
//#ifdef H5
setH5WebIcon()
//#endif
await getUser()
})
</script>
<style lang="scss">
page {
height: 100%;
background-color: #f6f7f8;
}
</style>

View File

@@ -0,0 +1,52 @@
import { client } from '@/utils/client'
import request from '@/utils/request'
// #ifdef H5
import { getSignLink } from '@/hooks/wechat'
// #endif
// 登录
export function login(data: Record<string, any>) {
return request.post({ url: '/login/account', data: { ...data, terminal: client } })
}
//注册
export function register(data: Record<string, any>) {
return request.post({ url: '/login/register', data: { ...data, channel: client } })
}
//向微信请求code的链接
export function getWxCodeUrl(data: Record<string, any>) {
return request.get({ url: '/login/codeUrl', data })
}
export function OALogin(data: Record<string, any>) {
return request.post({ url: '/login/oaLogin', data })
}
export function mnpLogin(data: Record<string, any>) {
return request.post({ url: '/login/mnpLogin', data })
}
//微信sdk配置
export const apiJsConfig = () =>
request.get({ url: '/wechat/jsConfig', data: { url: getSignLink() } })
//更新微信小程序头像昵称
export function updateUser(data: Record<string, any>, header: any) {
return request.post({ url: '/login/updateUser', data, header })
}
//小程序绑定微信
export function mnpAuthBind(data: Record<string, any>) {
return request.post({ url: '/login/mnpAuthBind', data })
}
//公众号绑定微信
export function oaAuthBind(data: Record<string, any>) {
return request.post({ url: '/login/oaAuthBind', data })
}
//佣金明细列表
export function userMoneyList(data?: any) {
return request.get({ url: '/finance/lists', data })
}

105
staff_uniapp/src/api/app.ts Normal file
View File

@@ -0,0 +1,105 @@
import request from '@/utils/request'
//发送短信
export function smsSend(data: any) {
return request.post({ url: '/sms/sendCode', data: data })
}
export function getConfig() {
return request.get({ url: '/config/config' })
}
export function getPolicy() {
return request.get({ url: '/config/agreement' })
}
export function uploadImage(file: any, token?: string) {
return request.uploadFile({
url: '/upload/image',
filePath: file,
name: 'file',
header: {
token
},
fileType: 'image'
})
}
export function wxJsConfig(data: any) {
return request.get({ url: '/wechat/jsConfig', data })
}
//获取城市列表
export function getCityList(data?: any) {
return request.get({ url: '/city/getCityLists', data })
}
//获取其他列表
export function getOtherList(data?: any) {
return request.get({ url: '/coach/otherLists', data })
}
//获取技能列表
export function getSkillList(data?: any) {
return request.get({ url: '/coach/skillLists', data })
}
//获取服务项目列表
export function getGoodsList(data?: any) {
return request.get({ url: '/goods/lists', data })
}
//获取服务项目详情
export function getGoodsDetail(data?: any) {
return request.get({ url: '/goods/detail', data })
}
//申请技师
export function apply(data?: any) {
return request.post({ url: '/coach/apply', data })
}
//获取订单列表
export function getOrderList(data?: any) {
return request.get({ url: '/order/lists', data })
}
//提现配置列表
export function withdrawConfigLists(data?: any) {
return request.get({ url: '/withdraw/configLists', data })
}
//提现配置
export function withdrawConfig(data?: any) {
return request.get({ url: '/withdraw/getWithDrawWay', data })
}
//设置提现配置
export function setWithdrawConfig(data?: any) {
return request.post({ url: '/withdraw/setWithDrawWay', data })
}
//财务明细列表
export function apiFinanceLists(data?: any) {
return request.get({ url: '/finance/lists', data })
}
/**
* @param { Object } params { location: xxx,xxx }
* @return { Promise }
* @description 地址逆解析
*/
export const getGeocoderCoordinate = (params: any) =>
request.get({ url: '/city/geocoderCoordinate', data: params })
//获取附近地址
export const getNearbyLocation = (params: any) =>
request.get({ url: '/city/getNearbyLocation', data: params })
/**
* @param { Object } params
* @return { Promise }
* @description 获取地级市列表 以首字母排序
*/
export const apiRegionCity = (params: any) => request.get({ url: '/city/city', data: params })

View File

@@ -0,0 +1,31 @@
import request from '@/utils/request'
//商家列表
export function ShopList(data?: any) {
return request.get({ url: '/shop/lists', data })
}
//申请结果
export function applyDetail(data?: any) {
return request.get({ url: '/shop/applyDetail', data })
}
//申请
export function apply(data?: any) {
return request.post({ url: '/shop/applyJoin', data })
}
//取消申请
export function cancelApply(data?: any) {
return request.post({ url: '/shop/cancel', data })
}
//退出商家
export function quitShop(data?: any) {
return request.post({ url: '/shop/applyQuit', data })
}
//商家详情
export function ShopDetail(data?: any) {
return request.get({ url: '/shop/detail', data })
}

View File

@@ -0,0 +1,18 @@
import request from '@/utils/request'
//获取提现配置
export function getCashOutConfig(data?: any) {
return request.post({ url: '/withdraw/getWithDrawInfo', data })
}
export function toCashOut(data?: any) {
return request.post({ url: '/withdraw/apply', data })
}
export function listData(data?: any) {
return request.get({ url: '/withdraw/logLists', data })
}
export function userWithdrawDetail(data?: any) {
return request.get({ url: '/withdraw/detail', data })
}

View File

@@ -0,0 +1,11 @@
import request from '@/utils/request'
//获取技师服务时间
export function getWorkTime() {
return request.get({ url: '/coach/getServerTime' })
}
//设置技师服务时间
export function setWorkTime(data: any) {
return request.post({ url: '/coach/setServerTime', data })
}

View File

@@ -0,0 +1,17 @@
import request from '@/utils/request'
/**
* @param { Object } params
* @return { Promise }
* @description 获取商品评价列表
*/
export const apiEvaluateGoodsLists = (params: any) =>
request.get({ url: '/goods_comment/lists', data: params })
/**
* @param { Object } params
* @return { Promise }
* @description 获取商品评价分类列表
*/
export const apiEvaluateGoodsCategory = (params: any) =>
request.get({ url: '/goods_comment/commentCategory', data: params })

View File

@@ -0,0 +1,52 @@
import request from '@/utils/request'
/**
* @description 获取文章分类
* @return { Promise }
*/
export function getArticleCate() {
return request.get({ url: '/article/cate' })
}
/**
* @description 获取文章列表
* @return { Promise }
*/
export function getArticleList(data: Record<string, any>) {
return request.get({ url: '/article/lists', data: data })
}
/**
* @description 获取文章详情
* @param { number } id
* @return { Promise }
*/
export function getArticleDetail(data: { id: number }) {
return request.get({ url: '/article/detail', data: data })
}
/**
* @description 加入收藏
* @param { number } id
* @return { Promise }
*/
export function addCollect(data: { id: number }) {
return request.post({ url: '/article/addCollect', data: data }, { isAuth: true })
}
/**
* @description 取消收藏
* @param { number } id
* @return { Promise }
*/
export function cancelCollect(data: { id: number }) {
return request.post({ url: '/article/cancelCollect', data: data }, { isAuth: true })
}
/**
* @description 获取收藏列表
* @return { Promise }
*/
export function getCollect() {
return request.get({ url: '/article/collect' })
}

View File

@@ -0,0 +1,32 @@
import request from '@/utils/request'
//获取订单详情
export function getOrderDetail(data: Record<string, any>) {
return request.get({ url: '/order/detail', data })
}
//接单
export function takeOrder(data: Record<string, any>) {
return request.post({ url: '/order/take', data })
}
//出发
export function depart(data: Record<string, any>) {
return request.post({ url: '/order/depart', data })
}
//到达
export function arrive(data: Record<string, any>) {
return request.post({ url: '/order/arrive', data })
}
//服务开始
export function startServer(data: Record<string, any>) {
return request.post({ url: '/order/startServer', data })
}
//服务完成
export function finishServer(data: Record<string, any>) {
return request.post({ url: '/order/finishServer', data })
}
// 收入列表
export function getInComeLists(data: Record<string, any>) {
return request.get({ url: '/order/incomeLists', data })
}

View File

@@ -0,0 +1,25 @@
import request from '@/utils/request'
import { client } from '@/utils/client'
//支付方式
export function getPayWay(data?: any) {
return request.get({ url: '/pay/payWay', data: { ...data, scene: client } }, { isAuth: true })
}
// 预支付
export function prepay(data: any) {
return request.post({ url: '/pay/prepay', data }, { isAuth: true })
}
// 预支付
export function getPayResult(data: any) {
return request.get({ url: '/pay/getPayResult', data }, { isAuth: true })
}
//跳转支付
export const apiJumpPayPrepay = (params: any,token:string) => request.post({ url: '/pay/prepay', data: params, header: {token:token}})
//跳转支付结果
export function apiJumpPayResult(params: any,token:string) {
return request.get({ url: '/pay/getPayResult', data: params, header: {token:token} })
}

View File

@@ -0,0 +1,28 @@
import request from '@/utils/request'
//首页数据
export function getIndex() {
return request.get({ url: '/index/index' })
}
// 装修页面
export function getDecorate(data: any) {
return request.get({ url: '/decorate/page', data }, { ignoreCancel: true })
}
// 底部导航
export function getDecorateTabbar() {
return request.get({ url: '/decorate/tabbar' }, { ignoreCancel: true })
}
// 系统风格
export function getDecorateStyle() {
return request.get({ url: '/decorate/style' }, { ignoreCancel: true })
}
/**
* @description 热门搜索
* @return { Promise }
*/
export function getHotSearch() {
return request.get({ url: '/search/hotLists' })
}

View File

@@ -0,0 +1,83 @@
import request from '@/utils/request'
export function getUserCenter(header?: any) {
return request.get({ url: '/coach/center', header }, { ignoreCancel: true })
}
// 实名信息
export function getCoachInfo(header?: any) {
return request.get({ url: '/coach/info', header })
}
// 个人信息
export function getUserInfo() {
return request.get({ url: '/coach/personalData' }, { isAuth: true })
}
// 个人编辑
export function userEdit(data: any) {
return request.post({ url: '/coach/setPersonalData', data }, { isAuth: true })
}
// 绑定手机
export function userBindMobile(data: any, header?: any) {
return request.post({ url: '/coach/bindMobile', data, header }, { isAuth: true })
}
// 微信电话
export function userMnpMobile(data: any) {
return request.post({ url: '/user/getMobileByMnp', data }, { isAuth: true })
}
// 更改密码
export function userChangePwd(data: any) {
return request.post({ url: '/login/changePassword', data }, { isAuth: true })
}
//忘记密码
export function forgotPassword(data: Record<string, any>) {
return request.post({ url: '/login/resetPassword', data })
}
//余额明细
export function accountLog(data: any) {
return request.get({ url: '/account_log/lists', data })
}
//修改技师状态
export function editStatus(data?: any) {
return request.get({ url: '/coach/updateWorkStatus', data })
}
//修改定位
export function editLocation(data?: any) {
return request.get({ url: '/coach/updateLocation', data })
}
//获取保证金套餐
export function getDepositList(data?: any) {
return request.get({ url: '/deposit/depositPackage', data })
}
//获取技师详情
export function getCoachDetail() {
return request.get({ url: '/coach/detail' })
}
//更新技师资料
export function updateCoach(data?: any) {
return request.post({ url: '/coach/updateInfo', data })
}
//充值保证金
export function apiRechargeDeposit(data: any) {
return request.post({ url: '/deposit/sumbitOrder', data }, { isAuth: true })
}
//获取技师完善资料详情
export function getUpdateInfoDetail(data?: any) {
return request.get({ url: '/coach/updateInfoDetail', data })
}
export const getKefuConfig = (params?: any) =>
request.post({ url: '/config/getKefuConfig', data: params })

View File

@@ -0,0 +1,90 @@
<template>
<view class="agreement" :class="{ shake: isShake }">
<view>
<u-checkbox v-model="isActive" shape="circle">
<view class="text-base flex">
已阅读并同意
<view class="text-primary" @click.stop>
<router-navigate
class="text-primary"
to="/pages/agreement/agreement?type=service"
>
服务协议
</router-navigate>
</view>
<view class="text-primary" @click.stop>
<router-navigate
class="text-primary"
to="/pages/agreement/agreement?type=privacy"
>
隐私协议
</router-navigate>
</view>
</view>
</u-checkbox>
</view>
</view>
<modal-popup
v-model:show="showPopup"
title="服务协议及隐私协议"
@confirm="isActive = true"
>
<template #content>
<view>
为了更好的保障您的权益请您阅读并同意 <text class="text-primary">服务协议隐私政策</text>
</view>
</template>
</modal-popup>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/stores/app'
import { computed, ref } from 'vue'
import ModalPopup from '../modal-popup/modal-popup.vue'
const appStore = useAppStore()
const showPopup = ref(false)
const isActive = ref(false)
const isShake = ref(false)
const checkAgreement = () => {
if (!isActive.value) {
showPopup.value = true
isShake.value = true
setTimeout(() => {
isShake.value = false
}, 1000)
}
return isActive.value
}
defineExpose({
checkAgreement
})
</script>
<style lang="scss">
.shake {
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
transform: translate3d(0, 0, 0);
}
@keyframes shake {
10%,
90% {
transform: translate3d(-1px, 0, 0);
}
20%,
80% {
transform: translate3d(2px, 0, 0);
}
30%,
50%,
70% {
transform: translate3d(-4px, 0, 0);
}
40%,
60% {
transform: translate3d(4px, 0, 0);
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<button
class="avatar-upload p-0 m-0 rounded"
:style="styles"
hover-class="none"
open-type="chooseAvatar"
@click="chooseAvatar"
@chooseavatar="chooseAvatar"
>
<image class="w-full h-full" mode="heightFix" :src="modelValue" v-if="modelValue" />
<slot v-else>
<div
:style="styles"
class="border border-dotted border-light flex w-full h-full flex-col items-center justify-center text-muted text-xs box-border rounded"
>
<u-icon name="plus" :size="36" />
添加图片
</div>
</slot>
</button>
</template>
<script lang="ts" setup>
import { uploadImage } from '@/api/app'
import { useUserStore } from '@/stores/user'
import { addUnit } from '@/utils/util'
import { isBoolean } from 'lodash-es'
import { computed, CSSProperties, onUnmounted } from 'vue'
const props = defineProps({
modelValue: {
type: String
},
fileKey: {
type: String,
default: 'uri'
},
size: {
type: [String, Number],
default: 120
},
round: {
type: [Boolean, String, Number],
default: false
},
border: {
type: Boolean,
default: true
}
})
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void
}>()
const userStore = useUserStore()
const styles = computed<CSSProperties>(() => {
const size = addUnit(props.size)
return {
width: size,
height: size,
borderRadius: isBoolean(props.round) ? (props.round ? '50%' : '') : addUnit(props.round)
}
})
const chooseAvatar = (e: any) => {
// #ifndef MP-WEIXIN
uni.navigateTo({
url: '/uni_modules/vk-uview-ui/components/u-avatar-cropper/u-avatar-cropper?destWidth=300&rectWidth=200&fileType=jpg'
})
// #endif
// #ifdef MP-WEIXIN
const path = e.detail?.avatarUrl
if (path) {
uploadImageIng(path)
}
// #endif
}
const uploadImageIng = async (file: string) => {
uni.showLoading({
title: '正在上传中...'
})
try {
const res: any = await uploadImage(file, userStore.temToken!)
uni.hideLoading()
console.log(res)
emit('update:modelValue', res[props.fileKey])
} catch (error) {
uni.hideLoading()
uni.$u.toast(error)
}
}
// 监听从裁剪页发布的事件,获得裁剪结果
uni.$on('uAvatarCropper', (path) => {
uploadImageIng(path)
})
onUnmounted(() => {
uni.$off('uAvatarCropper')
})
</script>
<style lang="scss" scoped>
.avatar-upload {
background: #fff;
overflow: hidden;
&::after {
border: none;
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<view>
<view
v-if="select"
class="bg-primary p-1 w-[46rpx] h-[46rpx] flex items-center justify-center rounded-full"
:style="{ background: background }"
>
<u-icon color="#fff" name="checkbox-mark"></u-icon>
</view>
<view
v-if="!select"
class="rounded-full w-[46rpx] h-[46rpx]"
style="border: 2px solid #c8c9cc80"
>
</view>
</view>
</template>
<script lang="ts" setup>
const props = defineProps({
select: {
type: Boolean,
default: false
},
background: {
type: String,
default: ''
}
})
</script>

View File

@@ -0,0 +1,87 @@
<template>
<view class="item">
<view class="flex items-center justify-between">
<view class="flex items-center">
<u-image
:src="data.avatar"
width="80"
height="80"
borderRadius="50%"
></u-image>
<view class="flex flex-col justify-between ml-2">
<view class="text-base font-medium">
{{ data.nickname }}
</view>
<view class="mt-1 text-muted text-xs">
{{ data.create_time }}
</view>
</view>
</view>
<u-rate
:count="5"
v-model="data.service_comment"
:disabled="true"
class="ml-auto"
inactive-color="#eaeaeb"
inactiveIcon="star-fill"
active-color="#d86930"
></u-rate>
</view>
<view class="mt-3 break-words text-content comment"> {{ data.comment }}</view>
<view class="mt-3 grid gap-2 grid-cols-4">
<view
v-for="(commentImage, index) in data.goods_comment_image"
:key="index"
class="mt-[10rpx]"
:class="{ 'mr-[10rpx]': (index + 1) % 4 != 0 }"
@click.stop="previewImage(data.goods_comment_image, index)"
>
<u-image
:src="commentImage.uri"
width="150"
height="150"
border-radius="14rpx"
></u-image>
</view>
</view>
<view class="reply mt-3" v-if="data.reply">
<span class="text-sm">商家回复</span>
<span class="text-sm text-content">{{ data.reply }}</span>
</view>
</view>
</template>
<script lang="ts" setup>
defineProps<{
data: any
}>()
const previewImage = (url: any, index: number | string) => {
uni.previewImage({
current: index,
urls: url.map((item: any) => item.uri)
})
}
</script>
<style lang="scss" scoped>
// .item {
// border-bottom: 2rpx solid #f5f5f5;
// }
.comment {
line-height: 42rpx;
letter-spacing: 3rpx;
}
.reply {
line-height: 40rpx;
letter-spacing: 3rpx;
padding: 10rpx 20rpx;
border-radius: 4rpx;
background: #F3F3F3;
}
</style>

View File

@@ -0,0 +1,131 @@
<template>
<u-upload
ref="uUploadRef"
:file-list="fileList"
:action="action"
v-bind="props"
:header="headers"
@on-success="onSuccess"
@on-uploaded="onUploaded"
@on-progress="onProgress"
@on-remove="onRemove"
>
<slot name="addBtn" />
</u-upload>
</template>
<script setup lang="ts">
import config from '@/config'
import { RequestCodeEnum } from '@/enums/requestEnums'
import { computed, ref, shallowRef, watch } from 'vue'
import { useUserStore } from '@/stores/user'
const props = withDefaults(
defineProps<{
modelValue: string | string[]
maxCount?: number
width?: number | string
height?: number | string
customBtn?: boolean
disabled?: boolean
header?: Record<string, any>
formData?: Record<string, any>
name?: string
multiple?: boolean
deletable?: boolean
withName: boolean
}>(),
{
maxCount: 1,
width: 180,
height: 180,
customBtn: false,
disabled: false,
header: () => ({}),
formData: () => ({}),
name: 'file',
multiple: true,
deletable: true,
withName: false
}
)
const emit = defineEmits<{
(event: 'update:modelValue', modelValue: string | string[]): void
}>()
const uUploadRef = shallowRef()
const userStore = useUserStore()
const action = `${config.baseUrl}${config.urlPrefix}/upload/image`
const fileList = ref<any[]>([])
const headers = computed(() => ({
token: userStore.token,
version: config.version,
...props.header
}))
const onSuccess = (data: any, index: number, list: any[]) => {
if (data.code !== RequestCodeEnum.SUCCESS) {
list[index].progress = 0
list[index].error = true
uni.$u.toast(data.msg)
} else {
const url = data.data?.uri
let result = url
if (!props.withName) {
if (props.maxCount <= 1) {
result = url
} else if (Array.isArray(props.modelValue)) {
result = [...props.modelValue, url]
} else {
result = [url]
}
} else {
result = [...props.modelValue, { url, name: data.data?.name }]
}
emit('update:modelValue', result)
}
}
const isUploading = ref(false)
const onProgress = () => {
isUploading.value = true
}
const onUploaded = () => {
isUploading.value = false
}
const onRemove = (index: number) => {
if (props.maxCount <= 1) {
emit('update:modelValue', '')
} else {
if (Array.isArray(props.modelValue)) {
const newValue = props.modelValue
newValue.splice(index, 1)
emit('update:modelValue', newValue)
}
}
}
watch(
() => props.modelValue,
(value) => {
if (!value || isUploading.value) return
uUploadRef.value?.clear()
if (!props.withName) {
if (Array.isArray(value)) {
fileList.value = value.map((url) => ({ url }))
} else if (typeof value === 'string') {
fileList.value = [{ url: value }]
}
} else {
console.log(value)
fileList.value = (value as []).map((item: any) => {
return { url: item.url }
})
// fileList.value = value.map((item) => {
// item.url
// })
}
},
{
immediate: true
}
)
</script>

View File

@@ -0,0 +1,96 @@
<template>
<u-swiper
:list="lists"
:mode="mode"
:height="height"
:effect3d="effect3d"
:indicator-pos="indicatorPos"
:autoplay="autoplay"
:interval="interval"
:duration="duration"
:circular="circular"
:borderRadius="borderRadius"
:current="current"
:name="name"
:bg-color="bgColor"
@click="handleClick"
@change="handleChange"
>
</u-swiper>
</template>
<script setup lang="ts">
import {useAppStore} from "@/stores/app";
import {navigateTo, navigateToMiniProgram, LinkTypeEnum} from "@/utils/util";
import {watchEffect, computed} from "vue";
import {useRouter} from "uniapp-router-next";
const emit = defineEmits(["change"]);
const props = withDefaults(
defineProps<{
content?: any; // 轮播图数据
mode?: string; // 指示器模式 rect / dot / number / none
height?: string; // 轮播图组件高度
indicatorPos?: string; // 指示器的位置 topLeft / topCenter / topRight / bottomLeft / bottomRight
effect3d?: boolean; // 是否开启3D效果
autoplay?: boolean; // 是否自动播放
interval?: number | string; // 自动轮播时间间隔单位ms
duration?: number | string; // 切换一张轮播图所需的时间单位ms
circular?: boolean; // 是否衔接播放
current?: number; // 默认显示第几项
name?: string; // 显示的属性
borderRadius?: string; //轮播图圆角值单位rpx
bgColor?: string; // 背景颜色
}>(),
{
content: {
data: [],
},
mode: "round",
indicatorPos: "bottomCenter",
height: "340",
effect3d: false,
autoplay: true,
interval: "2500",
duration: 300,
circular: true,
current: 0,
name: "image",
borderRadius: "0",
bgColor: "#f3f4f6",
}
);
const {getImageUrl} = useAppStore();
watchEffect(() => {
try {
const content = props?.content;
const len = content?.data?.length;
if (!len) return;
for (let i = 0; i < len; i++) {
const item = content.data[i];
item.image = getImageUrl(item.image);
}
emit("change", 0);
} catch (error) {
//TODO handle the exception
console.log("轮播图数据错误", error);
}
});
const lists = computed(() => props.content.data || []);
const router = useRouter();
const handleClick = (index: number) => {
const link = props.content.data[index]?.link;
if (!link) return
navigateTo(link);
};
const handleChange = (index: number) => {
emit("change", index);
};
</script>
<style></style>

View File

@@ -0,0 +1,130 @@
<template>
<u-popup
v-model="showPopup"
mode="center"
:mask-close-able="false"
:customStyle="{
'background': `none`
}"
:closeable="closeable"
@close="emits('close')"
>
<view
style="width: 600rpx;border-radius: 20rpx;"
class="modal-popup p-[40rpx] text-center"
>
<view class="py-2 font-medium text-2xl text-black relative z-10">
{{ title }}
</view>
<view
class="py-[16px] text-base text-content relative z-10"
style="width: 500rpx; margin: 0 auto;"
>
<slot name="content">{{ content }}</slot>
</view>
<slot name="footer">
<view
class="flex gap-[20rpx] mt-[40rpx]"
>
<view class="flex-1">
<u-button
@click="cancel"
>
取消
</u-button>
</view>
<view class="flex-1">
<u-button
type="primary"
@click="confirm"
>
确定
</u-button>
</view>
</view>
</slot>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const props = defineProps<{
show: boolean
title: string
content: string
closeable: boolean
}>()
const emits = defineEmits<{
(event: 'update:show', show: boolean): void
(event: 'update', value: any): void
(event: 'refresh'): void
(event: 'close'): void
(event: 'cancel'): void
(event: 'confirm'): void
}>()
const showPopup = computed({
get() {
return props.show
},
set(val) {
emits('update:show', val)
}
})
const cancel = () => {
showPopup.value = false
emits('cancel')
}
const confirm = async () => {
showPopup.value = false
emits('confirm')
// 检测定位权限, 如果是就打开设置
if (props.title === '定位权限未授权') {
const settings = await uni.openSetting()
if (settings.authSetting['scope.userLocation']) {
emits('refresh') // 重新获取定位
}
}
}
</script>
<style lang="scss" scoped>
.modal-popup {
position: relative;
background: linear-gradient(to bottom, var(--color-primary-light-9) 0%, #fff 50%, #fff 100%);
z-index: 2; // 确保内容的z-index高于伪元素
&::before,
&::after {
content: '';
position: absolute;
width: 160px;
height: 160px;
opacity: 0.3;
border-radius: 50%;
z-index: 1; // 确保伪元素的z-index低于内容
}
&::before {
left: -50px;
top: -90px;
background: linear-gradient(200deg, var(--color-primary-light-3) 0%, #fff 100%);
}
&::after {
right: -50px;
top: -90px;
background: linear-gradient(150deg, var(--color-primary-light-3) 0%, #fff 100%);
}
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<view>
<u-popup v-model="showPopup" mode="bottom" border-radius="14" :mask-close-able="false">
<view class="h-[1000rpx] p-[40rpx]">
<view class="flex items-center">
<image
class="w-[100rpx] h-[100rpx] rounded"
mode="heightFix"
:src="logo"
></image>
<text class="text-3xl ml-5 font-bold">{{ title }}</text>
</view>
<view class="mt-5 text-muted">
建议使用您的微信头像和昵称以便获得更好的体验
</view>
<view class="mt-[30rpx]">
<form @submit="handleSubmit">
<u-form-item required label="头像" :labelWidth="120">
<view class="flex-1">
<avatar-upload v-model="avatar"></avatar-upload>
</view>
</u-form-item>
<u-form-item required label="昵称" :labelWidth="120">
<input
class="flex-1 h-[60rpx]"
name="nickname"
type="nickname"
placeholder="请输入昵称"
/>
</u-form-item>
<view class="mt-[80rpx]">
<button
class="bg-primary rounded-full text-white text-lg h-[80rpx] leading-[80rpx]"
hover-class="none"
form-type="submit"
>
确定
</button>
</view>
<view class="flex justify-center mt-[60rpx]">
<view class="text-muted" @click="showPopup = false">暂不登录</view>
</view>
</form>
</view>
</view>
</u-popup>
</view>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
const props = defineProps({
show: {
type: Boolean
},
logo: {
type: String
},
title: {
type: String
}
})
const emit = defineEmits<{
(event: 'update:show', show: boolean): void
(event: 'update', value: any): void
}>()
const showPopup = computed({
get() {
return props.show
},
set(val) {
emit('update:show', val)
}
})
const avatar = ref()
const handleSubmit = (e: any) => {
const { nickname } = e.detail.value
if (!avatar.value)
return uni.$u.toast('请添加头像')
if (!nickname)
return uni.$u.toast('请输入昵称')
emit('update', {
avatar: avatar.value,
nickname
})
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,65 @@
<template>
<navigator :url="`/pages/news_detail/news_detail?id=${newsId}`">
<view class="news-card flex bg-white px-[20rpx] py-[32rpx]">
<view class="mr-[20rpx]" v-if="item.image">
<u-image :src="item.image" width="240" height="180"></u-image>
</view>
<view class="news-card-content flex flex-col justify-between flex-1">
<view class="news-card-content-title text-base">{{ item.title }}</view>
<view class="news-card-content-intro text-gray-400 text-sm mt-[16rpx]">
{{ item.desc }}
</view>
<view class="text-muted text-xs w-full flex justify-between mt-[12rpx]">
<view>{{ item.create_time }}</view>
<view class="flex items-center">
<image
src="/static/images/icon/icon_visit.png"
class="w-[30rpx] h-[30rpx]"
></image>
<view class="ml-[10rpx]">{{ item.click }}</view>
</view>
</view>
</view>
</view>
</navigator>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const props = withDefaults(
defineProps<{
item: any
newsId: number
}>(),
{
item: {},
newsId: ''
}
)
</script>
<style lang="scss" scoped>
.news-card {
border-bottom: 1px solid #f8f8f8;
&-content {
&-title {
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
&-intro {
-webkit-line-clamp: 1;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
}
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<view class="flex flex-col items-center justify-center">
<u-image width="400" height="400" :src="empty"></u-image>
<view class="text-info text-sm mt-2">您当前未登录登录账号可查看信息</view>
<u-button @click="toLogin" class="mt-8 w-[300rpx]" type="primary">去登录</u-button>
</view>
</template>
<script setup lang="ts">
import empty from '@/static/images/empty.png'
import { navigateTo } from '@/utils/util'
const toLogin = () => {
uni.navigateTo({ url: '/pages/login/login' })
}
</script>

View File

@@ -0,0 +1,104 @@
<template>
<u-button
v-if="data.take_order_btn == 1"
:size="size"
type="primary"
@click="handleTakeOrder"
>
立即接单
</u-button>
<u-button
v-if="data.depart_btn == 1"
:size="size"
type="primary"
@click="handleDepartOrder"
>
立即出发
</u-button>
<u-button
v-if="data.arrive_btn == 1"
:size="size"
type="primary"
@click="handleArriveOrder"
>
已到达
</u-button>
<u-button
v-if="data.server_start_btn == 1"
:size="size"
type="primary"
@click="handleStartServer"
>
开始服务
</u-button >
<u-button
v-if="data.server_finish_btn"
:size="size"
type="primary"
@click="handleFinishServer"
>
服务完成
</u-button>
</template>
<script lang="ts" setup>
import { takeOrder, depart, startServer } from '@/api/order'
const props = defineProps<{
data: any
size: 'medium' | 'mini' | 'default'
}>()
const emit = defineEmits<{
(e: 'command', v: any): void
}>()
// 接单操作-
const handleTakeOrder = async () => {
const { msg } = await takeOrder({ id: props.data.id })
emit('command', {
type: 'take_order',
data: msg
})
}
// 出发操作
const handleDepartOrder = async () => {
try {
await depart({ id: props.data.id })
} finally {
emit('command', {
type: 'depart_order',
data: false
})
}
}
// 我已到达操作
const handleArriveOrder = () => {
emit('command', {
type: 'arrived_order',
data: props.data.id
})
}
// 开始服务操作
const handleStartServer = async () => {
try {
await startServer({ id: props.data.id })
} finally {
emit('command', {
type: 'start_order',
data: props.data.id
})
}
}
// 服务完成操作
const handleFinishServer = async () => {
emit('command', {
type: 'finish_order',
data: props.data.id
})
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<popup title="请上传图片" mode="bottom" ref="popRef" @confirm="$emit('confirm', imgList)">
<view style="width: 100%;height: 480rpx;">
<file-upload
width="216rpx"
height="216rpx"
maxCount="6"
v-model="imgList"
ref="fileUploadRef"
></file-upload>
</view>
</popup>
</template>
<script lang="ts" setup>
import popup from '@/components/popup/index.vue'
import { ref } from 'vue'
import { shallowRef } from 'vue'
const popRef = shallowRef()
const emits = defineEmits<{
(event: 'confirm', value: string[]): void
}>()
const imgList = ref<string[]>([])
//打开弹框
const open = () => {
imgList.value = []
popRef.value.open()
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,118 @@
<template>
<view @click="toDetail" class="bg-white rounded-lg px-[24rpx] py-[20rpx] mb-2">
<!-- 订单头部 -->
<view
class="u-flex justify-between"
:class="{
'gray-effect': data.order_status === 7
}"
>
<view
style="padding: 3rpx 8rpx; border-radius: 8rpx"
class="u-flex bg-primary-light-9 text-primary"
>
<u-icon name="calendar" size="26"></u-icon>
<text class="ml-1 text-xs">
上门时间{{ data.appoint_date }} {{ data.appoint_time }}
</text>
</view>
<view
class="text-primary text-xs"
:class="{
'text-[#3DA0FD]': data.order_status === 1,
'text-[#FD463D]': data.order_status === 2,
'text-[#3DA0FD]': data.order_status === 3,
'text-[#333333]': data.order_status === 4,
'text-[#E86016]': data.order_status === 5,
'text-[#E86016]': data.order_status === 6,
'text-[#333333]': data.order_status === 7
}"
>
{{ data.order_status_desc }}
</view>
</view>
<view v-for="item in data.order_goods" :key="item.order_id" class="flex items-center mt-3">
<view class="flex-none">
<u-image
width="140"
height="140"
border-radius="20"
:src="item.goods_image"
></u-image>
</view>
<view
v-if="data?.order_goods?.length > 0"
class="flex-1 min-w-0 flex ml-2 flex-col justify-between"
>
<view class="text-base text-main font-bold line-clamp-1">
{{ item.goods_name || '' }}
</view>
<view class="text-xs text-muted font-medium my-1"
>服务时间{{ item.duration || '' }}分钟</view
>
<price
font-weight="600"
:content="item.goods_price || 0"
main-size="32rpx"
minor-size="20rpx"
color="#E86016"
></price>
</view>
<view class="ml-auto text-xs text-muted">x {{ item.goods_num }}</view>
</view>
<view class="bg-[#F6F7F8] p-[12rpx] mt-2 text-muted text-xs font-medium">
<view>
<text class="font-text-content">服务地址</text>
<text
>{{ data.address_snap.province }} {{ data.address_snap.city }}
{{ data.address_snap.district }} {{ data.address_snap.address }}
{{ data.address_snap?.house_number }}
</text>
</view>
<view class="mt-1 line-clamp-2">
<text class="font-text-content">用户备注</text>
<text>{{ data.user_remark || '-' }}</text>
</view>
</view>
<view class="flex justify-between items-center mt-4">
<view class="text-main">
<text class="text-xs">合计</text>
<price
font-weight="600"
:content="data.total_order_amount"
main-size="40rpx"
minor-size="24rpx"
color="#333333"
></price>
</view>
<view @click.stop>
<slot />
</view>
</view>
</view>
</template>
<script lang="ts" setup>
const prop = defineProps({
data: {
type: Object,
default: () => {}
}
})
//跳转至详情页
const toDetail = () => {
uni.navigateTo({
url: `/packages/pages/order_detail/order_detail?id=${prop.data.id}`
})
}
</script>
<style lang="scss" scoped>
.gray-effect {
filter: grayscale(100%);
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<view
class="page-status"
v-if="status !== PageStatusEnum['NORMAL']"
:class="{ 'page-status--fixed': fixed }"
>
<!-- Loading -->
<template v-if="status === PageStatusEnum['LOADING']">
<slot name="loading">
<u-loading :size="60" mode="flower" />
</slot>
</template>
<!-- Error -->
<template v-if="status === PageStatusEnum['ERROR']">
<slot name="error"></slot>
</template>
<!-- Empty -->
<template v-if="status === PageStatusEnum['EMPTY']">
<slot name="empty"></slot>
</template>
</view>
<template v-else>
<slot> </slot>
</template>
</template>
<script lang="ts" setup>
import { PageStatusEnum } from '@/enums/appEnums'
const props = defineProps({
status: {
type: String,
default: PageStatusEnum['LOADING']
},
fixed: {
type: Boolean,
default: true
}
})
</script>
<style lang="scss" scoped>
.page-status {
height: 100%;
width: 100%;
min-height: 100%;
padding: 0;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
&--fixed {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 900;
}
}
</style>

View File

@@ -0,0 +1,322 @@
<template>
<u-popup
v-model="showPay"
mode="bottom"
safe-area-inset-bottom
:mask-close-able="false"
border-radius="14"
closeable
@close="handleClose"
>
<view class="h-[900rpx]">
<page-status :status="popupStatus" :fixed="false">
<template #error>
<u-empty text="订单信息错误,无法查询到订单信息" mode="order"></u-empty>
</template>
<template #default>
<view class="payment h-full w-full flex flex-col">
<view class="header py-[50rpx] flex flex-col items-center">
<price
:content="payData.order_amount"
mainSize="44rpx"
minorSize="40rpx"
fontWeight="500"
color="#333"
></price>
</view>
<view class="main flex-1 mx-[20rpx]">
<view>
<view class="payway-lists">
<u-radio-group v-model="payWay" class="w-full">
<view
class="p-[20rpx] flex items-center w-full payway-item"
v-for="(item, index) in payData.lists"
:key="index"
@click="selectPayWay(item.pay_way)"
>
<u-icon
class="flex-none"
:size="48"
:name="item.icon"
></u-icon>
<view class="mx-[16rpx] flex-1">
<view class="payway-item--name flex-1">
{{ item.name }}
</view>
<view class="text-muted text-xs">{{
item.extra
}}</view>
</view>
<u-radio activeColor="var(--color-primary)" class="mr-[-20rpx]" :name="item.pay_way">
</u-radio>
</view>
</u-radio-group>
</view>
</view>
</view>
<view class="submit-btn p-[20rpx]">
<u-button
@click="handlePay"
shape="circle"
type="primary"
:loading="isLock"
>
立即支付
</u-button>
</view>
</view>
</template>
</page-status>
</view>
</u-popup>
<u-popup
class="pay-popup"
v-model="showCheckPay"
round
mode="center"
borderRadius="10"
:maskCloseAble="false"
>
<view class="content bg-white w-[560rpx] p-[40rpx]">
<view class="text-2xl font-medium text-center"> 支付确认 </view>
<view class="pt-[30rpx] pb-[40rpx]">
<view> 请在微信内完成支付如果您已支付成功请点击`已完成支付`按钮 </view>
</view>
<view class="flex">
<view class="flex-1 mr-[20rpx]">
<u-button
shape="circle"
type="primary"
plain
size="medium"
hover-class="none"
:customStyle="{ width: '100%' }"
@click="queryPayResult(false)"
>
重新支付
</u-button>
</view>
<view class="flex-1">
<u-button
shape="circle"
type="primary"
size="medium"
hover-class="none"
:customStyle="{ width: '100%' }"
@click="queryPayResult()"
>
已完成支付
</u-button>
</view>
</view>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { pay, PayWayEnum } from '@/utils/pay'
import { getPayWay, prepay, getPayResult } from '@/api/pay'
import { computed, ref, watch } from 'vue'
import { useLockFn } from '@/hooks/useLockFn'
import { series } from '@/utils/util'
import { ClientEnum, PageStatusEnum, PayStatusEnum } from '@/enums/appEnums'
import { useUserStore } from '@/stores/user'
import { client } from '@/utils/client'
/*
页面参数 orderId订单idfrom订单来源
*/
const props = defineProps({
show: {
type: Boolean,
required: true
},
showCheck: {
type: Boolean
},
// 订单id
orderId: {
type: Number,
required: true
},
//订单来源
from: {
type: String,
required: true
},
//h5微信支付回跳路径一般为拉起支付的页面路径
redirect: {
type: String
}
})
const emit = defineEmits(['update:showCheck', 'update:show', 'close', 'success', 'fail'])
const payWay = ref()
const popupStatus = ref(PageStatusEnum.LOADING)
const payData = ref<any>({
order_amount: '',
lists: []
})
const showCheckPay = computed({
get() {
return props.showCheck
},
set(value) {
emit('update:showCheck', value)
}
})
const showPay = computed({
get() {
return props.show
},
set(value) {
emit('update:show', value)
}
})
const handleClose = () => {
showPay.value = false
emit('close')
}
const getPayData = async () => {
popupStatus.value = PageStatusEnum.LOADING
try {
payData.value = await getPayWay({
order_id: props.orderId,
from: props.from
})
popupStatus.value = PageStatusEnum.NORMAL
const checkPay =
payData.value.lists.find((item: any) => item.is_default) || payData.value.lists[0]
payWay.value = checkPay?.pay_way
} catch (error) {
popupStatus.value = PageStatusEnum.ERROR
}
}
const userStore = useUserStore()
const selectPayWay = (pay: number) => {
payWay.value = pay
}
const payment = (() => {
// 查询是否绑定微信
const checkIsBindWx = async () => {
if (
userStore.userInfo.is_auth == 0 &&
[ClientEnum.OA_WEIXIN, ClientEnum.MP_WEIXIN].includes(client) &&
payWay.value == PayWayEnum.WECHAT
) {
const res: any = await uni.showModal({
title: '温馨提示',
content: '当前账号未绑定微信,无法完成支付',
confirmText: '去绑定'
})
if (res.confirm) {
uni.navigateTo({
url: '/pages/user_set/user_set'
})
}
return Promise.reject()
}
}
// 调用预支付
const prepayTask = async () => {
uni.showLoading({
title: '正在支付中'
})
const data = await prepay({
order_id: props.orderId,
from: props.from,
pay_way: payWay.value,
redirect: props.redirect
})
return data
}
//拉起支付
const payTask = async (data: any) => {
try {
const res = await pay.payment(data.pay_way, data.config)
return res
} catch (error) {
return Promise.reject(error)
}
}
return series(checkIsBindWx, prepayTask, payTask)
})()
const { isLock, lockFn: handlePay } = useLockFn(async () => {
try {
const res: PayStatusEnum = await payment()
handlePayResult(res)
uni.hideLoading()
} catch (error) {
uni.hideLoading()
console.log(error)
}
})
const handlePayResult = (status: PayStatusEnum) => {
switch (status) {
case PayStatusEnum.SUCCESS:
emit('success')
break
case PayStatusEnum.FAIL:
emit('fail')
break
}
}
const queryPayResult = async (confirm = true) => {
const res = await getPayResult({
order_id: props.orderId,
from: props.from
})
if (res.pay_status === 0) {
if (confirm == true) {
uni.$u.toast('您的订单还未支付,请重新支付')
}
showPay.value = true
handlePayResult(PayStatusEnum.FAIL)
} else {
if (confirm == false) {
uni.$u.toast('您的订单已经支付,请勿重新支付')
}
handlePayResult(PayStatusEnum.SUCCESS)
}
showCheckPay.value = false
}
watch(
() => props.show,
(value) => {
if (value) {
if (!props.orderId) {
popupStatus.value = PageStatusEnum.ERROR
return
}
getPayData()
}
},
{
immediate: true
}
)
</script>
<style lang="scss">
.payway-lists {
.payway-item {
border-bottom: 1px solid;
@apply border-page;
}
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<view>
<u-popup
border-radius="20"
v-model="show"
:mode="mode"
safe-area-inset-bottom
>
<!-- <view
class="py-[10rpx] px-[19rpx] rounded-lg flex flex-col items-center w-full"
style="background: linear-gradient(169deg, #b2e2eb 17.38%, #fff 80.95%)"
> -->
<view class="py-[10rpx] px-[19rpx] rounded-lg flex flex-col items-center w-full">
<view v-if="title" class="text-center py-[20rpx] font-bold text-xl">
{{ title }}
</view>
<slot>
<view class="text-center p-4">{{ content }}</view>
</slot>
<view class="flex w-full mt-2">
<u-button @click="confirm" v-if="showConfirm" type="primary" class="w-full"
>确定</u-button
>
<u-button
@click="$emit('cancle')"
v-if="showCancle"
type="primary"
class="w-full ml-1"
plain
>取消</u-button
>
</view>
</view>
</u-popup>
</view>
</template>
<script lang="ts" setup>
import { times } from 'lodash-es'
import { ref } from 'vue'
const prop = defineProps({
mode: {
type: String,
default: 'center'
},
title: {
type: String,
default: ''
},
showConfirm: {
type: Boolean,
default: true
},
showCancle: {
type: Boolean,
default: false
},
content: {
type: String,
default: ''
},
async: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['confirm', 'cancle'])
const show = ref(false)
const confirm = () => {
if (!prop.async) {
show.value = false
}
emit('confirm')
}
const open = () => {
show.value = true
}
const close = () => {
show.value = false
}
defineExpose({
open,
close
})
</script>

View File

@@ -0,0 +1,126 @@
<template>
<view class="price-container">
<view
:class="['price-wrap', { 'price-wrap--disabled': lineThrough }]"
:style="{ color: color }"
>
<!-- Prefix -->
<view class="fix-pre" :style="{ fontSize: minorSize }">
<slot name="prefix">{{ prefix }}</slot>
</view>
<!-- Content -->
<view :style="{ 'font-weight': fontWeight }">
<!-- Integer -->
<text :style="{ fontSize: mainSize }">{{ integer }}</text>
<!-- Decimals -->
<text :style="{ fontSize: minorSize }">{{ decimals }}</text>
</view>
<!-- Suffix -->
<view class="fix-suf" :style="{ fontSize: minorSize }">
<slot name="suffix">{{ suffix }}</slot>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
/**
* @description 价格展示,适用于有前后缀,小数样式不一
* @property {String|Number} content 价格 (必填项)
* @property {Number} prec 小数位 (默认: 2)
* @property {Boolean} autoPrec 自动小数位【注以prec为最大小数位】 (默认: true)
* @property {String} color 颜色 (默认: 'unset')
* @property {String} mainSize 主要内容字体大小 (默认: 46rpx)
* @property {String} minorSize 主要内容字体大小 (默认: 32rpx)
* @property {Boolean} lineThrough 贯穿线 (默认: false)
* @property {String|Number} fontWeight 字重 (默认: normal)
* @property {String} prefix 前缀 (默认: ¥)
* @property {String} suffix 后缀
* @example <price content="100" suffix="\/元" />
*/
import { computed } from 'vue'
import { formatPrice } from '@/utils/util'
/** Props Start **/
const props = withDefaults(
defineProps<{
content: string | number // 标题
prec?: number // 小数数量
autoPrec?: boolean // 动态小数
color?: string // 颜色
mainSize?: string // 主要内容字体大小
minorSize?: string // 次要内容字体大小
lineThrough?: boolean // 贯穿线
fontWeight?: string // 字重
prefix?: string // 前缀
suffix?: string // 后缀
}>(),
{
content: '',
prec: 2,
autoPrec: true,
color: '#FA8919',
mainSize: '36rpx',
minorSize: '28rpx',
lineThrough: false,
fontWeight: 'normal',
prefix: '¥',
suffix: ''
}
)
/** Props End **/
/** Computed Start **/
/**
* @description 金额主体部分
*/
const integer = computed(() => {
return formatPrice({
price: props.content,
take: 'int'
})
})
/**
* @description 金额小数部分
*/
const decimals = computed(() => {
let decimals = formatPrice({
price: props.content,
take: 'dec',
prec: props.prec
})
// 小数余十不能是 .10||.20||.30以此类推,
decimals = decimals % 10 == 0 ? decimals.substr(0, decimals.length - 1) : decimals
return props.autoPrec ? (decimals * 1 ? '.' + decimals : '') : props.prec ? '.' + decimals : ''
})
/** Computed End **/
</script>
<style lang="scss" scoped>
.price-container {
display: inline-block;
}
.price-wrap {
display: flex;
align-items: baseline;
&--disabled {
position: relative;
&::before {
position: absolute;
left: 0;
top: 50%;
right: 0;
transform: translateY(-50%);
display: block;
content: '';
height: 0.05em;
background-color: currentColor;
}
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<view class="bg-white rounded-[24rpx] p-2 flex mb-2" @click="toDetail">
<u-image
class="flex-none"
border-radius="20rpx"
height="160rpx"
width="160rpx"
:src="data.image"
/>
<view class="ml-2 h-full flex-1 min-w-0">
<view class="font-bold text-base line-clamp-2">{{ data.name }}</view>
<view class="mt-2">
<price fontWeight="900" :content="data.price"></price>
</view>
</view>
<view v-if="showCheckbox" class="ml-auto pl-4 flex items-center" @tap.stop="clickCheck">
<checkboxMark :select="isSelect"/>
</view>
<view v-else class="ml-auto flex items-center ml-2 text-muted">
<u-icon name="arrow-right" class="flex-none" :size="30"/>
</view>
</view>
</template>
<script lang="ts" setup>
import price from '../price/price.vue'
import checkboxMark from '@/components/checkbox-mark/index.vue'
const props = defineProps({
data: {} as any,
isSelect: {
type: Boolean,
default: false
},
showCheckbox: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['checkChange'])
const clickCheck = () => {
// isSelect.value = !isSelect.value
emit('checkChange', props.data.id)
}
const toDetail = () => {
uni.navigateTo({
url: `/packages/pages/project_detail/project_detail?id=${props.data.id}`
})
}
</script>

View File

@@ -0,0 +1,84 @@
<template>
<view
:class="{ active, inactive: !active, tab: true }"
:style="shouldShow ? '' : 'display: none;'"
>
<slot v-if="shouldRender"></slot>
</view>
</template>
<script lang="ts" setup>
import { ref, provide, inject, watch, computed, onMounted, getCurrentInstance } from 'vue'
const props = withDefaults(
defineProps<{
dot?: boolean | string
name?: boolean | string
info?: any
}>(),
{
dot: false,
name: ''
}
)
const active = ref<boolean>(false)
const shouldShow = ref<boolean>(false)
const shouldRender = ref<boolean>(false)
const inited = ref(undefined)
const updateTabs: any = inject('updateTabs')
const handleChange: any = inject('handleChange')
const updateRender = (value) => {
inited.value = inited.value || value
active.value = value
shouldRender.value = inited.value!
shouldShow.value = value
}
const update = () => {
if (updateTabs) {
updateTabs()
}
}
const instance = getCurrentInstance()
console.log(instance)
handleChange(instance?.props, updateRender)
onMounted(() => {
update()
})
const changeData = computed(() => {
const { dot, info } = props
return {
dot,
info
}
})
watch(
() => changeData.value,
() => {
update()
}
)
watch(
() => props.name,
(val) => {
update()
}
)
</script>
<style>
.tab.active {
height: auto;
}
.tab.inactive {
height: 0;
overflow: visible;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<u-tabbar
v-model="current"
v-bind="tabbarStyle"
:list="tabbarList"
:hide-tab-bar="true"
@change="handleChange"
></u-tabbar>
</template>
<script lang="ts" setup>
import {useAppStore} from '@/stores/app'
import {navigateTo} from '@/utils/util'
import {computed, ref, onMounted} from 'vue'
import {getDecorateTabbar} from "@/api/shop";
const current = ref()
const appStore = useAppStore()
const tabbarStyle = ref({
activeColor: '#007AFF',
inactiveColor: '#999999'
})
const tabbarList = ref<any>([])
const getTabbarList = async () => {
const res = await getDecorateTabbar()
const data = JSON.parse(res.data)
tabbarStyle.value = {
activeColor: data.style.selected_color,
inactiveColor: data.style.default_color
}
tabbarList.value = data.list?.filter((item: any) => item.is_show == 1)
.map((item: any) => {
return {
iconPath: item.unselected,
selectedIconPath: item.selected,
text: item.name,
link: item.link,
pagePath: item.link.path
}
})
}
const nativeTabbar = [
'/pages/index/index',
'/pages/order/order',
'/pages/user/user'
]
const handleChange = (index: number) => {
const selectTab = tabbarList.value[index]
const navigateType = nativeTabbar.includes(selectTab.link.path) ? 'switchTab' : 'reLaunch'
navigateTo(selectTab.link, navigateType)
}
onMounted(() => {
getTabbarList()
})
</script>

View File

@@ -0,0 +1,72 @@
import { reactive } from 'vue'
/**
* @description 触碰屏幕钩子函数
* @return { Function } 暴露钩子
*/
export function useTouch() {
// 最小移动距离
const MIN_DISTANCE = 10
const touch = reactive<any>({
direction: '',
deltaX: 0,
deltaY: 0,
offsetX: 0,
offsetY: 0
})
/**
* @description 计算距离
* @return { string } 空字符串
*/
const getDirection = (x: number, y: number) => {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal'
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical'
}
return ''
}
/**
* @description 重置参数
*/
const resetTouchStatus = () => {
touch.direction = ''
touch.deltaX = 0
touch.deltaY = 0
touch.offsetX = 0
touch.offsetY = 0
}
/**
* @description 触发
*/
const touchStart = (event: any) => {
resetTouchStatus()
const events = event.touches[0]
touch.startX = events.clientX
touch.startY = events.clientY
}
/**
* @description 移动
*/
const touchMove = (event: any) => {
const events = event.touches[0]
touch.deltaX = events.clientX - touch.startX
touch.deltaY = events.clientY - touch.startY
touch.offsetX = Math.abs(touch.deltaX)
touch.offsetY = Math.abs(touch.deltaY)
touch.direction = touch.direction || getDirection(touch.offsetX, touch.offsetY)
}
return {
touch,
resetTouchStatus,
touchStart,
touchMove
}
}

View File

@@ -0,0 +1,437 @@
<template>
<view class="tabs">
<u-sticky :enable="isFixed" :bg-color="stickyBgColor" :offset-top="top" :h5-nav-height="0">
<view
:id="id"
:style="{
background: bgColor
}"
>
<scroll-view
:style="{ height: height + 'rpx' }"
scroll-x
class="scroll-view"
:scroll-left="scrollLeft"
scroll-with-animation
>
<view class="scroll-box" :class="{ 'tabs-scorll-flex': !isScroll }">
<view
v-for="(item, index) in list"
class="tab-item line1"
:id="'tab-item-' + index"
:key="index"
@tap="clickTab(index)"
:style="[tabItemStyle(index)]"
>
<u-badge
:count="item[count] || item['dot'] || 0"
:offset="offset"
size="mini"
></u-badge>
{{ item[name] || item['name'] }}
</view>
<view v-if="showBar" class="tab-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</u-sticky>
<view
class="tab-content"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchcancel="onTouchEnd"
@touchend="onTouchEnd"
>
<!-- <view class="tab-track" :class="{'tab-animated': animated}" :style="[trackStyle]"> -->
<view>
<slot></slot>
</view>
<!-- </view> -->
</view>
</view>
</template>
<script lang="ts" setup>
import { getRect } from '@/utils/util'
import {
ref,
reactive,
computed,
watch,
provide,
nextTick,
onMounted,
getCurrentInstance
} from 'vue'
import { useTouch } from './hooks/useTouch'
// Touch 钩子
const { touch, resetTouchStatus, touchStart, touchMove } = useTouch()
const emit = defineEmits<{
(event: 'change', value: number): void
}>()
const props = withDefaults(
defineProps<{
isScroll?: boolean // 导航菜单是否需要滚动如只有2或者3个的时候就不需要滚动了此时使用flex平分tab的宽度
current?: number | string // 当前活动tab的索引
height?: number | string // 导航栏的高度和行高
fontSize?: number | string // 字体大小
duration?: number | string // 过渡动画时长, 单位ms
activeColor?: number | string // 选中项的主题颜色
inactiveColor?: number | string // 未选中项的颜色
barWidth?: number | string // 菜单底部移动的bar的宽度单位rpx
barHeight?: number // 移动bar的高度
gutter?: number | string // 单个tab的左或有内边距左右相同
bgColor?: number | string // 导航栏的背景颜色
name?: string // 读取传入的数组对象的属性(tab名称)
count?: string // 读取传入的数组对象的属性(徽标数)
offset?: number[] // 徽标数位置偏移
bold?: boolean // 活动tab字体是否加粗
activeItemStyle?: any // 当前活动tab item的样式
showBar?: boolean // 是否显示底部的滑块
barStyle?: any // 底部滑块的自定义样式
itemWidth?: string // 标签的宽度
isFixed?: boolean // 吸顶是否固定
top?: number | string // 吸顶顶部距离
stickyBgColor?: string // 吸顶颜色
swipeable?: boolean // 是否允许滑动切换
// animated: boolean // 切换动画
}>(),
{
isScroll: true,
current: 0,
height: 80,
fontSize: 28,
duration: 0.3,
activeColor: 'var(--color-primary)',
inactiveColor: '#333',
barWidth: 40,
barHeight: 4,
gutter: 30,
bgColor: '#FFFFFF',
name: 'name',
count: 'count',
offset: [5, 20],
bold: true,
activeItemStyle: {},
showBar: true,
barStyle: {},
itemWidth: 'auto',
isFixed: false,
top: 0,
stickyBgColor: '#FFFFFF',
swipeable: true
// animated: true
}
)
const list = ref<any>([])
const childrens = ref<any>([])
const scrollLeft = ref<number>(0) // 滚动scroll-view的左边滚动距离
const tabQueryInfo = ref<any>([]) // 存放对tab菜单查询后的节点信息
const componentWidth = ref<number>(0) // 屏幕宽度单位为px
const scrollBarLeft = ref<number>(0) // 移动bar需要通过translateX()移动的距离
const parentLeft = ref<number>(0) // 父元素(tabs组件)到屏幕左边的距离
const id = ref<string>('cu-tab') // id值
const currentIndex = ref<any>(props.current)
const barFirstTimeMove = ref<boolean>(true) // 滑块第一次移动时(页面刚生成时),无需动画,否则给人怪异的感觉
const swiping = ref<boolean>(false)
//@ts-ignore
const ctx = getCurrentInstance()
// 监听tab的变化重新计算tab菜单的布局信息因为实际使用中菜单可能是通过
// 后台获取的如新闻app顶部的菜单获取返回需要一定时间所以list变化时重新获取布局信息
watch(
() => list.value,
async (n, o) => {
// list变动时重制内部索引否则可能导致超出数组边界的情况
if (!barFirstTimeMove.value && n.length !== o.length) {
currentIndex.value = 0
}
// 用$nextTick等待视图更新完毕后再计算tab的局部信息否则可能因为tab还没生成就获取就会有问题
await nextTick()
init()
}
)
watch(
() => props.current,
(nVal, oVal) => {
// 视图更新后再执行移动操作、
nextTick(() => {
currentIndex.value = nVal
scrollByIndex()
})
},
{ immediate: true }
)
// 移动bar的样式
const tabBarStyle = computed(() => {
const style = {
width: props.barWidth + 'rpx',
transform: `translate(${scrollBarLeft.value}px, -100%)`,
// 滑块在页面渲染后第一次滑动时,无需动画效果
'transition-duration': `${barFirstTimeMove.value ? 0 : props.duration}s`,
'background-color': props.activeColor,
height: props.barHeight + 'rpx',
opacity: barFirstTimeMove.value ? 0 : 1,
// 设置一个很大的值,它会自动取能用的最大值,不用高度的一半,是因为高度可能是单数,会有小数出现
'border-radius': `${props.barHeight / 2}px`
}
Object.assign(style, props.barStyle)
return style
})
// tab的样式
const tabItemStyle = computed(() => {
return (index) => {
let style: any = {
height: props.height + 'rpx',
'line-height': props.height + 'rpx',
'font-size': props.fontSize + 'rpx',
padding: props.isScroll ? `0 ${props.gutter}rpx` : '',
flex: props.isScroll ? 'auto' : '1',
width: `${props.itemWidth}rpx`
}
// 字体加粗
if (index == currentIndex.value && props.bold) style.fontWeight = 'bold'
if (index == currentIndex.value) {
style.color = props.activeColor
// 给选中的tab item添加外部自定义的样式
style = Object.assign(style, props.activeItemStyle)
} else {
style.color = props.inactiveColor
}
return style
}
})
// const trackStyle = computed(() => {
// if (!props.animated) return ''
// return {
// left: -100 * currentIndex.value + '%',
// 'transition-duration': props.duration + 's',
// '-webkit-transition-duration': props.duration + 's',
// }
// })
const updateTabs = () => {
list.value = childrens.value.map((item) => {
const { name, dot, active, inited } = item.event
const { updateRender } = item
return {
name,
dot,
active,
inited,
updateRender
}
})
// nextTick(() => {
// init()
// })
}
// 设置一个init方法方便多处调用
const init = async () => {
// 获取tabs组件的尺寸信息
const tabRect = await getRect('#' + id.value, false, ctx)
// tabs组件距离屏幕左边的宽度
parentLeft.value = tabRect.left
// tabs组件的宽度
componentWidth.value = tabRect.width
getTabRect()
}
// 点击某一个tab菜单
const clickTab = (index) => {
// 点击当前活动tab不触发事件
if (index == currentIndex.value) return
nextTick(() => {
currentIndex.value = index
scrollByIndex()
})
// 发送事件给父组件
emit('change', index)
}
// 查询tab的布局信息
const getTabRect = () => {
// 创建节点查询
const query: any = uni.createSelectorQuery().in(ctx)
// 历遍所有tab这里是执行了查询最终使用exec()会一次性返回查询的数组结果
for (let i = 0; i < list.value.length; i++) {
// 只要size和rect两个参数
query.select(`#tab-item-${i}`).fields({
size: true,
rect: true
})
}
// 执行查询,一次性获取多个结果
query.exec((res) => {
tabQueryInfo.value = res
// 初始化滚动条和移动bar的位置
scrollByIndex()
})
}
// 滚动scroll-view让活动的tab处于屏幕的中间位置
const scrollByIndex = () => {
// 当前活动tab的布局信息有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
const tabInfo = tabQueryInfo.value[currentIndex.value]
if (!tabInfo) return
// 活动tab的宽度
const tabWidth = tabInfo.width
// 活动item的左边到tabs组件左边的距离用item的left减去tabs的left
const offsetLeft = tabInfo.left - parentLeft.value
// 将活动的tabs-item移动到屏幕正中间实际上是对scroll-view的移动
const scrollLefts = offsetLeft - (componentWidth.value - tabWidth) / 2
scrollLeft.value = scrollLefts < 0 ? 0 : scrollLefts
// 当前活动item的中点点到左边的距离减去滑块宽度的一半即可得到滑块所需的移动距离
const left = tabInfo.left + tabInfo.width / 2 - parentLeft.value
// 计算当前活跃item到组件左边的距离
scrollBarLeft.value = left - uni.upx2px(props.barWidth) / 2
// 第一次移动滑块的时候barFirstTimeMove为true放到延时中将其设置false
// 延时是因为scrollBarLeft作用于computed计算时需要一个过程需否则导致出错
if (barFirstTimeMove.value == true) {
setTimeout(() => {
barFirstTimeMove.value = false
}, 100)
}
// 更新子组件的显示
childrens.value.forEach((item, ind) => {
const active = ind === currentIndex.value
if (active !== item.event.active || !item.event.inited) {
item.updateRender(active)
}
})
}
// 子组件调用此函数而产生的事件通信
const handleChange = (event, updateRender) => {
childrens.value.push({ event: event, updateRender })
}
// 手指触摸
const onTouchStart = (event) => {
if (!props.swipeable) return
swiping.value = true
touchStart(event)
}
// 手指滑动
const onTouchMove = (event) => {
if (!props.swipeable || !swiping.value) return
touchMove(event)
}
// 手指滑动结束
const onTouchEnd = () => {
if (!props.swipeable || !swiping.value) return
const minSwipeDistance = 50
if (touch.direction === 'horizontal' && touch.offsetX >= minSwipeDistance) {
let index,
len = list.value.length,
curIndex = currentIndex.value
if (touch.deltaX <= 0) {
curIndex >= len - 1 ? (index = 0) : (index = curIndex + 1)
} else {
curIndex <= 0 ? (index = len - 1) : (index = curIndex - 1)
}
nextTick(() => {
currentIndex.value = index
scrollByIndex()
})
// 发送事件给父组件
emit('change', index)
}
swiping.value = false
}
onMounted(() => {
updateTabs()
})
provide('handleChange', handleChange)
provide('updateTabs', updateTabs)
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-box {
height: 100%;
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
.tab-fixed {
position: sticky;
top: 0;
width: 100%;
}
/* #ifdef H5 */
// 通过样式穿透隐藏H5下scroll-view下的滚动条
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.tab-item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
.tab-bar {
position: absolute;
bottom: 6rpx;
}
.tabs-scorll-flex {
display: flex;
justify-content: space-between;
}
// .tab-content {
// overflow: hidden;
// .tab-track {
// position: relative;
// width: 100%;
// height: 100%;
// }
// .tab-animated {
// display: flex;
// transition-property: left;
// }
// }
</style>

View File

@@ -0,0 +1,43 @@
<template>
<view
class="banner translate-y-0"
:class="{ 'px-[20rpx]': !isLargeScreen }"
v-if="content.data.length && content.enabled"
>
<LSwiper
:content="content"
:height="isLargeScreen ? '1100' : '321'"
:circular="true"
:effect3d="false"
:border-radius="isLargeScreen ? '0' : '14'"
interval="7000"
bgColor="transparent"
@change="handleChange"
></LSwiper>
</view>
</template>
<script setup lang="ts">
import LSwiper from '@/components/l-swiper/l-swiper.vue'
import {useAppStore} from "@/stores/app";
const emit = defineEmits(['change'])
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
},
isLargeScreen: {
type: Boolean
}
})
const {getImageUrl} = useAppStore();
const handleChange = (index: number) => {
emit('change', getImageUrl(props['content'].data[index].bg))
}
</script>

View File

@@ -0,0 +1,65 @@
<template>
<view class="bg-white p-[30rpx] flex text-[#101010] font-medium text-lg">
联系我们
</view>
<view
class="customer-service bg-white flex flex-col justify-center items-center mx-[36rpx] mt-[30rpx] rounded-[20rpx] px-[20rpx] pb-[100rpx]"
>
<view
class="w-full border-solid border-0 border-b border-[#f5f5f5] p-[30rpx] text-center text-[#101010] text-base font-medium">
{{ content.title }}
</view>
<view class="mt-[60rpx]">
<!-- 这样渲染是为了能在小程序中长按识别二维码 -->
<u-parse :html="richTxt"></u-parse>
<!-- <u-image width="200" height="200" border-radius="10rpx" :src="getImageUrl(content.qrcode)"/>-->
</view>
<view v-if="content.remark" class="text-sm mt-[40rpx] font-medium">{{ content.remark }}</view>
<view v-if="content.mobile" class="text-sm mt-[24rpx] flex flex-wrap">
<!-- #ifdef H5 -->
<a class="ml-[10rpx] phone text-primary underline" :href="'tel:' + content.mobile">
{{ content.mobile }}
</a>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="ml-[10rpx] phone text-primary underline" @click="handleCall">
{{ content.mobile }}
</view>
<!-- #endif -->
</view>
<view v-if="content.time" class="text-muted text-sm mt-[30rpx]">
服务时间{{ content.time }}
</view>
</view>
</template>
<script lang="ts" setup>
import {useAppStore} from '@/stores/app'
import { computed } from 'vue'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const {getImageUrl} = useAppStore()
const richTxt = computed(() => {
const src = getImageUrl(props.content.qrcode)
return `<img src="${src}" style="width: 100px;height: 100px; border-radius: 8px" />`
})
const handleCall = () => {
uni.makePhoneCall({
phoneNumber: String(props.content.mobile)
})
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,54 @@
<template>
<view
class="banner h-[200rpx] mx-[20rpx] mt-[20rpx] translate-y-0"
v-if="showList.length && content.enabled"
>
<swiper
class="swiper h-full"
:indicator-dots="showList.length > 1"
indicator-active-color="#4173ff"
:autoplay="true"
>
<swiper-item
v-for="(item, index) in showList"
:key="index"
@click="handleClick(item.link)"
>
<u-image
mode="widthFix"
width="100%"
height="100%"
:src="getImageUrl(item.image)"
:border-radius="14"
/>
</swiper-item>
</swiper>
</view>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app'
import { navigateTo } from '@/utils/util'
import { computed } from 'vue'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const handleClick = (link: any) => {
navigateTo(link)
}
const { getImageUrl } = useAppStore()
const showList = computed(() => {
return props.content.data?.filter((item: any) => item.is_show == '1') || []
})
</script>
<style></style>

View File

@@ -0,0 +1,75 @@
<template>
<div class="relative mx-[20rpx] mt-[20rpx]">
<swiper
class="py-[20rpx] bg-white rounded-lg"
:style="{
height: navList[0].length > content.per_line ? '288rpx' : '132rpx'
}"
:autoplay="false"
:indicator-dots="false"
@change="swiperChange"
>
<swiper-item v-for="(sItem, sIndex) in navList" :key="sIndex">
<view class="nav" v-if="navList.length && content.enabled">
<view
class="grid grid-rows-auto gap-y-3 w-full"
:style="{ 'grid-template-columns': `repeat(${content.per_line}, 1fr)` }"
>
<view
v-for="(item, index) in sItem"
:key="index"
class="flex flex-col items-center"
@click="handleClick(item.link)"
>
<u-image width="82" height="82" :src="getImageUrl(item.image)" alt=""/>
<view class="mt-[14rpx] text-xs">{{ item.name }}</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</div>
</template>
<script setup lang="ts">
import {ref, watch, computed} from 'vue'
import {useAppStore} from '@/stores/app'
import {navigateTo, sliceArray} from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const {getImageUrl} = useAppStore()
const swiperCurrent = ref<number>(0)
const navList = ref<Record<string, any>>([])
const pagesNum = computed<number>(() => {
return props.content.per_line * props.content.show_line
})
watch(
() => props.content.data,
(val) => {
const num = props.content.style === 1 ? val.length : pagesNum.value
navList.value = sliceArray(val, num)
console.log(navList.value)
},
{deep: true, immediate: true}
)
const handleClick = (link: any) => {
navigateTo(link)
}
const swiperChange = (e: any) => {
swiperCurrent.value = e.detail.current
}
</script>

View File

@@ -0,0 +1,93 @@
<template>
<!-- #ifndef H5 -->
<u-sticky h5-nav-height="0" bg-color="transparent">
<u-navbar
:class="{ 'fixed top-0 z-10': isLargeScreen }"
:is-back="false"
:is-fixed="true"
:title="metaData.title"
:custom-title="metaData.title_type == 2"
:border-bottom="false"
:title-bold="true"
:background="{ background: 'rgba(256,256, 256, 0)' }"
:title-color="percent > 0.5 ? '#000' : metaData.text_color == 1 ? '#fff' : '#000'"
>
<template #default>
<navigator
url="/pages/search/search"
class="mini-search"
hover-class="none"
:style="{ opacity: isLargeScreen ? 1 : percent }"
>
<u-icon name="search"></u-icon>
</navigator>
</template>
<template #title>
<image
class="!h-[54rpx]"
:src="metaData.title_img"
mode="widthFix"
></image>
</template>
</u-navbar>
</u-sticky>
<!-- #endif -->
<navigator
v-if="!isLargeScreen"
url="/pages/search/search"
class="px-[24rpx] mt-[24rpx] mb-[30rpx]"
:style="{ opacity: 1 - percent }"
hover-class="none"
>
<u-search
placeholder="请输入关键词搜索"
:height="72"
:disabled="true"
:show-action="false"
bgColor="#ffffff"
></u-search>
</navigator>
</template>
<script setup lang="ts">
import {computed} from "vue";
const props = defineProps({
pageMeta: {
type: Object,
default: () => []
},
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
},
isLargeScreen: {
type: Boolean
},
percent: {
type: Number
}
})
const metaData: any = computed(() => {
return props.pageMeta[0].content
})
</script>
<style scoped>
.mini-search {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
margin-left: 20rpx;
background-color: #ffffff;
border-radius: 50%;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2);
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<view
class="banner h-[200rpx] mx-[20rpx] mt-[20rpx] translate-y-0"
v-if="content.data.length && content.enabled"
>
<swiper
class="swiper h-full"
:indicator-dots="content.data.length > 1"
indicator-active-color="#4173ff"
:autoplay="true"
>
<swiper-item
v-for="(item, index) in content.data"
:key="index"
@click="handleClick(item.limk)"
>
<u-image
mode="aspectFit"
width="100%"
height="100%"
:src="getImageUrl(item.image)"
:border-radius="14"
/>
</swiper-item>
</swiper>
</view>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const handleClick = (link: any) => {
navigateTo(link)
}
const { getImageUrl } = useAppStore()
</script>
<style></style>

View File

@@ -0,0 +1,122 @@
<template>
<view class="user-info mb-[0rpx]" v-if="content.enabled">
<view class="flex items-center justify-between px-[30rpx] pb-[30rpx] pt-[40rpx]">
<view
v-if="isLogin"
class="flex items-center flex-1"
@click="router.navigate(userInfo.audit_status !== 1 ? '/packages/pages/apply/apply' : '/pages/user_set/user_set')"
>
<u-avatar :src="userInfo.avatar" :size="108"></u-avatar>
<view class="ml-[28rpx] text-black">
<view class="text-xl text-main font-medium">{{ userInfo.name }}</view>
<view
v-if="userInfo.audit_status !== 1"
class="text-base mt-1 text-main"
>
未入驻
</view>
<view
v-else-if="content.user_info == 2"
class="text-base mt-1 text-content"
@click.stop="copy(userInfo.mobile)"
>
手机号{{ userInfo.mobile || '-' }}
</view>
<view
v-else
class="text-base mt-1 text-content"
@click.stop="copy(userInfo.sn)"
>
工号: {{ userInfo.sn || '-' }}
</view>
</view>
</view>
<navigator
v-else
class="flex items-center text-black"
hover-class="none"
url="/pages/login/login"
>
<u-avatar src="/static/images/user/default_avatar.png" :size="108"></u-avatar>
<view class="ml-[28rpx] text-black">
<view class="text-xl text-main font-bold">立即登录/注册</view>
<view class="text-base text-content mt-1">
登录后查看更多
</view>
</view>
</navigator>
<navigator v-if="isLogin" hover-class="none" url="/pages/user_set/user_set">
<u-icon name="arrow-right" color="#999" :size="28"></u-icon>
</navigator>
<navigator v-else hover-class="none" url="/pages/login/login">
<u-icon name="arrow-right" color="#999" :size="28"></u-icon>
</navigator>
</view>
<view class="rounded-xl p-[10px]" v-if="userInfo.audit_status !== null">
<view class="flex justify-around py-2">
<view
class="text-center"
v-if="content.content?.includes('value1')"
@click="router.navigate('/packages/pages/bond/bond')"
>
<view class="text-[40rpx] font-bold pb-1">
{{ userInfo?.deposit || 0 }}
</view>
<view class="text-base text-muted">保证金</view>
</view>
<view
class="text-center"
v-if="content.content?.includes('value2')"
@click="router.navigate('/packages/pages/my_project/my_project')"
>
<view class="text-[40rpx] font-bold pb-1">
{{ userInfo?.server_count || 0 }}
</view>
<view class="text-base text-muted">服务项目</view>
</view>
<view
class="text-center"
v-if="content.content?.includes('value3')"
@click="router.navigate('/packages/pages/balance/balance')"
>
<view class="text-[40rpx] font-bold pb-1">
{{ userInfo?.money || 0 }}
</view>
<view class="text-base text-muted">佣金</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useCopy } from '@/hooks/useCopy'
import { useRouter } from 'uniapp-router-next'
const { copy } = useCopy()
const router = useRouter()
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
},
userInfo: {
type: Object,
default: () => ({})
},
isLogin: {
type: Boolean
}
})
</script>
<style lang="scss" scoped>
.user-info {
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div
class="user-service bg-white mx-[30rpx] mt-[30rpx] rounded-lg p-[30rpx]"
v-if="content.enabled"
>
<div
v-if="content.title"
class="title text-content text-base"
>
<div>{{ content.title }}</div>
</div>
<!-- 横排 -->
<div v-if="content.style == 1" class="grid grid-cols-4 gap-x-6">
<div
v-for="(item, index) in content.data"
:key="index"
class="flex flex-col items-center pt-[40rpx]"
@click="handleClick(item.link)"
v-show="Number(item.is_show)"
>
<u-image width="68" height="68" :src="getImageUrl(item.image)" alt="" />
<div class="mt-2 text-xs">{{ item.name }}</div>
</div>
</div>
<!-- 竖排 -->
<div v-if="content.style == 2">
<div
v-for="(item, index) in content.data"
:key="index"
class="flex items-center h-[100rpx] px-[24rpx]"
@click="handleClick(item.link)"
v-show="Number(item.is_show)"
>
<u-image width="52" height="52" :src="getImageUrl(item.image)" alt="" />
<div class="ml-[20rpx] flex-1">{{ item.name }}</div>
<div class="text-muted">
<u-icon name="arrow-right" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/stores/app'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
},
userInfo: {
type: Object,
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
const handleClick = (link: any) => {
console.log(link)
if (link.path == '/packages/pages/join_business/join_business' && props.userInfo.shop_id) {
link.path = '/packages/pages/business_detail/business_detail'
link.query = {
id: props.userInfo.shop_id
}
}
navigateTo(link)
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,23 @@
import { isDevMode } from '@/utils/env'
const envBaseUrl = import.meta.env.VITE_APP_BASE_URL || ''
let baseUrl = `${envBaseUrl}/`
/*
* 微信小程序在`VITE_APP_BASE_URL`存在或`dev`模式下
* 使用`VITE_APP_BASE_URL`的值
* 其他情况使用`[baseUrl]`,方便服务端替换
*/
//#ifdef MP-WEIXIN
baseUrl = isDevMode() || envBaseUrl ? baseUrl : '[baseUrl]'
//#endif
const config = {
version: '1.3.9', //版本号
baseUrl, //请求接口域名
urlPrefix: 'coachapi', //请求默认前缀
timeout: 60 * 1000 //请求超时时长
}
export default config

View File

@@ -0,0 +1,5 @@
//菜单主题类型
export enum AgreementEnum {
PRIVACY = 'privacy',
SERVICE = 'service'
}

View File

@@ -0,0 +1,55 @@
//菜单主题类型
export enum ThemeEnum {
LIGHT = 'light',
DARK = 'dark'
}
// 客户端
export enum ClientEnum {
MP_WEIXIN = 1, // 微信-小程序
OA_WEIXIN = 2, // 微信-公众号
H5 = 3, // H5
IOS = 5, //苹果
ANDROID = 6 //安卓
}
export const serviceEnum = {
'1': 'mnp',
'2': 'oa',
'3': 'h5'
}
export enum SMSEnum {
LOGIN = 'YZMDLSF',
BIND_MOBILE = 'BDSJHMSF',
CHANGE_MOBILE = 'BGSJHMSF',
FIND_PASSWORD = 'CSDLMMSF',
REGISTER = 'ZCYZMSF'
}
export enum SearchTypeEnum {
HISTORY = 'history'
}
// 用户资料
export enum FieldType {
NONE = '',
AVATAR = 'avatar',
USERNAME = 'account',
NICKNAME = 'nickname',
INTRODUCTION = 'introduction',
GENDER = 'gender'
}
// 支付结果
export enum PayStatusEnum {
SUCCESS = 'success',
FAIL = 'fail',
PENDING = 'pending'
}
// 页面状态
export enum PageStatusEnum {
LOADING = 'loading', // 加载中
NORMAL = 'normal', // 正常
ERROR = 'error', // 异常
EMPTY = 'empty' // 为空
}

View File

@@ -0,0 +1,9 @@
// 本地缓冲key
//token
export const TOKEN_KEY = 'staff_token'
// 搜索历史记录
export const HISTORY = 'staff_history'
export const BACK_URL = 'staff_back_url'

View File

@@ -0,0 +1,11 @@
// 本地缓冲key
//token
export const TOKEN_KEY = 'staff_token'
// 搜索历史记录
export const HISTORY = 'staff_history'
export const BACK_URL = 'staff_back_url'
export const PAY_STATUS_EVENT = 'event:payStatus'

View File

@@ -0,0 +1,22 @@
export enum ContentTypeEnum {
// json
JSON = 'application/json;charset=UTF-8',
// form-data 上传资源(图片,视频)
FORM_DATA = 'multipart/form-data;charset=UTF-8'
}
export enum RequestMethodsEnum {
GET = 'GET',
POST = 'POST'
}
export enum RequestCodeEnum {
SUCCESS = 1, //成功
FAILED = 0, // 失败
TOKEN_INVALID = -1 // TOKEN参数无效
}
export enum RequestErrMsgEnum {
ABORT = 'request:fail abort',
TIMEOUT = 'request:fail timeout'
}

View File

@@ -0,0 +1,5 @@
//菜单主题类型
export enum WithdrawEnum {
COMMISSION = 1, // 1-佣金提现;
EARNEST= 2 // 2-保证金提现;
}

View File

@@ -0,0 +1,112 @@
import { getPayWay, prepay } from '@/api/pay'
import { wxpay,alipay } from '@/utils/pay'
// import { toast } from '@/utils/util'
import { getClient } from '@/utils/client'
import { useUserStore } from '@/stores/user'
/**
* @description 支付函数钩子
* @param { emit } 是否刷新页面
* @return { Function } 暴露钩子
*/
export function usePay() {
/**
* @description 1. 初始化获取支付方式
* @param { params } 支付订单数据
*/
const initPayWay = async (params: any): Promise<void> => {
try {
const res = await getPayWay({
//支付方式
from: params.from || 'order',
scene: getClient()
})
return res
} catch (err) {
console.log('获取支付方式', err)
}
}
/**
* @description 2. 预支付
* @param { params } 支付订单数据
*/
const handlePayPrepay = async (params: any): Promise<void> => {
try {
const res = await prepay({
from: params.from || 'order',
pay_way: params.pay_way,
order_id: params.order_id
})
const param = JSON.stringify({
order_id: params.order_id,
from: params.from
})
// 支付方式:1-微信支付;2-支付宝支付;3-余额支付;
if (params.pay_way !== 3) {
handlePay(res, params)
} else {
uni.reLaunch({
url: `/bundle/pages/payment_result/payment_result?param=${param}`
})
uni.$u.toast('支付成功')
}
} catch (err) {
uni.$u.toast(err)
}
}
/**
* @description 3. 吊起支付并处理支付结构
* @param { res } 支付订单数据
*/
const handlePay = async (res: any, params: any): Promise<void> => {
try {
// // #ifdef MP
// await wxpay(res.config)
// uni.$u.toast('支付成功')
// // #endif
// // #ifdef H5
// await wxpay(res.config)
// // #endif
//支付方式:1-微信支付;2-支付宝支付;3-余额支付;
switch (params.pay_way) {
case 1:
await wxpay(res.config)
break;
case 2:
await alipay(res.config)
break;
default:
uni.$u.toast('支付异常')
}
const param = JSON.stringify({
order_id: params.order_id,
from: params.from
})
if (params.from === 'deposit') {
return uni.reLaunch({
url: '/bundle/pages/deposit/index?isPay=true'
})
}
uni.reLaunch({ url: `/bundle/pages/payment_result/payment_result?param=${param}` })
} catch (err) {
if (params.from === 'order') uni.reLaunch({ url: `/pages/order/index` })
if (Number(params.order_amount) == 0) {
return uni.$u.toast('下单成功')
}
uni.$u.toast('支付取消')
}
}
return {
initPayWay,
handlePayPrepay,
handlePay
}
}

View File

@@ -0,0 +1,10 @@
export function useCopy() {
const copy = (text: string) => {
uni.setClipboardData({
data: String(text)
})
}
return {
copy
}
}

View File

@@ -0,0 +1,122 @@
import { reactive, ref, watch } from 'vue'
import { isWeixinClient } from '@/utils/client'
import { getGeocoderCoordinate } from '@/api/app'
// #ifdef H5
import { getLocation as getWeChatLocation } from '@/hooks/wechat'
// #endif
import { editLocation } from '@/api/user'
export interface LocationType {
latitude: string | number
longitude: string | number
name?: string
city_id?: number | null
id?: string
}
export const location: LocationType = reactive({
latitude: '',
longitude: '',
name: '',
city_id: null,
id: ''
})
export function useLocation() {
const errorTitle = ref<string | null>(null)
const errorContent = ref<string | null>(null)
const showLocationModal = ref<boolean>(false)
// 获取微信或浏览器位置信息
const getLocationData = async () => {
return new Promise(async (resolve, reject) => {
try {
if (isWeixinClient()) {
// 微信客户端获取定位
try {
const wechatLocation: any = await getWeChatLocation()
location.latitude = wechatLocation.latitude
location.longitude = wechatLocation.longitude
editLocation({ latitude: location.latitude, longitude: location.longitude })
await reverseGeocode(
String(wechatLocation.latitude),
String(wechatLocation.longitude)
)
resolve(location)
} catch (err) {
errorTitle.value = '微信定位获取失败'
errorContent.value = '微信定位失败:' + (err?.errMsg || err)
console.error('微信定位失败:', err)
showLocationModal.value = true
reject(err)
}
} else {
// H5或其他客户端获取定位
uni.getLocation({
type: 'gcj02', // 常用坐标系
async success(res) {
location.latitude = res.latitude
location.longitude = res.longitude
await reverseGeocode(String(res.latitude), String(res.longitude))
editLocation({ latitude: res.latitude, longitude: res.longitude })
resolve(location)
},
fail(err) {
handleLocationFailure(err)
}
})
}
} catch (err) {
errorTitle.value = '定位获取失败'
errorContent.value = '定位失败原因:' + (err?.errMsg || err)
console.error('定位失败原因:', err)
showLocationModal.value = true
reject(err)
}
})
}
// 定位失败处理
const handleLocationFailure = (err: any) => {
if (!uni.getSystemInfoSync().locationEnabled) {
showLocationModal.value = true
errorTitle.value = '定位服务未开启'
errorContent.value = '请开启定位服务后重新进入应用'
} else {
showLocationModal.value = true
errorTitle.value = '定位权限未授权'
errorContent.value = '请在设置中打开授权,以便我们能够更好的提供服务。'
}
console.error('获取位置失败:', err)
}
const reverseGeocode = async (latitude: string, longitude: string) => {
try {
const data = await getGeocoderCoordinate({
location: `${latitude},${longitude}`
})
console.log(data)
setLocationData(data.result)
} catch (error) {
console.error('Error reverse geocoding location:', error)
}
}
const setLocationData = (data: LocationType | any) => {
// @ts-ignore
// Reflect.ownKeys(data).map(key => location[key] = data[key])
location.latitude = data.location.lat
location.longitude = data.location.lng
location.name = data.formatted_addresses.recommend
location.city_id = data.ad_info.city_code
}
return {
location,
errorTitle,
errorContent,
showLocationModal,
getLocationData,
setLocationData
}
}

View File

@@ -0,0 +1,21 @@
import { ref } from 'vue'
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
const isLock = ref(false)
const lockFn = async (...args: any[]) => {
if (isLock.value) return
isLock.value = true
try {
const res = await fn(...args)
isLock.value = false
return res
} catch (e) {
isLock.value = false
throw e
}
}
return {
isLock,
lockFn
}
}

View File

@@ -0,0 +1,156 @@
// #ifdef H5
import weixin from 'weixin-js-sdk'
import { useUserStore } from '@/stores/user'
import { apiJsConfig, getWxCodeUrl, OALogin } from '@/api/account'
//判断是否为安卓环境
function _isAndroid() {
const u = navigator.userAgent
return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1
}
export function getSignLink() {
if (typeof window.signLink === 'undefined' || window.signLink === '') {
window.signLink = location.href.split('#')[0]
}
return _isAndroid() ? location.href.split('#')[0] : window.signLink
}
export function wxOaConfig() {
return new Promise((resolve, reject) => {
apiJsConfig().then((res) => {
console.log('res:', res) //微信配置
weixin.config({
debug: false, // 开启调试模式
appId: res.appId, // 必填,公众号的唯一标识
timestamp: res.timestamp, // 必填,生成签名的时间戳
nonceStr: res.nonceStr, // 必填,生成签名的随机串
signature: res.signature, // 必填,签名
jsApiList: res.jsApiList, // 必填需要使用的JS接口列表
success: () => {
resolve('success')
},
fail: (res: any) => {
reject('weixin config is fail')
}
})
})
})
}
//获取微信登录url
export function getWxUrl() {
getWxCodeUrl({url: location.href}).then((res) => {
location.href = res.url
})
}
//微信授权
export function authLogin(code) {
return new Promise((resolve, reject) => {
OALogin({
code
}).then((res) => {
const { userStore } = useUserStore()
userStore.setToken(res.token)
resolve(res)
})
})
}
export function wxOaReady() {
return new Promise((resolve, reject) => {
weixin.ready(() => {
resolve('success')
console.log('111222')
})
})
}
export function wxOaShare(options: Record<any, any>) {
wxOaReady().then(() => {
const { shareTitle, shareLink, shareImage, shareDesc } = options
weixin.updateTimelineShareData({
title: shareTitle, // 分享标题
link: shareLink, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareImage // 分享图标
})
// 发送给好友
weixin.updateAppMessageShareData({
title: shareTitle, // 分享标题
link: shareLink, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareImage, // 分享图标
desc: shareDesc
})
// 发送到tx微博
weixin.onMenuShareWeibo({
title: shareTitle, // 分享标题
link: shareLink, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareImage, // 分享图标
desc: shareDesc
})
})
}
export function wxOaPay(options: Record<any, any>) {
console.log('options:', options)
return new Promise((reslove, reject) => {
wxOaReady()
.then(() => {
console.log('微信支付', options)
weixin.chooseWXPay({
timestamp: options.timeStamp, // 支付签名时间戳注意微信jssdk中的所有使用timestamp字段均为小写
nonceStr: options.nonceStr, // 支付签名随机串,不长于 32 位
package: options.package, // 统一支付接口返回的prepay_id参数值提交格式如prepay_id=***
signType: options.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: options.paySign, // 支付签名
success: (res: any) => {
reslove(res)
},
cancel: (res: any) => {
reject(res)
},
fail: (res: any) => {
reject(res)
}
})
})
.catch((err) => {
console.log(err)
})
})
}
export function wxOaAddress() {
return new Promise((resolve, reject) => {
wxOaReady().then(() => {
weixin.openAddress({
success: (res: any) => {
resolve(res)
},
fail: (res: any) => {
reject(res)
}
})
})
})
}
export function getLocation() {
return new Promise((reslove, reject) => {
wxOaReady().then(() => {
weixin.getLocation({
type: 'gcj02',
success: (res: any) => {
reslove(res)
},
fail: (res: any) => {
reject(res)
}
})
})
})
}
// #endif

22
staff_uniapp/src/main.ts Normal file
View File

@@ -0,0 +1,22 @@
import { createSSRApp } from 'vue'
import App from './App.vue'
import plugins from './plugins'
import router from './router'
import './styles/index.scss'
import { setupMixin } from './mixins'
import share from '@/utils/share'
export function createApp() {
const app = createSSRApp(App)
setupMixin(app)
app.use(plugins)
// #ifdef MP-WEIXIN
app.mixin(share)
// #endif
app.use(router)
return {
app
}
}

View File

@@ -0,0 +1,103 @@
{
"name" : "smam",
"appid" : "__UNI__F3A86A8",
"description" : "",
"versionName" : "1.3.9",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"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 */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wxba391e34eddf2cc7",
"setting" : {
"urlCheck" : false,
"es6" : true,
"minified" : true
},
"__usePrivacyCheck__" : true,
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "你的位置信息将用于小程序位置接口的效果展示"
}
},
"requiredPrivateInfos" : [
"getLocation",
"chooseAddress",
"chooseLocation",
"choosePoi",
"onLocationChange",
"startLocationUpdate",
"startLocationUpdateBackground"
]
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"h5" : {
"router" : {
"mode" : "history",
"base" : "/coach/"
},
"title" : "加载中",
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "A34BZ-FT5K6-DTPSC-E6RVP-JOHHV-WQB27"
}
}
}
}
}

View File

@@ -0,0 +1,5 @@
import { App } from 'vue'
import theme from './theme'
export function setupMixin(app: App) {
app.mixin(theme)
}

View File

@@ -0,0 +1,18 @@
import { useThemeStore } from '@/stores/theme'
import { useAppStore } from '@/stores/app'
export default {
computed: {
$theme() {
const themeStore = useThemeStore()
const appStore = useAppStore()
return {
primaryColor: themeStore.primaryColor,
pageStyle: themeStore.vars,
navColor: themeStore.navColor,
navBgColor: themeStore.navBgColor,
// title: appStore.getWebsiteConfig.shop_name
}
}
}
}

View File

@@ -0,0 +1,24 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="h-screen flex flex-col justify-center items-center">
<view>
<u-empty text="对不起,您访问的页面不存在" mode="data"></u-empty>
</view>
<view class="w-full px-[100rpx] mt-[40rpx]">
<router-navigate
class="bg-primary rounded-full text-btn-text leading-[80rpx] text-center"
to="/"
nav-type="reLaunch"
>
返回首页
</router-navigate>
</view>
</view>
</template>

View File

@@ -0,0 +1,76 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<view>
<view class="list">
<z-paging
auto-show-back-to-top
ref="paging"
v-model="dataList"
@query="queryList"
:fixed="false"
height="100%"
>
<view class="card u-flex justify-between" v-for="(item, index) in dataList" :key="index">
<view>
<view class="font-bold text-1xl">{{ item.change_type_desc }}</view>
<view class="mt-[16rpx] text-[24rpx] text-muted">{{ item.create_time }}</view>
</view>
<view class="font-bold text-1xl">
<view class="text-[#2189ff]" v-if="item.action == 2">{{ item.change_amount }}</view>
<view class="text-[#ff2c3c]" v-if="item.action == 1">{{ item.change_amount }}</view>
</view>
</view>
</z-paging>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue'
import { apiFinanceLists } from '@/api/app'
import { onLoad } from '@dcloudio/uni-app'
const dataList = ref<Array<any>>([])
const paging = shallowRef<any>(null)
const type = ref(1)
//获取佣金明细
const queryList = async (page_no: number, page_size: number) => {
try {
const { lists } = await apiFinanceLists({
type: type.value,
page_no,
page_size
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
onLoad(async (options:any) => {
type.value = options.type as number
})
</script>
<style lang="scss" scoped>
.list {
padding: 30rpx 24rpx;
height: 100vh;
.card {
background-color: #ffffff;
padding: 30rpx 24rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,278 @@
<template>
<view class="flex flex-col w-full h-full">
<view class="pt-[30rpx] pb-[80rpx] px-[20rpx] text-white topBg">
<view class="flex">
<u-image width="120rpx" height="120rpx" :src="examine"></u-image>
<view class="flex flex-col justify-between ml-2">
<view class="text-5xl">填写个人信息</view>
<view>快来获得入驻资格吧</view>
</view>
</view>
<view
style="background-color: rgba(255, 255, 255, 0.1)"
class="flex justify-between mt-4 p-[20rpx] rounded-lg"
>
<view class="flex flex-col items-center progressItem">
<view
style="background-color: rgba(255, 255, 255, 0.1)"
class="rounded-full w-[44rpx] h-[44rpx] flex items-center justify-center"
>
<view v-if="current == 0" class="w-[22rpx] h-[22rpx] bg-white rounded-full">
</view>
<view
v-else
class="flex justify-center items-center w-full h-full bg-white rounded-full"
>
<u-icon name="checkmark" :color="$theme.primaryColor"></u-icon>
</view>
</view>
<view class="mt-2">基本信息</view>
</view>
<view class="flex flex-col items-center progressItem">
<view
style="background-color: rgba(255, 255, 255, 0.1)"
class="rounded-full w-[44rpx] h-[44rpx] flex items-center justify-center"
>
<view v-if="current < 1" class="font-medium">2</view>
<view v-if="current == 1" class="w-[22rpx] h-[22rpx] bg-white rounded-full">
</view>
<view
v-if="current > 1"
class="flex justify-center items-center w-full h-full bg-white rounded-full"
>
<u-icon name="checkmark" :color="$theme.primaryColor"></u-icon>
</view>
</view>
<view class="mt-2">服务技能</view>
</view>
<view class="flex flex-col items-center progressItem">
<view
style="background-color: rgba(255, 255, 255, 0.1)"
class="rounded-full w-[44rpx] h-[44rpx] flex items-center justify-center"
>
<view v-if="current < 2">3</view>
<view v-if="current == 2" class="w-[22rpx] h-[22rpx] bg-white rounded-full">
</view>
<view
v-if="current > 2"
class="flex justify-center items-center w-full h-full bg-white rounded-full"
>
<u-icon name="checkmark" :color="$theme.primaryColor"></u-icon>
</view>
</view>
<view class="mt-2">个人资料</view>
</view>
</view>
</view>
<scroll-view
class="w-full h-full rounded-t-2xl min-h-0 pt-[40rpx] mt-[-30rpx] bg-white box-border relative z-50"
scroll-y
>
<base-data v-model="formData" v-if="current == 0"></base-data>
<skill-data v-model="formData" v-if="current == 1"></skill-data>
<personal-data
v-model="formData" v-if="current == 2"
@education-show="educationShow = true"
@nation-show="nationShow = true"
></personal-data>
</scroll-view>
<view class="px-[20rpx] py-[10rpx] bottom flex bg-white relative">
<u-button @click="toPer" v-if="current != 0" class="w-full" type="primary" plain>
上一步
</u-button>
<u-button @click="toNext" v-if="current != 2" class="w-full ml-2" type="primary">
下一步
</u-button>
<u-button @click="submit" v-if="current == 2" class="w-full ml-2" type="primary">
提交
</u-button>
</view>
<u-select
v-model="educationShow"
:z-index="9999"
:list="educationList"
safe-area-inset-bottom
@confirm="eductionConfirm"
/>
<u-select
v-model="nationShow"
:z-index="9999"
:list="nationList"
safe-area-inset-bottom
@confirm="nationConfirm"
/>
</view>
</template>
<script lang="ts" setup>
import {apply, getOtherList} from '@/api/app'
import { getCoachDetail } from '@/api/user'
import { ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { useUserStore } from '@/stores/user'
console.log(12)
import examine from '@/static/images/examine.png'
import BaseData from './components/base.vue'
import SkillData from './components/skill.vue'
import PersonalData from './components/personal-data.vue'
interface FormDataType {
name: string
age: number | null
gender: 1,
mobile: string
id_card: string
education: string
nation: string
province_id: string
city_id: string
region_id: string
address_detail: string
skill_id: string
goods_ids: number[]
id_card_back: string
id_card_front: string
portrait_shooting: string
work_photo: string
work_status: number
server_status: number
life_photo: string[]
regionStr: string
[key: string]: number | any
}
const userStore = useUserStore()
const educationShow = ref(false)
const nationShow = ref(false)
const educationList = ref([])
const nationList = ref([])
const formData = ref<FormDataType>({
name: '',
age: null,
gender: 1,
mobile: '',
id_card: '',
education: '',
nation: '',
province_id: '',
city_id: '',
region_id: '',
address_detail: '',
skill_id: '',
goods_ids: [],
id_card_back: '',
id_card_front: '',
portrait_shooting: '',
work_photo: '',
work_status: 1,
server_status: 1,
life_photo: [],
regionStr: '',
longitude: '',
latitude: ''
})
formData.value.regionStr = formData.value.province_id
? formData.value?.province_name +
' ' +
formData.value?.city_name +
' ' +
formData.value?.region_name
: ''
const current = ref(0)
const toPer = () => {
current.value != 0 && current.value--
}
const toNext = () => {
current.value != 2 && current.value++
}
const eductionConfirm = (value: any) => {
formData.value.education = value[0].label
}
const nationConfirm = (value: any) => {
formData.value.nation = value[0].label
}
const getDetail = async () => {
const data: any = await getCoachDetail()
if (Array.isArray(data) || Reflect.ownKeys(data).length <= 1) return
Reflect.ownKeys(formData.value).forEach((item: any) => {
formData.value[item] = data[item]
})
formData.value.regionStr = data.region_desc
formData.value.goods_ids = data.goods_lists?.map((item: { id: number }) => item.id)
formData.value.life_photo = data.life_photo?.map((item: { uri: string }) => item.uri)
}
const getOther = async () => {
// 获取其余数据
const res = await getOtherList()
educationList.value = res.education_lists?.map((item: any, index: any) => {
return { value: index, label: item }
})
nationList.value = res.nation_lists?.map((item: any, index: any) => {
return { value: index, label: item }
})
}
//提交
const submit = async () => {
if (!Number(formData.value.age)) {
return uni.showToast({
title: '请输入年龄',
icon: 'none'
})
}
if (!formData.value.portrait_shooting) {
return uni.showToast({
title: '请上传实拍照片',
icon: 'none'
})
}
await apply(formData.value)
await userStore.getUser()
await uni.navigateBack()
}
onLoad(async () => {
await getDetail()
await getOther()
})
onUnload(() => uni.$off(['selectProject']))
</script>
<style lang="scss" scoped>
.progressItem {
position: relative;
&:not(:last-child) {
&::before {
content: '';
position: absolute;
width: 150rpx;
height: 6rpx;
background-color: rgba(255, 255, 255, 0.5);
top: 20rpx;
left: 125rpx;
}
}
}
.topBg {
background: linear-gradient(180deg, #0963ea 0%, #0b66ef 32.51%, #6aa6ff 68.91%, #f6f7f8 97.06%);
}
.bottom {
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
</style>

View File

@@ -0,0 +1,152 @@
<template>
<view class="pb-4 px-[30rpx]">
<formItem title="姓名">
<view class="flex items-center">
<view class="bg-[#F8F9F9] p-2 rounded-lg">
<u-input v-model="data.name" placeholder="请输入您的姓名"></u-input>
</view>
<view class="ml-4">
<u-radio-group v-model="data.gender">
<u-radio :name="1">男士</u-radio>
<u-radio :name="2">女士</u-radio>
</u-radio-group>
</view>
</view>
</formItem>
<formItem title="年龄/岁">
<view class="w-full">
<view class="bg-[#F8F9F9] p-2 rounded-lg">
<u-input v-model="data.age" placeholder="请输入您的年龄/岁" :type="'number'"></u-input>
</view>
</view>
</formItem>
<formItem title="身份证号码">
<view class="">
<view class="bg-[#F8F9F9] p-2 rounded-lg">
<u-input v-model="data.id_card" placeholder="请输入身份证号码"></u-input>
</view>
</view>
</formItem>
<formItem title="身份证照片(人像面)">
<view class="">
<fileUpload
v-model="data.id_card_back"
:max-count="1"
width="203"
height="203"
></fileUpload>
</view>
</formItem>
<formItem title="身份证照片(国徽面)">
<view class="">
<fileUpload
v-model="data.id_card_front"
:max-count="1"
width="203"
height="203"
></fileUpload>
</view>
</formItem>
<formItem title="实拍照片(正面免冠素颜照)">
<view class="">
<fileUpload
v-model="data.portrait_shooting"
:max-count="1"
width="203"
height="203"
></fileUpload>
</view>
</formItem>
<formItem title="地址">
<view
@click="goPage('/packages/pages/location/index')"
class="bg-[#F8F9F9] p-3 rounded-lg text-info flex items-center justify-between"
>
<view>{{ data.regionStr || '请选择详细地址' }}</view>
<view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
</view>
</formItem>
<formItem title="详细地址">
<view class="bg-[#F8F9F9] p-2 rounded-lg text-info">
<u-input
v-model="data.address_detail"
type="textarea"
placeholder="请输入详细地址"
></u-input>
</view>
</formItem>
</view>
</template>
<script lang="ts" setup>
import fileUpload from '@/components/file-upload/file-upload.vue'
import formItem from './form-item.vue'
import { ref, computed } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { getGeocoderCoordinate } from '@/api/app'
const props = withDefaults(
defineProps<{
modelValue: any
}>(),
{
modelValue: {}
}
)
const emit = defineEmits(['update:modelValue'])
const data: any = computed({
set(value) {
emit('update:modelValue', value)
},
get() {
return props.modelValue
}
})
//页面跳转
const goPage = (url: string) => {
uni.navigateTo({ url: url })
}
onLoad(async (options: any) => {
// 监听选择的地址
uni.$on('choiceAddress', async(event) => {
data.value.longitude = event.longitude
data.value.latitude = event.latitude
try {
const addressInfo = await getGeocoderCoordinate({
location: `${data.value.latitude},${data.value.longitude}`
})
if (addressInfo.status == 0) {
let city_id = addressInfo.result.ad_info.city_code.substr(3, 6)
if (city_id == 110000 || city_id == 310000 || city_id == 210000 || city_id == 410000) {
city_id = city_id * 1
city_id += 100
}
data.value.city_id = city_id + ''
data.value.province_id = data.value.city_id.substr(0, 3) + '000'
data.value.region_id = addressInfo.result.ad_info.adcode
data.value.address_detail = addressInfo.result.address_component.street_number?.length > 0 ? addressInfo.result.address_component.street_number : addressInfo.result.address_component.street
data.value.regionStr = `${addressInfo.result.ad_info.province} ${addressInfo.result.ad_info.city} ${addressInfo.result.ad_info.district}`
} else {
uni.showToast({
title: addressInfo.message,
icon: 'none'
})
}
} catch (error) {
console.log('逆解析地址错误:', error)
}
})
})
onUnload(() => {
uni.$off(['choiceAddress'])
})
</script>

View File

@@ -0,0 +1,17 @@
<template>
<view class="mb-4">
<view>{{ title }}</view>
<view class="mt-2">
<slot />
</view>
</view>
</template>
<script lang="ts" setup>
const prop = defineProps({
title: {
type: String,
default: ''
}
})
</script>

View File

@@ -0,0 +1,73 @@
<template>
<view class="pb-4 px-[30rpx]">
<formItem title="工装照(头像)">
<view class="">
<file-upload
v-model="data.work_photo"
:max-count="1"
width="203"
height="203"
></file-upload>
</view>
</formItem>
<formItem title="生活照(用于个人详情页可上传多张)">
<view class="">
<file-upload
v-model="data.life_photo"
:max-count="4"
width="203"
height="203"
></file-upload>
</view>
</formItem>
<formItem title="学历">
<view
@click="emit('educationShow')"
class="bg-[#F8F9F9] p-3 rounded-lg text-info flex items-center justify-between"
>
<view>{{ data.education ? data.education : '请选择你的学历程度' }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
</formItem>
<formItem title="民族">
<view
@click="emit('nationShow')"
class="bg-[#F8F9F9] p-3 rounded-lg text-info flex items-center justify-between"
>
<view>{{ data.nation ? data.nation : '请选择你的名族' }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
</formItem>
</view>
</template>
<script lang="ts" setup>
import FileUpload from '@/components/file-upload/file-upload.vue'
import formItem from './form-item.vue'
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
modelValue: any
}>(),
{
modelValue: {}
}
)
const emit = defineEmits([
'update:modelValue',
'educationShow',
'nationShow'
])
const data: any = computed({
set(value) {
emit('update:modelValue', value)
},
get() {
return props.modelValue
}
})
</script>

View File

@@ -0,0 +1,105 @@
<template>
<view class="pb-4 px-[30rpx]">
<formItem title="服务技能">
<view
@click="show = true"
class="bg-[#F8F9F9] p-3 rounded-lg text-info flex items-center justify-between"
>
<view>{{ skillName ? skillName : '请选择你的服务技能' }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
</formItem>
<formItem title="服务项目">
<view
@click="toSelectProject"
class="bg-[#F8F9F9] p-3 rounded-lg text-info flex items-center justify-between"
>
<view>{{
data?.goods_ids.length != 0 ? `已选${data?.goods_ids.length}` : `请选择你的服务项目`
}}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
</formItem>
<u-select
v-model="show"
:z-index="9999"
:list="skillList"
safe-area-inset-bottom
@confirm="confirm"
/>
</view>
</template>
<script lang="ts" setup>
import { getSkillList } from '@/api/app'
import formItem from './form-item.vue'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'uniapp-router-next'
const emit = defineEmits(['update:modelValue'])
const props = withDefaults(
defineProps<{
modelValue: any
}>(),
{
modelValue: {}
}
)
const router = useRouter()
//显示弹框
const show = ref(false)
const skillList = ref<any[]>([])
const skillName = ref('')
const data: any = computed({
set(value) {
emit('update:modelValue', value)
},
get() {
return props.modelValue
}
})
const toSelectProject = () => {
if (!data.value.skill_id) {
uni.$u.toast('请选择技能')
return
}
router.navigate({
path: '/packages/pages/select_project/select_project',
query: {
id: data.value.skill_id,
title: skillName.value,
ids: data?.value.goods_ids.join(',')
}
})
}
const getSkill = async () => {
const res = await getSkillList()
skillList.value = res.map((item: any) => {
return { value: item.id, label: item.name }
})
if (data.value.skill_id) {
skillName.value = skillList.value.find(
(item: any) => item.value == data.value.skill_id
).label
}
}
//弹框确定
const confirm = (value: any) => {
data.value.goods_ids = []
data.value.skill_id = value[0].value
skillName.value = value[0].label
}
uni.$on('selectProject', (val) => {
console.log('选择了哦', val)
data.value.goods_ids = val
})
onMounted(() => getSkill())
</script>

View File

@@ -0,0 +1,63 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<view class="relative user-balance">
<image class="absolute w-full" :src="balanceBG"></image>
<Nav :percent="percent"></Nav>
<view class="px-[30rpx] mt-[40rpx] w-full pb-[40rpx]">
<card></card>
<statement :scrollTop="scrollTop" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Nav from './components/nav.vue'
import statement from './components/statement.vue'
import balanceBG from '../../static/images/balanceBG.png'
import { onPageScroll } from '@dcloudio/uni-app'
import card from './components/card.vue'
const scrollTop = ref<number>(0)
const percent = ref<number>(0)
onPageScroll((event: any) => {
scrollTop.value = event.scrollTop
const top = uni.upx2px(100)
percent.value = event.scrollTop / top > 1 ? 1 : event.scrollTop / top
})
</script>
<style lang="scss" scoped>
.user-balance {
// &::before {
// content: '';
// position: absolute;
// top: 0;
// right: 0;
// width: 100%;
// height: 400rpx;
// z-index: 2;
// background-size: contain;
// background-repeat: no-repeat;
// background-position: top right;
// background-image: url('@/packages/static/images/topBg.png');
// }
// &::after {
// content: '';
// position: absolute;
// top: -770px;
// left: 50%;
// transform: translateX(-50%);
// width: 1000px;
// height: 1000px;
// z-index: 1;
// border-radius: 1000px;
// background: linear-gradient(292deg,var(--color-primary) 29.15%, var(--color-primary-light-5) 69.66%);
// }
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<view class="w-full relative mt-[70rpx] z-10">
<view class="wrapper backdrop-blur-sm">
<view class="flex items-center">
<u-image
:src="userStore.userInfo.avatar"
width="60"
height="60"
border-radius="50%"
></u-image>
<view class="text-base text-white ml-2">
{{ userStore.userInfo.name }}
</view>
<view class="ml-auto mr-3 text-white" @click="toCashOutRecord">提现记录</view>
</view>
<view
class="flex justify-between"
style="margin-top: 32rpx"
>
<view>
<price
:content="userStore.userInfo.money"
main-size="72rpx"
minor-size="48rpx"
font-weight="900"
color="#ffffff"
></price>
<view class="mt-1 text-base text-white font-medium">
当前余额()
</view>
</view>
<view
class="text-primary text-center text-xs bg-white inline"
style="height: 72rpx; margin-top: 30px; padding: 18rpx 28rpx; border-radius: 30px 0 0 30px;"
@click="toCashOut()"
>
<text>我要提现</text>
<u-icon name="arrow-right" size="24"></u-icon>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import {useUserStore} from '@/stores/user'
import {useRouter} from "uniapp-router-next";
import { WithdrawEnum } from '@/enums/withdraw'
const router = useRouter()
const userStore = useUserStore()
const toCashOutRecord = () => {
uni.navigateTo({
url: `/packages/pages/cash_out_record/cash_out_record?apply_type=${WithdrawEnum.COMMISSION}`
})
}
//提现
const toCashOut = () => {
uni.navigateTo({
url: `/packages/pages/cash_out/cash_out?apply_type=${WithdrawEnum.COMMISSION}`
})
}
</script>
<style lang="scss" scoped>
.wrapper {
height: 316rpx;
padding: 32rpx 0 52rpx 32rpx;
flex-shrink: 0;
border-radius: 32rpx;
border: 2rpx solid #ffffffcc;
background: #ffffff4d;
box-shadow: 0 6rpx 72rpx 0 #10653333;
backdrop-filter: blur(48rpx);
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<u-navbar
:is-back="true"
title="我的余额"
:border-bottom="false"
:title-bold="true"
:fixed="false"
title-color="#fff"
back-icon-color="#fff"
:background="{
background: percent == 0 ? 'rgba(256,256, 256, 0)' : $theme.primaryColor
}"
/>
</template>
<script lang="ts" setup>
import { watch } from 'vue'
const props = defineProps({
percent: {
type: Number,
defualt: 0
}
})
watch((value: any) => {
console.log(value)
}, props.percent)
</script>

View File

@@ -0,0 +1,75 @@
<template>
<view>
<z-paging :use-page-scroll="true" ref="paging" v-model="listData" @query="queryList">
<view class="flex items-center pt-[40rpx] pb-[28rpx]">
<view class="block"></view>
<view class="text-xl font-medium ml-2">账户流水</view>
</view>
<view
v-for="(item, index) in listData"
:key="index"
class="bg-white px-[20rpx] py-[40rpx] mb-3 rounded-lg flex items-center"
>
<u-image :src="cashOutIcon" width="80rpx" height="80rpx"></u-image>
<view class="ml-3 flex flex-col justify-between">
<view class="font-medium text-xl">{{ item.change_type_desc }}</view>
<view class="text-xs text-muted mt-2">{{ item.create_time }}</view>
</view>
<view class="font-bold text-[40rpx] ml-auto">
<text :class="item.action == 1 ? 'text-primary' : 'text-warning'">
{{ item.action == 1 ? '+' : '-' }}{{ item.change_amount }}
</text>
</view>
</view>
</z-paging>
</view>
</template>
<script lang="ts" setup>
import { userMoneyList } from '@/api/account'
import { ref, shallowRef, watch } from 'vue'
import cashOutIcon from '@/packages/static/images/cashOutIcon.png'
import { onReachBottom } from '@dcloudio/uni-app'
import { WithdrawEnum } from '@/enums/withdraw'
const props = defineProps<{
scrollTop: number
}>()
const paging = shallowRef()
const listData = ref<any>([])
const queryList = async (page_no: number, page_size: number) => {
try {
const res = await userMoneyList({
page_no,
page_size,
type: WithdrawEnum.COMMISSION
})
console.log(res.lists)
paging.value.complete(res.lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
watch(() => props.scrollTop, (v) => {
paging.value?.updatePageScrollTop(v)
})
// 底部刷新
onReachBottom(() => {
paging.value?.doLoadMore()
})
</script>
<style lang="scss" scoped>
.block {
width: 8rpx;
height: 32rpx;
background-color: #0b66ef;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<view class="p-4">
<view
v-for="(item, index) in config.way_list"
:key="index"
class="mb-2 bg-white rounded-lg px-[30rpx] py-[30rpx] flex items-center"
@click="goPage('/packages/pages/bind_edit_cash_out/index?type='+item.type)"
>
<u-image
v-if="item.type == 1"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="wechatIcon"
></u-image>
<u-image
v-if="item.type == 2"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="aliIcon"
></u-image>
<u-image
v-if="item.type == 3"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="bankIcon"
></u-image>
<view class="ml-2 flex flex-col justify-between">
<view class="font-bold text-lg"
>提现至{{
item.type == 1 ? '微信' : item == 2 ? '支付宝' : '银行卡'
}}</view
>
<view class="text-sm text-info">
{{ item.config.name?.length ? item.config.name + ' ' + (item.type == 1 ? item.config.mobile : item.type == 2 ? item.config.account : item.type == 3 ? item.config.bank_card : '') : '请设置提现账户' }}
</view>
</view>
<u-icon class="ml-auto" name="arrow-right"></u-icon>
</view>
<EditPop :type="editType" ref="popRef" @confirm="getConfig"></EditPop>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import EditPop from './components/editPop.vue'
import { shallowRef } from 'vue'
import wechatIcon from '@/packages/static/images/wechat.png'
import aliIcon from '@/packages/static/images/ali.png'
import bankIcon from '@/packages/static/images/bank.png'
import { getCashOutConfig } from '@/api/cashOut'
import { onShow } from '@dcloudio/uni-app'
//提现配置
const config = ref<any>({})
//获取配置
const getConfig = async () => {
config.value = await getCashOutConfig()
}
const popRef = shallowRef()
const editType = ref(0)
const openEditPop = (type: number) => {
editType.value = type
popRef.value.open()
}
// 跳转页面
const goPage = (url: any) => {
uni.navigateTo({ url: url })
}
onShow(() => {
getConfig()
})
</script>

View File

@@ -0,0 +1,96 @@
<template>
<view>
<u-popup v-model="show" mode="bottom" height="50%" border-radius="14">
<view class="text-center py-4 font-bold text-lg">{{ typeText }}提现账户</view>
<view class="mt-2 px-4" v-if="type == 1">
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center">
<text class="w-[100rpx]">姓名</text>
<u-input v-model="config.name" class="flex-1 ml-2" />
</view>
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center mt-4">
<text class="w-[100rpx]">手机号</text>
<u-input v-model="config.mobile" class="flex-1 ml-2" />
</view>
<u-button @click="submit" class="mt-4" type="primary">确定</u-button>
</view>
<view class="mt-2 px-4" v-if="type == 2">
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center">
<text class="w-[100rpx]">姓名</text>
<u-input v-model="config.name" class="flex-1 ml-2" />
</view>
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center mt-4">
<text class="w-[100rpx]">账号</text>
<u-input v-model="config.account" class="flex-1 ml-2" />
</view>
<u-button @click="submit" class="mt-4" type="primary">确定</u-button>
</view>
<view class="mt-2 px-4" v-if="type == 3">
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center">
<text class="w-[120rpx]">姓名</text>
<u-input v-model="config.name" class="flex-1 ml-2" />
</view>
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center mt-4">
<text class="w-[120rpx]">开户行</text>
<u-input v-model="config.bank" class="flex-1 ml-2" />
</view>
<view class="bg-[#F6F7F8] rounded-md p-2 flex items-center mt-4">
<text class="w-[120rpx]">银行卡号</text>
<u-input v-model="config.bank_card" class="flex-1 ml-2" />
</view>
<u-button @click="submit" class="mt-4" type="primary">确定</u-button>
</view>
</u-popup>
</view>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { ref } from 'vue'
import { setWithdrawConfig, withdrawConfig } from '@/api/app'
import { nextTick } from 'vue'
const props = defineProps({
type: {
type: Number,
default: 0
}
})
const emit = defineEmits(['confirm'])
const config = ref({
name: '',
bank: '',
bank_card: '',
mobile: '',
account: ''
})
const typeText = computed(() => {
return props.type == 1 ? '微信' : props.type == 2 ? '支付宝' : '银行卡'
})
const submit = async () => {
await setWithdrawConfig({ type: props.type, config: config.value })
emit('confirm')
show.value = false
}
const getConfig = async () => {
await nextTick()
const res = await withdrawConfig({ type: props.type })
Object.keys(res.config).map((item) => {
//@ts-ignore
config.value[item] = res.config[item]
})
}
const show = ref(false)
const open = () => {
show.value = true
getConfig()
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,135 @@
<template>
<view class="bind">
<view v-if="type == 1">
<view>
<view class="font-bold text-2xl mb-[20rpx]">姓名</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.name" :clearable="false" placeholder="输入微信姓名" />
</view>
</view>
<view class="mt-[30rpx]">
<view class="font-bold text-2xl mb-[20rpx]">手机号</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.mobile" :clearable="false" placeholder="输入微信绑定的手机号" />
</view>
</view>
</view>
<view v-if="type == 2">
<view>
<view class="font-bold text-2xl mb-[20rpx]">姓名</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.name" :clearable="false" placeholder="输入支付宝姓名" />
</view>
</view>
<view class="mt-[30rpx]">
<view class="font-bold text-2xl mb-[20rpx]">账号</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.account" :clearable="false" placeholder="输入支付宝账号" />
</view>
</view>
</view>
<view v-if="type == 3">
<view>
<view class="font-bold text-2xl mb-[20rpx]">姓名</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.name" :clearable="false" placeholder="输入开户人姓名" />
</view>
</view>
<view class="mt-[30rpx]">
<view class="font-bold text-2xl mb-[20rpx]">开户行</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.bank" :clearable="false" placeholder="输入开户行名称" />
</view>
</view>
<view class="mt-[30rpx]">
<view class="font-bold text-2xl mb-[20rpx]">银行卡号</view>
<view>
<u-input class="w-full m-0" :custom-style="{'padding':'14rpx 20rpx','border-radius':'12rpx','background-color':'#f5f7f9'}" v-model="config.bank_card" :clearable="false" placeholder="输入银行卡号" />
</view>
</view>
</view>
<view class="footer">
<button class="bg-primary text-lg text-white" @click="confirm">
确定
</button>
<!-- <u-button type="primary" size="medium" @click="confirm">确定</u-button> -->
</view>
</view>
</template>
<script lang="ts" setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref, nextTick } from 'vue'
import { setWithdrawConfig, withdrawConfig } from '@/api/app'
const type = ref<number>(1)
const config = ref({
name: '',
bank: '',
bank_card: '',
mobile: '',
account: ''
})
const getConfig = async () => {
await nextTick()
const res = await withdrawConfig({ type: type.value })
Object.keys(res.config).map((item) => {
//@ts-ignore
config.value[item] = res.config[item]
})
}
//提交
const confirm = async() => {
await setWithdrawConfig({ type: type.value, config: config.value })
uni.navigateBack()
}
onLoad((option) => {
type.value = option?.type || 1
let titleName = '微信'
if(type.value == 2) {
titleName = '支付宝'
}
if(type.value == 3) {
titleName = '银行卡'
}
uni.setNavigationBarTitle({
title: titleName
})
getConfig()
})
</script>
<style lang="scss" scoped>
.bind {
height: 100vh;
background-color: #ffffff;
padding: 40rpx 24rpx;
// ::v-deep .input .u-input {
// background-color: #f5f7f9 !important;
// padding: 14rpx 20rpx !important;
// border-radius: 12rpx !important;
// }
}
// 底部按钮
.footer {
left: 0;
bottom: 0;
width: 100%;
position: fixed;
z-index: 11;
padding: 20rpx 30rpx 50rpx;
background-color: #ffffff;
box-shadow: 2rpx 2rpx 22rpx rgba($color: #000000, $alpha: 0.2);
::v-deep button {
width: 100% !important;
}
}
</style>

View File

@@ -0,0 +1,174 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<view class="relative">
<image class="absolute w-full" :src="bondBG"></image>
<Nav :percent="percent"></Nav>
<view class="px-[30rpx] mt-[40rpx] w-full pb-[260rpx]">
<card :bond-data="depositData.deposit"></card>
<bond_main :list-data="depositData.package_list" @change="handleFormData"></bond_main>
</view>
<bottom @click="recharge"></bottom>
</view>
<!-- 支付宝支付弹窗 -->
<u-popup
v-model="alipayShow"
mode="bottom"
height="600rpx"
safe-area-inset-bottom
border-radius="20"
closeable
@close="handleclose"
>
<view style="padding: 60rpx 30rpx;display: flex;justify-content: center;align-items: center;flex-direction: column;">
<view style="font-size: 50rpx;margin: 10rpx 0 20rpx;">{{ formData.money }}</view>
<view class="flex row-between m-t-50" style="width: 100%;justify-content: space-between;font-weight: bold;">
<text class="bold">支付方式</text>
<text class="bold">支付宝</text>
</view>
<view class="p-20 m-t-50 m-b-50" style="width: 100%;background-color: #9e9e9e40;padding: 15rpx 15rpx;margin: 50rpx 0;">请复制链接,粘贴至浏览器并支付</view>
<button @click="copyAlipayLink()" style="border-radius: 12rpx;width: 100%;height: 80rpx;line-height: 80rpx;font-size: 28rpx;color: white;background-color: #F36161;">复制链接</button>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import Nav from './components/nav.vue'
import bondBG from '../../static/images/bondBG.png'
import card from './components/card.vue'
import bond_main from './components/bond_main.vue'
import bottom from './components/bottom.vue'
import { ref, reactive } from 'vue'
import { onPageScroll } from '@dcloudio/uni-app'
import { getDepositList,apiRechargeDeposit } from '@/api/user'
import { getToken } from '@/utils/auth'
import { useUserStore } from '@/stores/user'
import { usePay } from '@/hooks/payment'
import { apiJumpPayPrepay, apiJumpPayResult } from '@/api/pay'
const { initPayWay, handlePayPrepay } = usePay()
const scrollTop = ref<number>(0)
const percent = ref<number>(0)
const formData = reactive({
pay_way: '' as any,
money: '' as any
})
const depositData = ref<any>({})
const orderId = ref()
const alipayLink = ref('')
const alipayShow = ref(false)
const token = getToken()
const userStore = useUserStore()
const getPackageList = async () => {
depositData.value = await getDepositList()
}
const handleFormData = (e:any) => {
formData.pay_way = e.pay_way
formData.money = e.money
}
//充值
const recharge = async() => {
if (!formData.money) return uni.$u.toast('请输入充值金额')
if (!formData.pay_way) return uni.$u.toast('请选择支付方式')
uni.showModal({
title: '温馨提示',
content: '是否确认下单支付?',
success: function (res) {
if (res.confirm) {
handlePlaceOrder()
}
}
})
}
// 下单处理
const handlePlaceOrder = async (): Promise<void> => {
uni.showLoading({
title: '订单提交中...',
mask: true
});
try {
const res = await apiRechargeDeposit(formData)
orderId.value = res.id
//支付宝支付
if(formData.pay_way == 2) {
uni.hideLoading();
// #ifdef MP-WEIXIN
alipayLink.value = `${import.meta.env.VITE_APP_BASE_URL || ''}coach/packages/pages/toAlipay/toAlipay?order_id=${orderId.value}&from=${res.type}&pay_way=${formData.pay_way}&key=${token}`
alipayShow.value = true
return
// #endif
//#ifdef H5
// let ua = navigator.userAgent.toLowerCase()
// if (ua.match(/MicroMessenger/i)) {
// //微信浏览器环境
// uni.reLaunch({
// url: `/packages/pages/toAlipay/toAlipay?order_id=${orderId.value}&from=${res.type}&pay_way=${formData.pay_way}&key=${token}`
// })
// return
// }
// 支付宝支付,这里只要提交表单
const result = await apiJumpPayPrepay({
from: res.type,
pay_way: formData.pay_way,
order_id: orderId.value
},token)
// 创建一个新的div元素
const docdiv = document.createElement('div');
// 渲染这个div中的表单res.data.pay_img就是后端返回的表单
docdiv.innerHTML = result.config;
// 进行元素追加渲染
document.body.appendChild(docdiv);
// 提交表单(alipaysubmit这个名字是和表单上的name一致的要注意别搞错了)
document.forms['alipaysubmit'].submit();
return
//#endif
}
//其他支付
handlePayPrepay({
from: res.type,
pay_way: formData.pay_way,
order_id: orderId.value
})
} catch (err) {
console.log('下单', err)
}
}
//支付宝弹窗关闭
function handleclose() {
const param = JSON.stringify({
order_id: orderId.value,
from: 'deposit'
})
uni.reLaunch({
url: `/pages/payment_result/payment_result?id=${orderId.value}&from=deposit`
})
}
//复制支付宝支付链接
function copyAlipayLink() {
uni.setClipboardData({
data: alipayLink.value
});
}
getPackageList()
onPageScroll((event: any) => {
scrollTop.value = event.scrollTop
const top = uni.upx2px(100)
percent.value = event.scrollTop / top > 1 ? 1 : event.scrollTop / top
})
</script>

View File

@@ -0,0 +1,95 @@
<template>
<view>
<view class="bg-white rounded-lg flex items-center mt-4 py-[20rpx] px-[32rpx]">
<text class="text-[40rpx] font-bold">¥</text>
<u-input placeholder="请输入自定义金额" class="ml-2" v-model="money"></u-input>
</view>
<view class="mt-[50rpx]">
<view class="flex items-center">
<view class="block"></view>
<view class="text-lg font-medium ml-2">支付方式</view>
</view>
<view class="mt-[28rpx]" v-for="item in payWayList" :key="item.pay_way" @click="choosePayWay = item.pay_way">
<view class="bg-white rounded-lg py-[26rpx] px-[32rpx] flex items-center">
<u-image width="50" height="50" :src="item.image"></u-image>
<view class="ml-2 font-semibold">{{ item.extra }}</view>
<checkboxMark background="#7E5008" class="ml-auto" :select="choosePayWay == item.pay_way"></checkboxMark>
</view>
</view>
</view>
<view class="mt-[50rpx]">
<view class="flex items-center">
<view class="block"></view>
<view class="text-lg font-medium ml-2">保证金套餐</view>
</view>
<view class="grid grid-cols-3 gap-2 mt-[28rpx]">
<view
v-for="(item, index) in listData"
:key="index"
class="relative bg-white p-[50rpx] flex flex-col items-center rounded-lg"
style="overflow: hidden;"
@click="money = item.money"
>
<view class="text-[28rpx] mb-[6rpx]">{{ item.name }}</view>
<view class="mb-[40rpx] text-center">
<price
fontWeight="900"
:content="item.money"
color="#7E5008"
main-size="48rpx"
minor-size="28rpx"
></price>
</view>
<view class="absolute bottom-0 bg-[#F9F1E6] w-full text-center text-[#7E5008] text-[24rpx] py-[6rpx]">
日限{{ item.order_limit }}
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import checkboxMark from '@/components/checkbox-mark/index.vue'
import { getPayWay } from '@/api/pay'
import { ref, watch } from 'vue'
const payWayList = ref<any>([])
const choosePayWay = ref(0)
const money = ref()
const emit = defineEmits(['change'])
const getPayWayList = async () => {
const data = await getPayWay()
choosePayWay.value = data.lists.find(element => element.is_default == 1)?.pay_way || 0
payWayList.value = data.lists
}
const prop = defineProps({
listData: {
type: Array,
default: () => [] as any
}
})
getPayWayList()
watch(
() => [money.value,choosePayWay.value],
([value1,value2]) => {
emit('change',{pay_way:choosePayWay.value,money:money.value})
},
{
immediate: true,
}
)
</script>
<style lang="scss" scoped>
.block {
width: 4px;
height: 36rpx;
background-color: #7e5008;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<view class="bottom fixed z-50 bottom-0 bg-white w-full pt-[20rpx] px-[30rpx] pb-[60rpx]">
<u-button v-if="true" :custom-style="btnClass" @click="beClick">缴纳保证金</u-button>
<view class="mt-[24rpx] text-[32rpx] font-bold text-center" @click="toCashOut">提现</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { WithdrawEnum } from '@/enums/withdraw'
const emit = defineEmits(['click'])
const btnClass = ref({
background: '#424755',
color: '#fff'
})
//点击
const beClick = () => {
console.log(uni.$u.color['error'])
emit('click')
}
//提现
const toCashOut = () => {
uni.navigateTo({
url: `/packages/pages/cash_out/cash_out?apply_type=${WithdrawEnum.EARNEST}`
})
}
</script>
<style lang="scss" scoped>
.bottom {
padding-bottom: calc(env(safe-area-inset-bottom) + 40rpx);
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<view class="w-full relative mt-[100rpx]">
<image
class="w-[100%] h-[316rpx]"
:src="bondCard"
style="border: 1px solid rgba(255, 255, 255, 0.4); border-radius: 30rpx"
></image>
<view class="absolute w-full z-30 top-0 p-4 text-white">
<view class="flex items-center">
<u-image
:src="userStore.userInfo.avatar"
width="60"
height="60"
border-radius="50%"
></u-image>
<view class="text-base text-white ml-2">
{{ userStore.userInfo.name }}
</view>
<view class="ml-auto text-white" @click="toCashOutRecord">提现记录</view>
</view>
<view class="mt-4">
<price
color="#fff"
minor-size="40rpx"
main-size="60rpx"
:content="bondData"
fontWeight="900"
></price>
</view>
<view class="text-lg mt-2">当前保证金</view>
<view class="absolute text-[#7E5008] right-[0rpx] bottom-[50rpx] bg-liu-shui"
@click="goPage('/packages/pages/account_detail/index?type=2')"
>
<span>账户流水</span>
<u-icon name="arrow-right" color="#7E5008" :size="24"></u-icon>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/stores/user'
import bondCard from '../../../static/images/bondCard.png'
import price from '@/components/price/price.vue'
import { WithdrawEnum } from '@/enums/withdraw'
const prop = defineProps({
bondData: {
type: [Number, String],
default: ''
}
})
const toCashOutRecord = () => {
uni.navigateTo({
url: `/packages/pages/cash_out_record/cash_out_record?apply_type=${WithdrawEnum.EARNEST}`
})
}
const userStore = useUserStore()
//页面跳转
const goPage = (url: string) => {
uni.navigateTo({ url: url })
}
</script>
<style scoped lang="scss">
.bg-liu-shui {
padding: 18rpx 16rpx 18rpx 28rpx;
background-color: #FFF;
border-radius: 30px 0 0 30px;
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<u-navbar
:is-back="true"
title="我的保证金"
:border-bottom="false"
:title-bold="true"
:fixed="false"
title-color="#fff"
back-icon-color="#fff"
:background="{
background: percent == 0 ? 'rgba(256,256, 256, 0)' : 'rgba(53,55, 66, 1)'
}"
/>
</template>
<script lang="ts" setup>
import { watch } from 'vue'
const props = defineProps({
percent: {
type: Number,
defualt: 0
}
})
watch((value: any) => {
console.log(value)
}, props.percent)
</script>

View File

@@ -0,0 +1,199 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<view class="business-detail h-full">
<page-status :status="status">
<view class="w-full" v-if="applyData.audit_status === 1 || applyData.audit_status === ''">
<Nav :data="applyData" :percent="percent"></Nav>
<l-swiper
:content="{
data: shopData?.shop_image || []
}"
name="uri"
height="750"
indicatorPos="bottomRight"
mode="number"
borderRadius="0"
/>
<view
class="relative z-10 px-[30rpx] pb-[200rpx]"
style="margin: -70rpx 0 0 0"
>
<side :data="shopData"></side>
<introduce :data="shopData"></introduce>
</view>
</view>
<view class="flex flex-col min-h-0 h-full" v-else>
<Nav :data="applyData" :percent="percent"></Nav>
<audit-view class="h-full" :data="applyData"></audit-view>
</view>
<bottom
:data="applyData"
:isJoin="isJoin"
@to-join="openJoin"
@cancel="openCancel"
@exit="openExit"
@re-apply="openReApply"
></bottom>
</page-status>
<!-- 提示弹窗 -->
<modal-popup
v-model:show="showModalPopup"
:title="modalPopupTitle"
:content="modalPopupContent"
@refresh="getDetail"
@confirm="handleCommand"
/>
</view>
</template>
<script lang="ts" setup>
import { onLoad, onPageScroll } from '@dcloudio/uni-app'
import { applyDetail, apply, cancelApply, ShopDetail, quitShop } from '@/api/business'
import { ref } from 'vue'
import { PageStatusEnum } from '@/enums/appEnums'
import { location } from '@/hooks/useLocation'
import { useUserStore } from '@/stores/user'
import Nav from './components/nav/index.vue'
import side from './components/side/index.vue'
import introduce from './components/introduce/index.vue'
import auditView from './components/audit-view/index.vue'
import bottom from './components/bottom/index.vue'
const userStore = useUserStore()
const status = ref(PageStatusEnum.LOADING)
const scrollTop = ref<number>(0)
const percent = ref<number>(0)
const id = ref(-1)
const detailData = ref<any>()
const command = ref<string>('')
const showModalPopup = ref<boolean>(false)
const modalPopupTitle = ref<string>('')
const modalPopupContent = ref<string>('')
// 是否能加入该商家
const isJoin = ref<boolean>(true)
//申请数据详情
const applyData = ref<any>({})
const getDetail = async () => {
applyData.value = await applyDetail({ id: id.value })
}
const shopData = ref<any>({})
const getShopDetail = async (longitude: number, latitude: number) => {
try {
shopData.value = await ShopDetail({ id: id.value, longitude, latitude })
status.value = PageStatusEnum.NORMAL
} catch (e) {
status.value = PageStatusEnum.ERROR
}
}
// 打开申请
const openJoin = async () => {
if(!!!isJoin.value) {
return uni.$u.toast('抱歉,只能添加本地的商家哦!')
}
command.value = 'apply'
showModalPopup.value = true
modalPopupTitle.value = '加入商家'
modalPopupContent.value = `确认加入“${shopData.value.name}”该商家吗?`
}
//取消申请
const openCancel = () => {
command.value = 'cancel'
showModalPopup.value = true
modalPopupTitle.value = '温馨提示'
modalPopupContent.value = `确认取消申请吗?`
}
// 退出商家
const openExit = () => {
command.value = 'exit'
showModalPopup.value = true
modalPopupTitle.value = '温馨提示'
modalPopupContent.value = `
为保证您的账户安全,在你提交的退出商家申请前,需满足以下条件 :
1、帐号处于安全状态。
2、帐号当前为有效帐号非冻结状态。
`
}
const openReApply = () => {
command.value = 'reapply'
showModalPopup.value = true
modalPopupTitle.value = '温馨提示'
modalPopupContent.value = `是否确定重新申请?`
}
const handleCommand = async () => {
switch (command.value) {
case 'apply':
await apply({ id: id.value })
detailData.value = await applyDetail({ id: id.value })
break
case 'cancel':
await cancelApply({ id: id.value })
detailData.value = await applyDetail({ id: id.value })
break
case 'exit':
await quitShop({ id: id.value })
detailData.value = await applyDetail({ id: id.value })
break
case 'reapply':
if (1 == applyData.value.type) {
await apply({ id: id.value })
} else {
await quitShop({ id: id.value })
}
detailData.value = await applyDetail({ id: id.value })
}
await getDetail()
userStore.getUser()
}
onLoad(async (option: any) => {
uni.showLoading({
title: '加载中'
})
id.value = option.id
if(option.isJoin) {
// 拿到的是字符串需要转会boolean类型
console.log('option.isJoin', option.isJoin)
console.log('JSON.parse(option.isJoin)', JSON.parse(option.isJoin))
isJoin.value = JSON.parse(option.isJoin)
}
await getShopDetail(location.longitude, location.latitude)
await getDetail()
uni.hideLoading()
})
onPageScroll((event: any) => {
scrollTop.value = event.scrollTop
const top = uni.upx2px(100)
percent.value = event.scrollTop / top > 1 ? 1 : event.scrollTop / top
})
</script>
<style lang="scss" scoped>
.business-detail {
:deep(.u-swiper-indicator) {
bottom: 100rpx !important;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<view class="h-full bg-white p-[30rpx]">
<view
class="flex justify-between items-center mb-10 p-[30rpx] bg-white rounded-xl"
style="box-shadow: 0 0px 10px 0 #141a231f;"
>
<view class="flex-none">
<u-image :src="data.logo" width="150" height="150"></u-image>
</view>
<view class="flex flex-col justify-between flex-1 h-full ml-3">
<view class="text-xl font-medium text-main">
{{ data.name }}
</view>
<view class="text-sm text-content mt-3">
{{ data.legal_person }}
</view>
</view>
<u-button type="primary" size="medium" @click="toCall">
联系TA
</u-button>
</view>
<view
v-if="data?.audit_status === 0"
class="bg-[#f5f7f9] text-main w-full p-4 rounded-lg text-center"
>
商家审核中
</view>
<view
v-if="data?.audit_status == 2"
class="bg-[#f5f7f9] text-main w-full p-4 rounded-lg text-center"
>
<view class="text-lg font-medium text-center mb-3">审核拒绝</view>
<view class="pb-3">
拒绝原因{{ data.audit_remark }}
</view>
</view>
</view>
</template>
<script lang="ts" setup>
const props = defineProps({
data: {
type: Object,
default: () => {
}
}
})
const toCall = () => {
uni.makePhoneCall({
phoneNumber: props.data.mobile
})
}
</script>

View File

@@ -0,0 +1,47 @@
<template>
<view class="bottom fixed z-50 bottom-0 bg-white w-full pt-[20rpx] px-[30rpx]">
<view v-if=" 1 === data.type && '' === data.audit_status">
<!-- <view class="">
isJoin:{{ isJoin }}
</view> -->
<u-button :type="isJoin ? 'primary' : 'info'" @click="$emit('toJoin')">加入商家</u-button>
</view>
<view v-if="1 === data.type">
<u-button v-if="0 === data?.audit_status" type="primary" @click="$emit('cancel')">取消申请</u-button>
<u-button v-if="2 === data?.audit_status|| 3 === data?.audit_status" type="default" plain
@click="$emit('reApply')">重新申请
</u-button>
<u-button v-if="1 === data?.audit_status" type="default" plain @click="$emit('exit')">退出商家</u-button>
</view>
<view v-if="2 === data.type">
<u-button v-if="0 === data?.audit_status" type="primary" @click="$emit('cancel')">取消申请</u-button>
<u-button v-if="2 === data?.audit_status" type="primary" @click="$emit('reApply')">重新申请</u-button>
<u-button v-if="3 === data?.audit_status" type="default" plain @click="$emit('exit')">退出商家</u-button>
</view>
</view>
</template>
<script lang="ts" setup>
import {join} from 'lodash-es'
import {watch} from 'vue'
const props = defineProps({
data: {
type: Object,
default: () => {
}
},
isJoin: {
type: Boolean,
}
})
const emit = defineEmits(['toJoin', 'reApply', 'exit', 'cancel'])
</script>
<style lang="scss" scoped>
.bottom {
box-shadow: 0 -4px 48px 0 #141a231f;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<view class="py-[28rpx] px-[24rpx] bg-white rounded-lg mt-4" v-if="data?.synopsis?.length">
<view class="text-lg font-normal font-bold title">商家介绍</view>
<view class="mt-4">
<view v-if="!data?.synopsis" class="text-info">暂无介绍</view>
<mp-html v-else :content="data?.synopsis"></mp-html>
</view>
</view>
</template>
<script lang="ts" setup>
const props = defineProps({
data: {
default: () => {},
type: Object
}
})
</script>
<style lang="scss" scoped>
.title {
position: relative;
&::before {
content: '';
position: absolute;
height: 70%;
width: 6rpx;
@apply bg-primary;
left: -10rpx;
top: 50%;
border-radius: 8rpx;
transform: translateY(-50%);
}
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<view>
<u-sticky h5-nav-height="0" :bg-color="data.audit_status == 1 ? 'transparent' : '#ffffff'">
<u-navbar
:is-back="true"
:is-fixed="data.audit_status == 1"
title="商家详情"
:immersive="true"
:border-bottom="false"
:title-bold="false"
z-index="11"
:background="{ background: `rgba(256,256, 256, ${data.audit_status == 1 ? percent : 1})` }"
:title-color="
percent > 0.5 ? '#000' : data.audit_status != 1 ? '#000' : '#fff'
"
:back-icon-color="
percent > 0.5 ? '#000' : data.audit_status != 1 ? '#000' : '#fff'
"
>
</u-navbar>
</u-sticky>
</view>
<!-- </u-sticky> -->
</template>
<script lang="ts" setup>
import { watch } from 'vue'
const props = defineProps({
percent: {
type: Number,
defualt: 0
},
data: {
type: Object,
default: () => {}
}
})
</script>

View File

@@ -0,0 +1,147 @@
<template>
<view class="p-[24rpx] rounded-lg bg-white">
<view class="font-black text-2xl mt-2">
{{ data?.name }}
</view>
<view class="flex items-center mb-3">
<u-rate
:count="5"
v-model="data.good_comment"
inactive-icon="star-fill"
active-icon="star-fill"
disabled
></u-rate>
<view class="text-[#E86016] font-bold ml-2">{{ data.good_comment }}</view>
<view class="mt-1 ml-4 text-muted">¥{{data?.money}}/</view>
</view>
<view class="mt-2">
<text class="text-primary text-xs font-medium">{{ data?.server_status == 1 ? '营业中' : '休息中' }}</text>
<text class="text-main font-medium text-xs ml-2"
>{{ getBusinessDays(data) }}{{ data?.business_start_time }}-{{
data?.business_end_time
}}</text
>
</view>
<view class="text-base text-content font-medium mt-2 introduct">
<text>{{data.category_name }}</text>
</view>
<view class="mt-4 flex items-center">
<view class="flex-1">
<view class="text-xs text-main font-medium">{{ data?.shop_address_detail }}</view>
<view class="flex items-center mt-1 text-xs text-muted font-medium">
<u-icon name="map" size="28"></u-icon>
<view class="ml-1">
{{ data?.province_name }}{{data?.city_name}}{{data?.region_name}}
</view>
</view>
</view>
<view class="flex flex-none ml-4 text-center">
<view @click="getLocation">
<view
class="w-[60rpx] h-[60rpx] rounded-full bg-page flex items-center justify-center"
>
<image src="@/packages/static/images/navigation.png" class="w-[44rpx] h-[44rpx]"></image>
</view>
<view class="text-[20rpx] text-content">导航</view>
</view>
<view class="ml-4" @click="toCall">
<view
class="w-[60rpx] h-[60rpx] rounded-full bg-page flex items-center justify-center"
>
<u-icon name="phone-fill" size="28"></u-icon>
</view>
<view class="text-[20rpx] text-content">电话</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import price from '@/components/price/price.vue'
const props = defineProps({
data: {
type: Object,
default: () => {
good_comment: 5
}
}
})
const getBusinessDays = (shopData: any) => {
const daysOfWeek = [
{ name: '周一', active: shopData?.monday },
{ name: '周二', active: shopData?.tuesday },
{ name: '周三', active: shopData?.wednesday },
{ name: '周四', active: shopData?.thursday },
{ name: '周五', active: shopData?.friday },
{ name: '周六', active: shopData?.saturday },
{ name: '周日', active: shopData?.sunday }
]
// 找到营业的日期
const openDays = daysOfWeek.filter((day) => day.active).map((day) => day.name)
// 根据连续的天数生成返回字符串
if (openDays.length === 0) return '无营业时间'
let result = ''
let start = openDays[0]
let end = start
for (let i = 1; i < openDays.length; i++) {
const currentDay = openDays[i]
const prevDay = openDays[i - 1]
// 检查是否连续
if (
daysOfWeek.findIndex((day) => day.name === currentDay) ===
daysOfWeek.findIndex((day) => day.name === prevDay) + 1
) {
end = currentDay
} else {
result += start === end ? start : `${start}${end}` + '、'
start = currentDay
end = start
}
}
result += start === end ? start : `${start}${end}`
return result
}
//打电话
const toCall = () => {
uni.makePhoneCall({
phoneNumber: props.data.mobile
})
}
const getLocation = () => {
uni.openLocation({
latitude: Number(props.data?.latitude),
longitude: Number(props.data?.longitude),
success: function () {
console.log('success')
}
})
}
</script>
<style lang="scss" scoped>
.introduct {
text {
position: relative;
margin-right: 20rpx;
&:not(:first-child) {
::before {
content: '|';
position: absolute;
left: -12rpx;
top: 50%;
transform: translateY(-50%);
}
}
}
}
</style>

View File

@@ -0,0 +1,152 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<!-- 余额保证金提现 -->
<view class="px-2 mt-4">
<view class="bg-white rounded-lg py-4 px-2">
<view>提现金额</view>
<view class="bg-page rounded-lg p-4 flex items-center mt-2">
<text class="text-4xl font-bold">¥</text>
<u-input
v-model="formData.money"
placeholder="请输入自定义金额"
class="ml-2"
></u-input>
</view>
<view class="mt-4 text-info flex items-center">
<text>可提现{{ formData.apply_type == WithdrawEnum.COMMISSION ? '余额':'保证金' }}</text>
<text v-if="formData.apply_type == WithdrawEnum.COMMISSION">¥{{ config.money }}</text>
<text v-if="formData.apply_type == WithdrawEnum.EARNEST">¥{{ config.deposit }}</text>
<text class="ml-2 text-primary" @click="allCashOut">全部提现</text>
<text class="ml-auto text-sm" v-if="formData.apply_type == WithdrawEnum.COMMISSION">提现手续费{{ config.service_charge }}%</text>
</view>
</view>
<view class="mt-4">
<view class="flex items-center">
<view class="block"></view>
<view class="text-lg font-medium ml-2">选择提现账户</view>
</view>
<view class="mt-4">
<view
@click="selectType(item.type)"
v-for="(item, index) in config.way_list"
:key="index"
class="bg-white p-4 rounded-lg flex items-center mb-2"
>
<u-image
v-if="item.type == 1"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="wechatIcon"
></u-image>
<u-image
v-if="item.type == 2"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="aliIcon"
></u-image>
<u-image
v-if="item.type == 3"
border-radius="16rpx"
width="50rpx"
height="50rpx"
:src="bankIcon"
></u-image>
<view class="flex flex-col justify-between ml-2">
<view class="font-bold text-lg"
>提现至{{
item.type == 1 ? '微信' : item.type == 2 ? '支付宝' : '银行卡'
}}</view
>
<view class="text-sm text-info" @click.stop="goPage('/packages/pages/bind_edit_cash_out/index?type='+item.type)">
<text>{{ item.config.name?.length ? item.config.name + ' ' + (item.type == 1 ? item.config.mobile : item.type == 2 ? item.config.account : item.type == 3 ? item.config.bank_card : '') : '请设置提现账户' }}</text>
<u-icon name="arrow-right" color="#909399" size="22rpx"></u-icon>
</view>
</view>
<checkbox-mark
:select="item.type == formData.type"
class="ml-auto"
></checkbox-mark>
</view>
<view class="text-muted text-sm">温馨提示{{ config.tips }}</view>
</view>
</view>
<bottom @click="cashOut"></bottom>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import bottom from './components/bottom.vue'
import { getCashOutConfig, toCashOut } from '@/api/cashOut'
import checkboxMark from '@/components/checkbox-mark/index.vue'
import wechatIcon from '@/packages/static/images/wechat.png'
import aliIcon from '@/packages/static/images/ali.png'
import bankIcon from '@/packages/static/images/bank.png'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { WithdrawEnum } from '@/enums/withdraw'
//提现配置
const config = ref<any>({})
//获取配置
const getConfig = async () => {
config.value = await getCashOutConfig()
}
//提现表单
const formData = ref<any>({
money: '',
type: '',
apply_type: 0, //1-佣金提现2-保证金提现;
})
//提现
const cashOut = async () => {
await toCashOut(formData.value)
uni.navigateTo({
url: `/packages/pages/cash_out_record/cash_out_record?apply_type=${formData.value.apply_type}`
})
}
//选择提现方式
const selectType = (type: number | string) => {
formData.value.type = type
}
const allCashOut = () => {
if(formData.value.apply_type == WithdrawEnum.COMMISSION) {
formData.value.money = config.value.money
}else {
formData.value.money = config.value.deposit
}
}
//页面跳转
const goPage = (url: string) => {
uni.navigateTo({ url: url })
}
onShow(() => {
getConfig()
})
onLoad((options) => {
formData.value.apply_type = options.apply_type || 0
})
</script>
<style lang="scss" scoped>
.block {
width: 5px;
height: 50rpx;
background-color: var(--color-primary, #0b66ef);
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<view class="bottom fixed z-50 bottom-0 bg-white w-full pt-[20rpx] px-[30rpx]">
<u-button type="primary" @click="beClick">提现</u-button>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const emit = defineEmits(['click'])
const btnClass = ref({
background: '#424755',
color: '#fff'
})
//点击
const beClick = () => {
console.log(uni.$u.color['error'])
emit('click')
}
</script>
<style lang="scss" scoped>
.bottom {
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
}
</style>

View File

@@ -0,0 +1,258 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="withdraw-detail" :style="$theme.pageStyle">
<view class="withdraw-detail-item">
<view class="py-[80rpx] bg-white rounded-t-[20rpx]">
<view class="text-[52rpx] font-black text-center">
¥{{ formData.money }}
</view>
<view v-if="formData.status == 1" class="mt-[16rpx] text-muted text-center">{{ formData.status_desc }}</view>
<view v-if="formData.status == 2 || formData.status == 4 || formData.status == 5" class="mt-[16rpx] text-primary text-center ">{{ formData.status_desc }}</view>
<view v-if="formData.status == 3 || formData.status == 6" class="mt-[16rpx] text-error text-center ">{{ formData.status_desc }}</view>
</view>
<view class="pt-[20rpx] px-[30rpx] bg-white rounded-b-[20rpx]">
<view class="flex justify-between withdrawal-content">
<view class="label">提现方式</view>
<view class="content">{{ formData.type_desc }}</view>
</view>
<!-- 微信收款码提现 -->
<view class="" v-if="formData.type == 1">
<view class="flex justify-between withdrawal-content">
<view class="label">姓名</view>
<view class="content">{{ formData.withdraw_config_snap.name }}</view>
</view>
<view class="flex justify-between withdrawal-content">
<view class="label">手机号码</view>
<view class="content">{{ formData.withdraw_config_snap.mobile }}</view>
</view>
</view>
<!-- 支付宝收款码提现 -->
<view v-if="formData.type == 2">
<view class="flex justify-between withdrawal-content">
<view class="label">姓名</view>
<view class="content">{{ formData.withdraw_config_snap.name }}</view>
</view>
<view class="flex justify-between withdrawal-content">
<view class="label">账号</view>
<view class="content">{{ formData.withdraw_config_snap.account }}</view>
</view>
</view>
<!-- 银行卡提现 -->
<view v-if="formData.type == 3">
<view class="flex justify-between withdrawal-content">
<view class="label">开户人</view>
<view class="content">{{ formData.withdraw_config_snap.name }}</view>
</view>
<view class="flex justify-between withdrawal-content">
<view class="label">开户行</view>
<view class="content">{{ formData.withdraw_config_snap.bank }}</view>
</view>
<view class="flex justify-between withdrawal-content">
<view class="label">银行卡号</view>
<view class="content">{{ formData.withdraw_config_snap.bank_card }}</view>
</view>
</view>
<view class="flex justify-between withdrawal-content">
<view class="label">提现时间</view>
<view class="content">{{ formData.create_time }}</view>
</view>
<view class="flex justify-between withdrawal-content" v-if="withdrawType == WithdrawEnum.COMMISSION">
<view class="label">服务费</view>
<view class="content">{{ formData.service_charge }}%</view>
</view>
<view class="flex justify-between withdrawal-content" v-if="withdrawType == WithdrawEnum.COMMISSION">
<view class="label">实际到账</view>
<view class="text-error">{{ formData.left_money }}</view>
</view>
</view>
<!-- 转账凭证 -->
<view
class="px-[30rpx] pt-[20rpx] mt-[20rpx] bg-white rounded-[20rpx]"
v-if="formData.status !== 1"
>
<view class="flex justify-between withdrawal-content">
<view class="label">审核时间</view>
<view class="content">{{ formData.update_time || '-' }}</view>
</view>
<view class="flex justify-between withdrawal-content" v-if="formData.status == 5">
<view class="label">转账凭证</view>
<!-- <u-image
height="160"
width="160"
:src="formData.transfer_proof"
v-if="formData.transfer_proof"
@click="showImage([formData.transfer_proof])"
>
</u-image> -->
<view class="text-primary" @click="showImage([formData.transfer_proof])">查看</view>
</view>
<view class="flex justify-between withdrawal-content" v-if="formData.status == 3 || formData.status == 6">
<view class="label">失败原因</view>
<view class="content">{{ formData.verify_remark || '-' }}</view>
</view>
</view>
</view>
<!-- <view class="check-withdrawal-record">
<view class="mt-[20rpx]">
<button
class="plain-primary h-[88rpx] leading-[88rpx] rounded-[30px] tohome"
@click="toHome"
>
返回首页
</button>
</view>
</view>
<view class="review-success-tips">* 审核成功后约72小时内到账请留意账户明细</view> -->
</view>
</template>
<script lang="ts" setup>
import { nextTick, reactive, ref, shallowRef, unref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { userWithdrawDetail } from '@/api/cashOut'
import PageStatus from '@/components/page-status/page-status.vue'
import { useRouter } from 'uniapp-router-next'
import { WithdrawEnum } from '@/enums/withdraw'
const router = useRouter()
const pageRef = shallowRef()
const withdrawId = ref<number>(0)
const withdrawType = ref(0)
const formData = ref<any>({
type: '', // 提现方式1-微信2-支付宝3-银行卡
typeMsg: '', // 提现类型描述
status: '', // 提现状态1-待审核2-审核成功3-审核失败;4-提现中5-转账成功6-转账失败
statusMSg: '', // 提现状态描述
money: '', // 是 提现金额
bank: '', // 否 type=3时需要 银行
bank_account: '', // 否 type=3时需要 账号
bank_account_name: '', // 否 type=3时需要 持卡人姓名
wechat_account: '', // 否 type=1时需要 微信账号
wechat_account_name: '', // 否 type=1时需要 微信真实姓名
alipay_account: '', // 否 type=2时需要 支付宝账号
alipay_account_name: '', // 否 type=2时需要 支付宝真实姓名
remark: '' // 否 提现备注
})
// 获取提现申请详情
const getWithdrawDetail = async () => {
try {
const data = await userWithdrawDetail({
id: withdrawId.value
})
Reflect.ownKeys(data).map((item: any) => {
formData[item] = data[item]
})
formData.value = data
// unref(pageRef).close()
} catch (error) {
// unref(pageRef).show({
// text: error,
// mode: 'order'
// })
console.log('获取提现详情', error)
}
}
const toRecord = () => {
router.redirectTo('/packageA/pages/withdraw_record/withdraw_record')
}
const toHome = () => {
router.reLaunch('/pages/index/index')
}
const showImage = (list: any) => {
uni.previewImage({
urls: list,
current: 1
})
}
onLoad((options: any) => {
console.log('cash_out_detail')
console.log('options ====>>>', options)
withdrawType.value = options?.withdrawType || 0
withdrawId.value = options?.id || ''
nextTick(() => {
getWithdrawDetail()
})
// await nextTick()
// try {
// if (!options?.id) {
// throw new Error('请传入详情ID')
// }
// withdrawId.value = options?.id || ''
// await getWithdrawDetail()
// } catch (error) {
// // unref(pageRef).show({
// // text: error,
// // mode: 'order'
// // })
// // console.log('商品详情报错', error)
// }
})
</script>
<style lang="scss" scoped>
.withdraw-detail {
padding: 20rpx 30rpx;
// padding-bottom: 100px;
.label {
color: #999
}
.content {
color: #666
}
.withdraw-detail-item {
.withdrawal-content {
padding-bottom: 20rpx;
}
}
.check-withdrawal-record {
padding: 40rpx 0rpx 40rpx 0;
margin: 0 30rpx;
}
.review-success-tips {
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
color: #999;
}
}
button::after {
border: none;
}
.tohome {
background-color: white !important;
}
</style>

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