初始版本
38
uniapp/src/App.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<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 { apiIndexVisit } from '@/api/app'
|
||||
import { getClient } from '@/utils/client'
|
||||
import { useLocation } from '@/hooks/useLocation'
|
||||
|
||||
const { getConfig, getSystemInfo } = useAppStore()
|
||||
const { getUser } = useUserStore()
|
||||
const { getTheme } = useThemeStore()
|
||||
const { getLocationData } = useLocation()
|
||||
|
||||
onLaunch(async () => {
|
||||
await getTheme()
|
||||
await getConfig()
|
||||
getSystemInfo()
|
||||
getLocationData()
|
||||
// #ifdef H5
|
||||
// const { status, page_status, page_url } = appStore.getH5Config
|
||||
// if (status == 0) {
|
||||
// if (page_status == 1) return (location.href = page_url)
|
||||
// uni.reLaunch({ url: '/pages/empty/empty' })
|
||||
// }
|
||||
// #endif
|
||||
await getUser()
|
||||
await apiIndexVisit({
|
||||
terminal: getClient()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #f6f7f8;
|
||||
}
|
||||
</style>
|
||||
76
uniapp/src/api/account.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import request from '@/utils/request'
|
||||
import { client } from '@/utils/client'
|
||||
|
||||
/** Login Start **/
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 微信登录
|
||||
*/
|
||||
export const apiWechatAuthLogin = (params: any) =>
|
||||
request.post({ url: '/login/oaLogin', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 手机号登录--发送验证码
|
||||
*/
|
||||
export const apiCaptchaLogin = (params: any) =>
|
||||
request.post({ url: '/login/captcha', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 手机号密码/手机号验证码登录
|
||||
*/
|
||||
export const login = (params: any) =>
|
||||
request.post({ url: '/login/account', data: { ...params, terminal: client } })
|
||||
|
||||
// 小程序-登录
|
||||
export function mnpLogin(data: Record<string, any>) {
|
||||
return request.post({ url: '/login/mnpLogin', data })
|
||||
}
|
||||
|
||||
//注册
|
||||
export function register(data: Record<string, any>) {
|
||||
return request.post({ url: '/login/register', data: { ...data, channel: client } })
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 小程序静默登录
|
||||
*/
|
||||
export const apiSilentLogin = (params: any) =>
|
||||
request.post({ url: '/login/silentLogin', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 退出登录
|
||||
*/
|
||||
export const apiLoginOut = (params: any) => request.post({ url: '/login/logout', data: params })
|
||||
/** Login End **/
|
||||
|
||||
/** Register Start **/
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 手机号注册--发送验证码
|
||||
*/
|
||||
export const apiCaptchaRegister = (params: any) =>
|
||||
request.post({ url: '/register/captcha', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 手机号注册
|
||||
*/
|
||||
export const apiAccountRegister = (params: any) =>
|
||||
request.post({ url: '/register/register', data: params })
|
||||
/** Register End **/
|
||||
|
||||
//更新微信小程序头像昵称
|
||||
export function updateUser(data: Record<string, any>, header: any) {
|
||||
return request.post({ url: '/login/updateUser', data, header })
|
||||
}
|
||||
143
uniapp/src/api/app.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import request from '@/utils/request'
|
||||
// #ifdef H5
|
||||
import { getSignLink } from '@/hooks/wechat'
|
||||
// #endif
|
||||
|
||||
//发送短信
|
||||
export function smsSend(data: any) {
|
||||
return request.post({ url: '/sms/sendCode', data: data })
|
||||
}
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 获取公共配置
|
||||
*/
|
||||
export const apiConfigGet = () => request.get({ url: '/config/config' })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 访问量
|
||||
*/
|
||||
export const apiIndexVisit = (params: any) => request.post({ url: '/index/visit', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 访问量
|
||||
*/
|
||||
export const getTabbarConfig = () => request.post({ url: '/decorate/tabbar' })
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 获取公共配置
|
||||
*/
|
||||
// 获取政策协议
|
||||
export const apiPolicyAgreement = () => request.get({ url: '/config/agreement' })
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 获取客服信息
|
||||
*/
|
||||
export const apiContactService = () => request.get({ url: '/user/customerService' })
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 微信消息订阅
|
||||
*/
|
||||
export const apiSubscribe = () => request.get({ url: '/subscribe/lists' })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 支付方式
|
||||
*/
|
||||
export const apiPayPayWay = (params: any) => request.get({ url: '/order/payWay', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 预支付
|
||||
*/
|
||||
|
||||
// 获取支付状态
|
||||
export function getPayStatus(data: any) {
|
||||
return request.get({ url: '/mall.pay/status', data: data }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 获取支付结果
|
||||
export function getPayResult(params: any,token?:string) {
|
||||
return request.get({ url: '/pay/getPayResult', data: params, header: {token:token} })
|
||||
}
|
||||
|
||||
export const apiPayPrepay = (params: any) => request.post({ url: '/pay/prepay', data: params })
|
||||
|
||||
export const apiJumpPayPrepay = (params: any,token:string) => request.post({ url: '/pay/prepay', data: params, header: {token:token}})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 公众号登录
|
||||
*/
|
||||
export const apiOALogin = (params: any) => request.post({ url: '/login/oaLogin', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 公众号-获取授权url
|
||||
*/
|
||||
export function apiCodeUrlGet(data: Record<string, any>) {
|
||||
return request.get({ url: '/login/codeUrl', data })
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 微信sdk配置
|
||||
*/
|
||||
export const apiJsConfig = () =>
|
||||
request.get({ url: '/wechat/jsConfig', data: { url: getSignLink() } })
|
||||
|
||||
/**
|
||||
* @param { Object } params { address: 广州市番禺区xxx }
|
||||
* @return { Promise }
|
||||
* @description 地址逆解析
|
||||
*/
|
||||
export const getGeocoder = (params: any) => request.get({ url: '/index/geocoder', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params { location: xxx,xxx }
|
||||
* @return { Promise }
|
||||
* @description 地址逆解析
|
||||
*/
|
||||
export const getGeocoderCoordinate = (params: any) =>
|
||||
request.get({ url: '/index/geocoderCoordinate', data: params })
|
||||
|
||||
export function uploadImage(file: any, token?: string) {
|
||||
return request.uploadFile({
|
||||
url: '/upload/image',
|
||||
filePath: file,
|
||||
name: 'file',
|
||||
header: {
|
||||
token
|
||||
},
|
||||
fileType: 'image'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const getAddress = (params: any) =>
|
||||
request.get({ url: '/index/address', data: params })
|
||||
|
||||
|
||||
|
||||
// 获取小程序码
|
||||
export function getMiniQrCode(data: any) {
|
||||
return request.get({ url: '/share/getMnpQrCode', data: data }, { isAuth: false })
|
||||
}
|
||||
|
||||
// 海报商品信息
|
||||
export function getPosterGoods(data: any) {
|
||||
return request.get({ url: '/mall.goods/detail_base64', data: data }, { isAuth: true })
|
||||
}
|
||||
21
uniapp/src/api/coach.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 技能列表
|
||||
*/
|
||||
export const apiSkillLists = () => request.get({ url: '/coach/skillLists' })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 服务师傅
|
||||
*/
|
||||
export const apiCoachLists = (params: any) => request.get({ url: '/coach/lists', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 服务师傅详情
|
||||
*/
|
||||
export const apiCoachDetail = (params: any) => request.get({ url: '/coach/detail', data: params })
|
||||
45
uniapp/src/api/goods.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 获取商品详情
|
||||
*/
|
||||
export const apiGoodsDetail = (params) => request.get({ url: '/goods/detail', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 商品收藏
|
||||
*/
|
||||
export const apiGoodsCollection = (params) => request.post({ url: '/goods/collect', data: params })
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 获取预约时间
|
||||
*/
|
||||
export const apiAppointTime = (params: any) => request.get({ url: '/order/getCoachServerTime', data: params })
|
||||
|
||||
/**
|
||||
* @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 })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 获取商品评价详情
|
||||
*/
|
||||
export const apiEvaluateGoodsDetail = (params: any) =>
|
||||
request.get({ url: '/goods_comment/commentDetail', data: params })
|
||||
87
uniapp/src/api/order.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 订单下单
|
||||
*/
|
||||
export const apiPlaceOrder = (params: any) =>
|
||||
request.post({url: '/order/placeOrder', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 订单列表
|
||||
*/
|
||||
export const apiOrderLists = (params: any) => request.get({url: '/order/lists', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 订单详情
|
||||
*/
|
||||
export const apiOrderDetail = (params: any) => request.get({url: '/order/detail', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 取消订单
|
||||
*/
|
||||
export const apiOrderCancel = (params: any) => request.post({url: '/order/cancel', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 删除订单
|
||||
*/
|
||||
export const apiOrderDel = (params: any) => request.post({url: '/order/del', data: params})
|
||||
|
||||
/** 师傅订单服务 Start **/
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 师傅服务列表
|
||||
*/
|
||||
export const apiStaffOrderLists = (params: any) =>
|
||||
request.get({url: '/staff_order/lists', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 师傅服务详情
|
||||
*/
|
||||
export const apiStaffOrderDetail = (params: any) =>
|
||||
request.get({url: '/staff_order/detail', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 确认服务
|
||||
*/
|
||||
export const apiStaffOrderConfirmService = (params: any) =>
|
||||
request.post({url: '/staff_order/confirmService', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 核销订单
|
||||
*/
|
||||
export const apiStaffOrderVerification = (params: any) =>
|
||||
request.post({url: '/staff_order/verification', data: params})
|
||||
/** 师傅订单服务 Start **/
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 补差价
|
||||
*/
|
||||
export const apiOrderGap = (params: any) =>
|
||||
request.post({url: '/order/orderGap', data: params})
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 加钟
|
||||
*/
|
||||
export const apiOrderAppend = (params: any) =>
|
||||
request.post({url: '/order/orderAppend', data: params})
|
||||
54
uniapp/src/api/shop.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
//首页数据
|
||||
export function getIndex() {
|
||||
return request.get({ url: '/index/index' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 风格主题
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getDecorateTheme() {
|
||||
return request.get({ url: '/decorate/style' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 服务列表
|
||||
* @param { any } data
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getServiceList(data: any) {
|
||||
return request.get({ url: '/index/serverLists', data }, { ignoreCancel: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 热门搜索
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getHotSearch() {
|
||||
return request.get({ url: '/search/hotLists' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 商家分类列表
|
||||
*/
|
||||
export const apiSkillLists = () => request.get({ url: '/coach/skillLists' })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 商家
|
||||
*/
|
||||
export const apiCoachLists = (params: any) => request.get({ url: '/coach/lists', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 商家详情
|
||||
*/
|
||||
export const apiCoachDetail = (params: any) => request.get({ url: '/coach/detail', data: params })
|
||||
// 门店列表
|
||||
export const apishopLists = (params: any) => request.get({ url: '/shop/lists', data: params })
|
||||
export const apishopDetail = (params: any) => request.get({ url: '/shop/detail', data: params })
|
||||
90
uniapp/src/api/store.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 装修数据
|
||||
*/
|
||||
export const getDecoratePage = (params: any) => request.get({ url: '/decorate/page', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 获取最近城市
|
||||
*/
|
||||
export const getNearbyCity = (params: { longitude: string; latitude: string }) =>
|
||||
request.get({ url: '/index/getNearbyCity', data: params }, { ignoreCancel: true })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 轮博图
|
||||
*/
|
||||
export const apiAdLists = (params: any) => request.get({ url: '/ad/lists', data: params })
|
||||
|
||||
/**
|
||||
* @return { Promise }
|
||||
* @description 服务分类
|
||||
*/
|
||||
export const apiCategoryLists = () => request.get({ url: '/goods_category/lists' })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 商品服务分类
|
||||
*/
|
||||
export const apiGoodsCategoryLists = (params: any) =>
|
||||
request.get({ url: '/goods_category/otherLists', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 服务列表
|
||||
*/
|
||||
export const apiGoodsLists = (params: any) =>
|
||||
request.get({ url: '/goods/lists', data: params }, { ignoreCancel: true })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 师傅详情
|
||||
*/
|
||||
export const apiStaffDetail = (params: any) => request.get({ url: '/staff/detail', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 获取地级市列表
|
||||
*/
|
||||
export const apiRegionCity = (params?: any) => request.get({ url: '/region/city', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 获取沿途地址
|
||||
*/
|
||||
export const getNearbyLocation = (params: any) => request.get({ url: '/index/getNearbyLocation', data: params })
|
||||
|
||||
/**
|
||||
* @description 热门搜索
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getHotSearch() {
|
||||
return request.get({ url: '/search/hotLists' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 搜索
|
||||
* @param { string } keyword 关键词
|
||||
* @return { Promise }
|
||||
*/
|
||||
export function getSearch(data: { keyword: string; pageNo: number; pageSize: number }) {
|
||||
return request.get({ url: '/search', data })
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @return { Promise }
|
||||
* @description 收藏接口
|
||||
*/
|
||||
export const putCollect = (params: { id: number, type: 1 | 2 | 3}) => request.post({ url: '/index/collect', data: params })
|
||||
139
uniapp/src/api/user.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* @description 用户中心
|
||||
*/
|
||||
export const getUserCenter = (header?: any) => request.get({ url: '/user/center', header })
|
||||
|
||||
// 获取用户信息
|
||||
export const apiUserInfoGet = (params?: any) => request.get({ url: '/user/info', data: params}, { isAuth: true })
|
||||
|
||||
// 设置用户信息
|
||||
export const apiUserInfoSet = (params: any) =>
|
||||
request.post({ url: '/user/setInfo', data: params }, { isAuth: true })
|
||||
|
||||
// 获取微信小程序手机号
|
||||
export const apiWechatMobileGet = (params: any) =>
|
||||
request.post({ url: '/user/getMobileByMnp', data: params }, { isAuth: true })
|
||||
|
||||
// 发送验证码-绑定手机号
|
||||
export const userBindMobileCaptcha = (params: any) =>
|
||||
request.post({ url: '/user/bindMobileCaptcha', data: params }, { isAuth: true })
|
||||
|
||||
// 绑定手机
|
||||
export function userBindMobile(data: any, header?: any) {
|
||||
return request.post({ url: '/user/bindMobile', data, header }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 微信电话
|
||||
export function userMnpMobile(data: any, header?: any) {
|
||||
return request.post({ url: '/user/getMobileByMnp', data, header }, { isAuth: true })
|
||||
}
|
||||
|
||||
// 绑定微信-小程序
|
||||
export const apiBindwx = (params: any, header?: any) =>
|
||||
request.post({ url: '/login/mnpAuthBind', data: params, header }, { isAuth: true })
|
||||
|
||||
// 绑定微信-公众号
|
||||
export const apiBindoa = (params: any, header?: any) =>
|
||||
request.post({ url: '/login/oaAuthBind', data: params, header }, { isAuth: true })
|
||||
|
||||
// 解绑 -- 测试用
|
||||
export const apiUnBindwx = (params: any, header?: any) =>
|
||||
request.post({ url: '/login/unbinding', data: params }, { isAuth: true })
|
||||
|
||||
// 是否设置登录密码
|
||||
export const apiHasPassword = () => request.get({ url: '/user/hasPassword' }, { isAuth: true })
|
||||
|
||||
// 设置登录密码
|
||||
export const apiSetPassword = (params: any) =>
|
||||
request.post({ url: '/user/setPassword', data: params }, { isAuth: true })
|
||||
|
||||
// 更改密码
|
||||
export function userChangePwd(data: any) {
|
||||
return request.post({ url: '/user/changePassword', data }, { isAuth: true })
|
||||
}
|
||||
|
||||
//忘记密码
|
||||
export function forgotPassword(data: Record<string, any>) {
|
||||
return request.post({ url: '/user/resetPassword', data })
|
||||
}
|
||||
|
||||
/** Profile End **/
|
||||
|
||||
/** Address Start **/
|
||||
/**
|
||||
* @description 获取地址列表
|
||||
*/
|
||||
export const apiAddressLists = (params: any) =>
|
||||
request.get({ url: '/user_address/lists', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 获取地址详情
|
||||
*/
|
||||
export const apiAddressDetail = (params: any) =>
|
||||
request.get({ url: '/user_address/detail', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 编辑地址
|
||||
*/
|
||||
export const apiAddressEdit = (params: any) =>
|
||||
request.post({ url: '/user_address/edit', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 切换默认地址
|
||||
*/
|
||||
export const apiAddressEditDefault = (params: any) =>
|
||||
request.post({ url: '/user_address/setDefault', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 新增地址
|
||||
*/
|
||||
export const apiAddressAdd = (params: any) =>
|
||||
request.post({ url: '/user_address/add', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 删除地址
|
||||
*/
|
||||
export const apiAddressDel = (params: any) =>
|
||||
request.post({ url: '/user_address/del', data: params })
|
||||
/** Address End **/
|
||||
|
||||
/** Evaluate Start **/
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 获取我的评价列表
|
||||
*/
|
||||
export const apiEvaluateLists = (params: any) =>
|
||||
request.get({ url: '/goods_comment/commentGoodsLists', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 评价服务信息
|
||||
*/
|
||||
export const apiEvaluateGoodsInfo = (params: any) =>
|
||||
request.get({ url: '/goods_comment/commentGoodsInfo', data: params })
|
||||
|
||||
/**
|
||||
* @param { Object } params
|
||||
* @description 提交评价信息
|
||||
*/
|
||||
export const apiEvaluateAdd = (params: any) =>
|
||||
request.post({ url: '/goods_comment/add', data: params })
|
||||
/** Evaluate End **/
|
||||
|
||||
/** Collect Start **/
|
||||
/**
|
||||
* @description 获取我的收藏列表
|
||||
*/
|
||||
export const apiCollectLists = (params: any) =>
|
||||
request.get({ url: '/user/collectLists', data: params })
|
||||
|
||||
/** Collect End **/
|
||||
export const getKefuConfig = (params?: any) =>
|
||||
request.post({ url: '/config/getKefuConfig', data: params }, { isAuth: true })
|
||||
32
uniapp/src/api/wallet.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 钱包信息
|
||||
export const apiUserWallet = () => request.get({ url: '/user/wallet' })
|
||||
|
||||
// 账户明细列表
|
||||
export const apiAccountLogLists = (params: any) =>
|
||||
request.get({ url: '/account_log/lists', data: params }, { ignoreCancel: true })
|
||||
|
||||
// 充值
|
||||
export const apiRecharge = (params: any) =>
|
||||
request.post({ url: '/recharge/recharge', data: params })
|
||||
|
||||
// 充值记录列表
|
||||
export const apiRechargeLogLists = (params: any) =>
|
||||
request.get({ url: '/recharge/logLists', data: params })
|
||||
|
||||
// 获取提现配置
|
||||
export const apiGetWithdrawConfig = (params: any) =>
|
||||
request.get({ url: '/withdraw/getConfig', data: params })
|
||||
|
||||
// 提现申请
|
||||
export const apiWithdrawApply = (params: any) =>
|
||||
request.post({ url: '/withdraw/apply', data: params })
|
||||
|
||||
// 提现申请列表
|
||||
export const apiWithdrawLists = (params: any) =>
|
||||
request.get({ url: '/withdraw/lists', data: params })
|
||||
|
||||
// 提现申请详情
|
||||
export const apiWithdrawDetail = (params: any) =>
|
||||
request.get({ url: '/withdraw/detail', data: params })
|
||||
52
uniapp/src/bundle/pages/account_detail/account_detail.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="main">
|
||||
<tabs
|
||||
:current="current"
|
||||
height="80"
|
||||
bar-width="60"
|
||||
:auth="true"
|
||||
bgColor="#fff"
|
||||
:activeColor="$theme.primaryColor"
|
||||
inactiveColor="#666"
|
||||
:itemWidth="150"
|
||||
:is-scroll="false"
|
||||
>
|
||||
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
|
||||
<view class="list">
|
||||
<list :status="item.status" :i="i" :index="current" />
|
||||
</view>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import list from './components/list.vue'
|
||||
import tab from '@/components/tab/tab.vue'
|
||||
import tabs from '@/components/tabs/tabs.vue'
|
||||
|
||||
const tabList = ref<any>([
|
||||
{
|
||||
name: '支出',
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
name: '收入',
|
||||
status: 2
|
||||
}
|
||||
])
|
||||
const current = ref<number>(0)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
height: calc(100vh - 44px - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
111
uniapp/src/bundle/pages/account_detail/components/list.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<z-paging ref="paging" v-model="balance.lists" @query="queryList" :fixed="false" height="100%">
|
||||
<!-- 余额明细列表 -->
|
||||
<view class="balance-details">
|
||||
<view v-for="(item, index) in balance.lists" :key="index">
|
||||
|
||||
<view class="balance-details-item flex">
|
||||
<view
|
||||
class="flex-none flex items-center justify-center bg-primary-light-9 rounded-full"
|
||||
style="width: 84rpx; height: 84rpx;"
|
||||
>
|
||||
<u-icon name="coupon-fill" :color="$theme.primaryColor" size="40"></u-icon>
|
||||
</view>
|
||||
<view class="flex-1 flex items-center justify-between ml-2">
|
||||
<view>
|
||||
<view class="flex-1 balance-details-item-text">
|
||||
{{ item.change_type_desc }}
|
||||
</view>
|
||||
<view class="balance-details-item-time">{{ item.create_time }}</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="balance-details-item-time">{{ item.remark }}</view>-->
|
||||
<view v-if="item.action == 2" class="balance-details-item-amount-add text-primary">
|
||||
+{{ item.change_amount }}
|
||||
</view>
|
||||
<view v-else class="balance-details-item-amount-reduce text-primary">
|
||||
-{{ item.change_amount }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, shallowRef } from 'vue'
|
||||
import { apiAccountLogLists } from '@/api/wallet'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
status: string | number
|
||||
i: number
|
||||
index: number
|
||||
}>(),
|
||||
{
|
||||
status: ''
|
||||
}
|
||||
)
|
||||
|
||||
const balance = reactive({
|
||||
lists: [] as any,
|
||||
change_type: 1
|
||||
})
|
||||
// 下拉组件的Ref
|
||||
const paging = shallowRef()
|
||||
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const { lists } = await apiAccountLogLists({
|
||||
page_no,
|
||||
page_size,
|
||||
action: props.status,
|
||||
change_object: balance.change_type
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.balance-details {
|
||||
margin: 30rpx;
|
||||
|
||||
.balance-details-item {
|
||||
background-color: #fff;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 36rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.balance-details-item-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.balance-details-item-amount-add {
|
||||
font-size: 40rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.balance-details-item-amount-reduce {
|
||||
font-size: 40rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.balance-details-item-time {
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding-top: 300rpx;
|
||||
}
|
||||
</style>
|
||||
39
uniapp/src/bundle/pages/agreement/agreement.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<view class="p-[24rpx]">
|
||||
<mp-html :content="agreementContent"></mp-html>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiPolicyAgreement } from '@/api/app'
|
||||
import { AgreementEnum } from "@/enums/agreementEnums"
|
||||
|
||||
let agreementType = ref('') // 协议类型
|
||||
const agreementContent = ref('') // 协议内容
|
||||
|
||||
const getData = async () => {
|
||||
const res = await apiPolicyAgreement()
|
||||
if( agreementType == AgreementEnum.SERVICE) {
|
||||
agreementContent.value = res.service_agreement
|
||||
uni.setNavigationBarTitle({
|
||||
title: String(res.service_title)
|
||||
})
|
||||
}else {
|
||||
agreementContent.value = res.privacy_agreement
|
||||
uni.setNavigationBarTitle({
|
||||
title: String(res.privacy_title)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (options.type) {
|
||||
agreementType = options.type
|
||||
getData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
253
uniapp/src/bundle/pages/appoint_time/index.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<!-- Header Start -->
|
||||
<scroll-view class="scroll-view-box" scroll-x="true">
|
||||
<block v-for="(item, index) in date" :key="index">
|
||||
<view
|
||||
class="day text-base"
|
||||
:class="{ active: dayIndex == index }"
|
||||
@click="dayIndex = index"
|
||||
>
|
||||
<view>{{ item.week }}</view>
|
||||
<view class="mt-[10rpx]">{{ item.date }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
<!-- Header End -->
|
||||
|
||||
<!-- Main Start -->
|
||||
<view class="time-box">
|
||||
<block v-for="(item2, index2) in timeSlot" :key="index2">
|
||||
<view
|
||||
class="time-item"
|
||||
:class="{
|
||||
select: selectIndex == index2,
|
||||
disabled: dayIndex === 0 && item2.disabled
|
||||
}"
|
||||
@click="selectAppoint(index2, dayIndex === 0 && item2.disabled)"
|
||||
>
|
||||
{{ item2.start_time }} - {{ item2.end_time }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- Main End -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { toast } from '@/utils/util'
|
||||
import { apiAppointTime } from '@/api/goods'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
|
||||
interface DateObj {
|
||||
year: number
|
||||
week: string | undefined
|
||||
date: string
|
||||
}
|
||||
|
||||
interface TimeSlotObj {
|
||||
start_time: string
|
||||
end_time: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
// 日期
|
||||
const date = ref<DateObj[]>([])
|
||||
// 当天日期索引
|
||||
const dayIndex = ref<number>(0)
|
||||
// 时间段选择索引
|
||||
const selectIndex = ref<number>(0)
|
||||
// 预约时间段
|
||||
const timeSlot = ref<TimeSlotObj[]>([])
|
||||
// 商品数据
|
||||
const goodsForm = ref<any | null>({
|
||||
goods_num: '' as string,
|
||||
id: '' as string
|
||||
})
|
||||
|
||||
/**
|
||||
* @param { number } index
|
||||
* @param { boolean } disabled
|
||||
* @return { void }
|
||||
* @description 选择上门时间段
|
||||
*/
|
||||
const selectAppoint = (index: number, disabled: boolean) => {
|
||||
if (disabled) return toast('服务时间段已过!请选择其他时间段')
|
||||
selectIndex.value = index
|
||||
// 缓存用户所选的预约时间
|
||||
uni.setStorage({
|
||||
key: 'selectDate',
|
||||
data: [dayIndex.value, selectIndex.value]
|
||||
})
|
||||
const dateTime = date.value[dayIndex.value]
|
||||
const timeSlotItem = timeSlot.value[index]
|
||||
const appointTime = {
|
||||
year: dateTime.year,
|
||||
date: dateTime.date,
|
||||
start_time: timeSlotItem.start_time,
|
||||
end_time: timeSlotItem.end_time
|
||||
}
|
||||
if (goodsForm.value.id != '') {
|
||||
const params = {
|
||||
goodsData: goodsForm.value,
|
||||
appointData: appointTime
|
||||
}
|
||||
goPage(`/pages/order_buy/index?params=${JSON.stringify(params)}`)
|
||||
return
|
||||
}
|
||||
uni.$emit('appointTime', appointTime)
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return { Promise } void
|
||||
* @description 请求获取预约时间
|
||||
*/
|
||||
const getAppointTime = async (): Promise<void> => {
|
||||
const { appoint_time, order_time } = await apiAppointTime()
|
||||
timeSlot.value = handleTimeSlot(appoint_time)
|
||||
date.value = handleAppointDay(order_time)
|
||||
|
||||
// 获取用户所选的预约时间
|
||||
uni.getStorage({
|
||||
key: 'selectDate',
|
||||
success: (res) => {
|
||||
dayIndex.value = res.data[0]
|
||||
selectIndex.value = res.data[1]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { TimeSlotObj[] } timeSlot
|
||||
* @return { TimeSlotObj[] } 时间段
|
||||
* @description 计算已过期时间
|
||||
*/
|
||||
const handleTimeSlot = (timeSlot: TimeSlotObj[]) => {
|
||||
const time = new Date() // 时间戳转换成标准日期时间
|
||||
const min = time.getMinutes()
|
||||
const startTime = time.getHours() + '' + (min <= 9 ? '0' + min : min) // 获取时分并组合成标准格式(时分)
|
||||
timeSlot.map((item) => {
|
||||
const end = item.end_time.replace(':', '')
|
||||
item.disabled = Number(startTime) - Number(end) >= 0
|
||||
})
|
||||
return timeSlot
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { number } days
|
||||
* @return { string } 日期
|
||||
* @description 根据返回预约天数计算出日期
|
||||
*/
|
||||
const handleAppointDay = (days: number | string | undefined) => {
|
||||
const timeArr = []
|
||||
// 现在时间戳
|
||||
const newTime = new Date().getTime()
|
||||
for (let i = 0; i <= days; i++) {
|
||||
// 获取天数时间戳
|
||||
const millisecond = newTime + i * 24 * 60 * 60 * 1000
|
||||
// 年
|
||||
const year = new Date(millisecond).getFullYear()
|
||||
// 月
|
||||
const month = new Date(millisecond).getMonth() + 1
|
||||
// 星期
|
||||
const week = new Date(millisecond).getDay()
|
||||
// 日
|
||||
const day = new Date(millisecond).getDate()
|
||||
// 未来天数的数组
|
||||
timeArr.push({
|
||||
year: year,
|
||||
week: handleWeek(week, i),
|
||||
date: (month <= 9 ? '0' + month : month) + '-' + (day <= 9 ? '0' + day : day)
|
||||
})
|
||||
}
|
||||
return timeArr
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { number } week
|
||||
* @param { number } i
|
||||
* @return { string } 日期
|
||||
* @description 转换日期
|
||||
*/
|
||||
const handleWeek = (week: number | string | undefined, i: number) => {
|
||||
if (i === 0) return '今天'
|
||||
else if (i === 1) return '明天'
|
||||
else if (week === 0) return '周日'
|
||||
else if (week === 1) return '周一'
|
||||
else if (week === 2) return '周二'
|
||||
else if (week === 3) return '周三'
|
||||
else if (week === 4) return '周四'
|
||||
else if (week === 5) return '周五'
|
||||
else if (week === 6) return '周六'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { string } url
|
||||
* @return { void }
|
||||
* @description 跳转页面方法
|
||||
*/
|
||||
const goPage = (url: string) => {
|
||||
uni.redirectTo({ url: url })
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.params) {
|
||||
const goodsData = JSON.parse(options.params)
|
||||
goodsForm.value = goodsData
|
||||
}
|
||||
})
|
||||
onShow(() => {
|
||||
getAppointTime()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.scroll-view-box {
|
||||
height: 140rpx;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
background-color: #ffffff;
|
||||
padding: 30rpx 20rpx 0 20rpx;
|
||||
|
||||
.day {
|
||||
width: 85rpx;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
color: #222222;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
.active {
|
||||
color: #f36161;
|
||||
border-bottom: 4px solid #f36161;
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.time-box {
|
||||
padding: 24rpx;
|
||||
|
||||
.time-item {
|
||||
width: 220rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
background-color: #ffffff;
|
||||
margin: 0 20rpx 20rpx 0;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.time-item:nth-child(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.select {
|
||||
background-color: rgba(255, 244, 244, 1);
|
||||
color: rgba(243, 97, 97, 1);
|
||||
// border: 1px solid rgba(243, 97, 97, 1);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: rgba(153, 153, 153, 1);
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
23
uniapp/src/bundle/pages/as_us/as_us.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="as-us flex flex-1 flex-col items-center justify-center">
|
||||
<image :src="appStore.config.web_logo" mode="" class="img"></image>
|
||||
<view class="text-content mt-[20rpx]">当前版本{{ `v${appStore.config.version}` }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.as-us {
|
||||
height: 100%;
|
||||
.img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 96rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
146
uniapp/src/bundle/pages/category_goods_list/index.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<scroll-view
|
||||
class="menu"
|
||||
scroll-x="true"
|
||||
:scroll-left="160 * idx"
|
||||
v-if="menuData.sons.length"
|
||||
>
|
||||
<view
|
||||
class="menu--item"
|
||||
:class="{ active: categoryId === menuData.info.id }"
|
||||
@click="changeTabs(menuData.info)"
|
||||
>
|
||||
<view class="flex justify-center">
|
||||
<u-image src="@/bundle/static/images/mb-like.svg" width="56" height="56" />
|
||||
</view>
|
||||
<view class="black font-medium mt-[14rpx]">{{ '全部服务' }}</view>
|
||||
</view>
|
||||
<block v-for="item in menuData.sons" :key="item.id">
|
||||
<view
|
||||
class="menu--item"
|
||||
:class="{ active: categoryId === item.id }"
|
||||
@click="changeTabs(item)"
|
||||
>
|
||||
<view class="flex justify-center">
|
||||
<u-image :src="item.image" width="56" height="56" />
|
||||
</view>
|
||||
<view class="black font-medium mt-[14rpx]">{{ item.name }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
<view class="main">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
v-model="goodsData"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<view class="px-[24rpx]">
|
||||
<goods-card :goodsList="goodsData"></goods-card>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiGoodsCategoryLists, apiGoodsLists } from '@/api/store'
|
||||
import GoodsCard from '@/components/goods-card/index.vue'
|
||||
|
||||
const categoryId = ref<number>()
|
||||
const menuData = ref<any>({
|
||||
info: {},
|
||||
sons: []
|
||||
})
|
||||
const goodsData = ref<any>([])
|
||||
const paging = shallowRef()
|
||||
// 记录当前选中商品下标
|
||||
const idx = ref(0)
|
||||
|
||||
onLoad((options: any) => {
|
||||
categoryId.value = options?.id * 1 || 0
|
||||
getCategoryList()
|
||||
})
|
||||
|
||||
// 切换菜单
|
||||
const changeTabs = (event: any) => {
|
||||
// 如果点击同一个2次的话那就是取消当前选择然后选择一级分类的商品
|
||||
categoryId.value = event.id === categoryId.value ? menuData.value.info.id : event.id
|
||||
paging.value.reload()
|
||||
}
|
||||
|
||||
// 获取分类列表
|
||||
const getCategoryList = async (): Promise<void> => {
|
||||
try {
|
||||
const res: any = await apiGoodsCategoryLists({ id: categoryId.value })
|
||||
uni.setNavigationBarTitle({
|
||||
title: res.info.name
|
||||
})
|
||||
menuData.value = res
|
||||
if (menuData.value.sons.length) {
|
||||
idx.value = menuData.value.sons.findIndex((item) => item.id === categoryId.value)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取商品列表
|
||||
const queryList = async (pageNo: number, pageSize: number) => {
|
||||
try {
|
||||
const { lists } = await apiGoodsLists({
|
||||
page_no: pageNo,
|
||||
page_size: pageSize,
|
||||
category_id: categoryId.value
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
|
||||
.menu {
|
||||
height: 188rpx;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
padding: 20rpx 0 20rpx 24rpx;
|
||||
|
||||
&--item {
|
||||
width: 160rpx;
|
||||
height: 148rpx;
|
||||
padding: 20rpx 0;
|
||||
margin-right: 20rpx;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #f36161;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
uniapp/src/bundle/pages/change_account/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view
|
||||
class="register min-h-full flex flex-col items-center px-[30rpx] pt-[60rpx] box-border"
|
||||
>
|
||||
<view class="w-full">
|
||||
|
||||
<view class="bg-white mb-[30rpx] rounded-[24rpx] px-[48rpx] py-[15rpx] flex items-center">
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="data"
|
||||
:border="false"
|
||||
:placeholder="fieldType == FieldType.NICKNAME ? '请输入昵称' : '请输入账号'"
|
||||
placeholder-style="color: #999"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="mt-[112rpx]">
|
||||
<u-button type="primary" @click="handleConfirm" class="rounded-[24rpx]"> 确定 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { forgotPassword } from '@/api/user'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { FieldType } from '@/enums/appEnums'
|
||||
import { apiUserInfoSet } from '@/api/user'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
const fieldType = ref('') // 1-昵称 2-账号
|
||||
|
||||
const data = ref('')
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!data.value && fieldType.value == FieldType.NICKNAME) return uni.$u.toast('请输入昵称')
|
||||
if (!data.value && fieldType.value == FieldType.USERNAME) return uni.$u.toast('请输入账号')
|
||||
|
||||
await apiUserInfoSet({
|
||||
field: fieldType.value,
|
||||
value: data.value
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
data.value = options.data || ''
|
||||
fieldType.value = options.type || ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
119
uniapp/src/bundle/pages/city/component/city-picker.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="showPopup"
|
||||
mode="bottom"
|
||||
border-radius="12"
|
||||
:closeable="true"
|
||||
:mask-close-able="true"
|
||||
>
|
||||
<view style="padding: 30rpx; height: 1100rpx">
|
||||
<view class="font-medium text-lg pb-4">
|
||||
请选择所在城市
|
||||
</view>
|
||||
<!-- 搜索 -->
|
||||
<u-search
|
||||
placeholder="搜索城市名"
|
||||
v-model="keyword"
|
||||
shape="round"
|
||||
:clearabled="true"
|
||||
:animation="true"
|
||||
:height="70"
|
||||
bg-color="#f2f2f2"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
@clear="handleSearch"
|
||||
></u-search>
|
||||
|
||||
<!-- 定位城市 -->
|
||||
<view class="text-[24rpx] text-[#c8c9cc] mt-[30rpx] mb-[30rpx]">已开通以下城市</view>
|
||||
<scroll-view scroll-y="true" :style="{ height: '400px' }">
|
||||
<view>
|
||||
<!-- 城市列表 -->
|
||||
<view
|
||||
v-for="(cityItem, cityIndex) in cityList"
|
||||
:key="cityIndex"
|
||||
class="anchor flex mb-[10rpx]"
|
||||
>
|
||||
<view class="font-bold text-[32rpx]">{{ cityIndex }}</view>
|
||||
<view class="ml-[20rpx]">
|
||||
<block v-for="(cityItem2, cityIndex2) in cityItem" :key="cityIndex2">
|
||||
<view
|
||||
@click="handleClick(cityItem2)"
|
||||
class="pb-[40rpx] pt-[4rpx] w-[500rpx]"
|
||||
>
|
||||
<view v-if="location.city_id != cityItem2.city_id">
|
||||
{{ cityItem2.name }}
|
||||
</view>
|
||||
<view v-else class="u-flex justify-between">
|
||||
<view :style="{'color':$theme.primaryColor}">{{ cityItem2.name }}</view>
|
||||
<u-icon name="checkmark" :color="$theme.primaryColor"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import {apiRegionCity} from '@/api/store'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean
|
||||
},
|
||||
location: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:show', show: boolean): void
|
||||
(event: 'update:location', value: any): void
|
||||
(event: 'update', value: any): void
|
||||
}>()
|
||||
|
||||
// 搜索关键字
|
||||
const keyword = ref<string | number>('')
|
||||
// 城市列表
|
||||
const cityList = ref<any>([])
|
||||
|
||||
const showPopup = computed({
|
||||
get() {
|
||||
return props.show
|
||||
},
|
||||
set(val) {
|
||||
emit('update:show', val)
|
||||
}
|
||||
})
|
||||
|
||||
const handleSearch = async () => {
|
||||
cityList.value = await apiRegionCity({
|
||||
keyword: keyword.value
|
||||
})
|
||||
}
|
||||
|
||||
const handleClick = (item: any) => {
|
||||
emit('update:location', {
|
||||
city_id: item.city_id,
|
||||
id: item.id,
|
||||
longitude: item.gcj02_lng,
|
||||
latitude: item.gcj02_lat,
|
||||
name: item.name
|
||||
})
|
||||
emit('update', item)
|
||||
showPopup.value = false
|
||||
}
|
||||
|
||||
// 初始化城市数据
|
||||
const initCityData = async (): Promise<void> => {
|
||||
cityList.value = await apiRegionCity()
|
||||
}
|
||||
|
||||
onMounted(() => { initCityData() })
|
||||
</script>
|
||||
305
uniapp/src/bundle/pages/city/index.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<view class="city-pages flex flex-col min-h-0 h-full">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search flex items-center">
|
||||
<!-- 左侧城市 -->
|
||||
<view class="flex items-center search--city" @click.stop="showCityPicker = true">
|
||||
<text class="mr-[15rpx]">{{ currentLocation.name }}</text>
|
||||
<u-icon name="arrow-down" size="20"></u-icon>
|
||||
</view>
|
||||
<!-- 右侧搜索城市 -->
|
||||
<input
|
||||
type="text"
|
||||
class="flex-1 search--search"
|
||||
placeholder="请输入地点"
|
||||
v-model="keyword"
|
||||
@confirm="handleSearch"
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 地图组件 -->
|
||||
<view class="mt-[30rpx] mb-[30rpx] map">
|
||||
<map
|
||||
id="myMap"
|
||||
theme="normal"
|
||||
style="width: 100%; height: 400rpx; border-radius: 20rpx; overflow: hidden"
|
||||
show-location
|
||||
:enable-overlooking="false"
|
||||
:scale="13"
|
||||
@regionchange="regionchange"
|
||||
:latitude="currentLocation.latitude"
|
||||
:longitude="currentLocation.longitude"
|
||||
>
|
||||
<cover-view class="iconImg">
|
||||
<cover-image
|
||||
class="img"
|
||||
src="https://hellouniapp.dcloud.net.cn/static/location.png"
|
||||
></cover-image>
|
||||
</cover-view>
|
||||
|
||||
<!-- #ifdef MP -->
|
||||
<cover-view class="origin" @click="originLocation">
|
||||
<cover-image
|
||||
class="origin_icon"
|
||||
src="@/bundle/static/images/map/origin.png"
|
||||
></cover-image>
|
||||
<cover-view class="text-xs mt-[15rpx]">定位</cover-view>
|
||||
</cover-view>
|
||||
<!-- #endif -->
|
||||
</map>
|
||||
</view>
|
||||
|
||||
<view class="address flex flex-col min-h-0 flex-1 mt-4" v-if="addressList?.length">
|
||||
<!-- 地址列表 -->
|
||||
<scroll-view scroll-y="true" class="h-full" :show-scrollbar="false">
|
||||
<view
|
||||
class="address-item"
|
||||
v-for="(item, index) in addressList"
|
||||
:key="index"
|
||||
@click="choiceAddress(item)"
|
||||
>
|
||||
<view class="u-flex justify-between">
|
||||
<view class="u-flex">
|
||||
<u-icon name="map"></u-icon>
|
||||
<view class="font-bold ml-[10rpx] w-[500rpx] truncate">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-[#909399] text-[24rpx]">
|
||||
{{ computeDistance(item._distance) }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="text-[#909399] text-[24rpx] ml-[40rpx] mt-[10rpx] w-[500rpx] truncate"
|
||||
>
|
||||
{{ item.address }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 无地址 -->
|
||||
<view v-else style="height: 60vh" class="flex flex-col items-center justify-center pb-3">
|
||||
<view class="mt-[40rpx]">
|
||||
<u-image src="@/bundle/static/images/map/good.png" width="290" height="200" />
|
||||
</view>
|
||||
<view class="my-[30rpx] text-muted">
|
||||
<text>没有数据哦~</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 定位提示弹窗 -->
|
||||
<modal-popup
|
||||
v-model:show="showLocationModal"
|
||||
:title="errorTitle"
|
||||
:content="errorContent"
|
||||
@refresh=""
|
||||
/>
|
||||
|
||||
<!-- 城市选择器 -->
|
||||
<city-picker
|
||||
v-model:show="showCityPicker"
|
||||
v-model:location="currentLocation"
|
||||
@update="getAddressList"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { locationState, useLocation } from '@/hooks/useLocation'
|
||||
import { getNearbyLocation } from '@/api/store'
|
||||
import CityPicker from './component/city-picker.vue'
|
||||
import { useRoute, useRouter } from 'uniapp-router-next'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const { showLocationModal, errorTitle, errorContent, getLocationData, setLocationData } =
|
||||
useLocation()
|
||||
|
||||
// 城市选择弹窗
|
||||
const showCityPicker = ref(false)
|
||||
//搜索关键词
|
||||
const keyword = ref('')
|
||||
//地址列表
|
||||
const addressList = ref([])
|
||||
// 当前选择城市
|
||||
const currentLocation = ref(locationState)
|
||||
// 创建map的上下文对象, 从而操控map组件
|
||||
const mapCtx = uni.createMapContext('myMap')
|
||||
|
||||
//计算地址距离
|
||||
const computeDistance = (distance: number) => {
|
||||
let res = distance + 'm'
|
||||
if (distance > 1000) {
|
||||
res = (distance / 1000).toFixed(3) + 'km'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const handleSearch = async () => {
|
||||
uni.showLoading({
|
||||
title: '搜索中...',
|
||||
mask: true
|
||||
})
|
||||
await getAddressList()
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
// 滑动地图更新当前定位
|
||||
const regionchange = (e: any) => {
|
||||
// #ifdef WEB
|
||||
if (e.detail.type == 'end' && (e.detail.causedBy == 'update' || e.detail.causedBy == 'drag')) {
|
||||
mapCtx.getCenterLocation({
|
||||
success: async (res: any) => {
|
||||
currentLocation.value.latitude = res.latitude
|
||||
currentLocation.value.longitude = res.longitude
|
||||
getAddressList()
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
if (e.type == 'end' && (e.causedBy == 'update' || e.causedBy == 'drag')) {
|
||||
mapCtx.getCenterLocation({
|
||||
success: async (res: any) => {
|
||||
currentLocation.value.latitude = res.latitude
|
||||
currentLocation.value.longitude = res.longitude
|
||||
getAddressList()
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 回到我的位置
|
||||
const originLocation = async () => {
|
||||
mapCtx.moveToLocation({
|
||||
latitude: currentLocation.value.latitude as number,
|
||||
longitude: currentLocation.value.longitude as number
|
||||
})
|
||||
await getLocationData()
|
||||
getAddressList()
|
||||
}
|
||||
|
||||
//选择地址
|
||||
const choiceAddress = (res: any) => {
|
||||
const latitude = res.location.lat
|
||||
const longitude = res.location.lng
|
||||
const info = {
|
||||
name: currentLocation.value.name,
|
||||
city_id: currentLocation.value.city_id,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
address: res.address,
|
||||
title: res.title
|
||||
}
|
||||
if (route.query.is_address) {
|
||||
console.log('emit', info)
|
||||
uni.$emit('city_select', info)
|
||||
} else {
|
||||
setLocationData(info)
|
||||
}
|
||||
|
||||
uni.$emit('updateAddress')
|
||||
|
||||
router.navigateBack()
|
||||
}
|
||||
|
||||
const getAddressList = async () => {
|
||||
const data = await getNearbyLocation({
|
||||
keyword: keyword.value,
|
||||
latitude: currentLocation.value.latitude,
|
||||
longitude: currentLocation.value.longitude
|
||||
})
|
||||
addressList.value = data.data
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
if (!currentLocation.value.city_id) {
|
||||
await getLocationData()
|
||||
await getAddressList()
|
||||
} else {
|
||||
await getAddressList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.city-pages {
|
||||
padding: 24rpx;
|
||||
|
||||
.search {
|
||||
padding: 15rpx 30rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #ffffff;
|
||||
|
||||
&--city {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
&--search {
|
||||
height: 50rpx;
|
||||
font-size: 26rpx;
|
||||
margin-left: 30rpx;
|
||||
padding-left: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
position: relative;
|
||||
|
||||
.iconImg {
|
||||
position: absolute;
|
||||
top: 46%;
|
||||
left: 47%;
|
||||
|
||||
.img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.origin {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
text-align: center;
|
||||
border: 1rpx solid rgba(187, 187, 187, 1);
|
||||
|
||||
.origin_icon {
|
||||
position: relative;
|
||||
top: 15%;
|
||||
left: 30%;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
background-color: #ffffff;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.address-item {
|
||||
padding-bottom: 30rpx;
|
||||
margin: 20rpx 0;
|
||||
border-bottom: 1px solid #f4f4f5;
|
||||
}
|
||||
|
||||
.address-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
457
uniapp/src/bundle/pages/coach_details/index.vue
Normal file
@@ -0,0 +1,457 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="coach-detail">
|
||||
<!-- #ifndef H5 -->
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="技师详情"
|
||||
:immersive="true"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, ${percent})` }"
|
||||
:title-color="percent > 0.5 ? '#000' : !isEmpty ? '#000' : '#fff'"
|
||||
:back-icon-color="percent > 0.5 ? '#000' : !isEmpty ? '#000' : '#fff'"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
<!-- #endif -->
|
||||
|
||||
<view v-if="isEmpty">
|
||||
<!-- 轮播图 -->
|
||||
<l-swiper
|
||||
:content="{
|
||||
data: coachData.life_photo
|
||||
}"
|
||||
name="url"
|
||||
height="750"
|
||||
indicatorPos="bottomRight"
|
||||
mode="number"
|
||||
borderRadius="0"
|
||||
/>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="coach-detail-info">
|
||||
<view class="flex items-center bg-[#FEF4EB] px-[30rpx] py-[12rpx] rounded-t-lg" @click="goTocredential()">
|
||||
<u-image mode="aspectFit" height="36" width="120" :src="anxingouLogo"></u-image>
|
||||
<view class="text-[#CB9F5D] text-sm ml-4 tips">
|
||||
<text>实名认证</text>
|
||||
<text>资质认证</text>
|
||||
<text>平台担保</text>
|
||||
</view>
|
||||
<u-icon color="#CB9F5D" class="ml-auto" size="28" name="arrow-right"></u-icon>
|
||||
</view>
|
||||
<view class="p-[24rpx] rounded-b-lg bg-white roun">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="text-xl font-bold">{{ coachData.name }}</view>
|
||||
<view class="text-[#C38925]">最早可约{{ coachData.first_appoint }}</view>
|
||||
</view>
|
||||
<view class="text-info mt-4 introduct">
|
||||
<text>{{ coachData.gender == 1 ? '男' : '女' }}</text>
|
||||
<text>{{ coachData.age }}岁</text>
|
||||
<text>{{ coachData.education }}</text>
|
||||
<text>{{ coachData.nation }}</text>
|
||||
<!-- <text>{{ coachData.shop_name }}</text> -->
|
||||
</view>
|
||||
<view class="text-xs mt-4 flex justify-between">
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="user"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">已服务</text>
|
||||
<text class="text-primary">{{ coachData.order_num }}人</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="favor"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">好评率</text>
|
||||
<text class="text-primary">{{ coachData.good_comment }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="distance"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">距离</text>
|
||||
<text class="text-primary">{{ coachData.distance_desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 入驻商家信息 -->
|
||||
<view v-if="Object.keys(coachData?.shop || {}).length" class="mt-[36rpx] bg-[#F6F7F8] py-[12rpx] pl-[12rpx] pr-[22rpx] flex items-center rounded-[20rpx]">
|
||||
|
||||
<u-image mode="aspectFill" height="92" width="92" :src="coachData?.shop?.logo" borderRadius="16" class="flex-none"></u-image>
|
||||
<view class="w-full mx-[20rpx]">
|
||||
<view class="text-base text-main mb-[8rpx] line-clamp-1">
|
||||
{{ coachData?.shop?.name || ''}}
|
||||
</view>
|
||||
<view class="text-xs text-muted">
|
||||
<span class="mr-[34rpx]">评分:{{ coachData?.shop?.good_comment || 0 }}</span>
|
||||
<span>人均:¥{{ coachData?.shop?.consumption || 0 }}</span>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-center">
|
||||
<view class="text-base text-content">{{ coachData?.shop?.distance || 0 }}km</view>
|
||||
<view class="text-xs text-muted">距离</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="coachData?.introduction"
|
||||
class="bg-white rounded-[20rpx] p-[24rpx] mx-[30rpx] mt-[30rpx]"
|
||||
>
|
||||
<view class="text-lg font-medium">自我介绍</view>
|
||||
<view class="mt-2 text-content">
|
||||
{{ coachData.introduction }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="rounded-[20rpx] bg-white p-[24rpx] m-[30rpx]">
|
||||
<tabs
|
||||
:isScroll="false"
|
||||
height="70"
|
||||
bar-width="60"
|
||||
font-size="32rpx"
|
||||
:bold="true"
|
||||
inactiveColor="#333"
|
||||
:activeColor="$theme.primaryColor"
|
||||
>
|
||||
<tab name="服务项目">
|
||||
<view class="mt-4" v-for="item in coachData.goods_lists" :key="item.id">
|
||||
<project :data="item" :coach_id="coachId" />
|
||||
</view>
|
||||
</tab>
|
||||
<tab name="用户评价">
|
||||
<evaluate
|
||||
v-for="item in coachData.comment_lists"
|
||||
:index="item.id"
|
||||
:data="item"
|
||||
/>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
|
||||
<view class="footer u-flex fixed">
|
||||
<view class="flex-1">
|
||||
<u-button type="primary" @click="handleCollection(coachData.is_collect)">
|
||||
<u-icon
|
||||
:name="coachData.is_collect ? 'star-fill' : 'star'"
|
||||
color="#ffffff"
|
||||
size="30"
|
||||
class="mb-[1rpx]"
|
||||
/>
|
||||
<text class="text-base font-medium ml-1">加收藏</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty
|
||||
text="抱歉,该技师不存在~"
|
||||
:src="'/static/images/empty/collection.png'"
|
||||
:icon-size="300"
|
||||
color="#888888"
|
||||
>
|
||||
<template #bottom>
|
||||
<view class="empty-bottom">
|
||||
<button
|
||||
class="bg-primary text-lg text-white leading-[80rpx] h-[80rpx]"
|
||||
@click="router.reLaunch('/pages/index/index')"
|
||||
>
|
||||
去看看其它
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</u-empty>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 返回顶部按钮 -->
|
||||
<u-back-top
|
||||
:scroll-top="scrollTop"
|
||||
:top="100"
|
||||
:customStyle="{
|
||||
backgroundColor: '#FFF',
|
||||
color: '#000',
|
||||
boxShadow: '0px 3px 6px rgba(0, 0, 0, 0.1)'
|
||||
}"
|
||||
>
|
||||
</u-back-top>
|
||||
|
||||
<!-- 定位提示弹窗 -->
|
||||
<modal-popup
|
||||
v-model:show="showLocationModal"
|
||||
:title="errorTitle"
|
||||
:content="errorContent"
|
||||
@refresh="handleRefresh"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
options: {
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, shallowRef, unref, nextTick, handleError } from 'vue'
|
||||
import { onLoad, onPageScroll, onShow } from '@dcloudio/uni-app'
|
||||
import { apiCoachDetail } from '@/api/coach'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useRouter } from 'uniapp-router-next'
|
||||
import { locationState, useLocation } from '@/hooks/useLocation'
|
||||
import anxingouLogo from '@/bundle/static/images/anxingouLogo.png'
|
||||
import user from '@/bundle/static/icon/user.png'
|
||||
import favor from '@/bundle/static/icon/favor.png'
|
||||
import distance from '@/bundle/static/icon/distance.png'
|
||||
|
||||
import project from '@/components/project-card/index.vue'
|
||||
import evaluate from '@/components/evaluate-card/index.vue'
|
||||
import { putCollect } from '@/api/store'
|
||||
|
||||
const {
|
||||
showLocationModal,
|
||||
errorTitle,
|
||||
errorContent,
|
||||
getLocationData
|
||||
} = useLocation()
|
||||
|
||||
type CoachType = {
|
||||
age: number
|
||||
comment_num: number
|
||||
distance: number
|
||||
distance_desc: string
|
||||
education: string
|
||||
first_appoint: string
|
||||
gender: number
|
||||
good_comment: string
|
||||
id: number
|
||||
introduction: string
|
||||
life_photo: string[]
|
||||
is_collect: number
|
||||
name: string
|
||||
nation: string
|
||||
order_num: number
|
||||
shop_name: string
|
||||
skill_id: number
|
||||
skill_name: string
|
||||
work_photo: string
|
||||
work_status: number
|
||||
goods_lists: any
|
||||
comment_lists: any
|
||||
health_certificate: string
|
||||
certification: string
|
||||
id_card: string
|
||||
shop: any
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const coachData = reactive<CoachType>({
|
||||
age: -1,
|
||||
comment_num: -1,
|
||||
distance: -1,
|
||||
distance_desc: '',
|
||||
education: '',
|
||||
first_appoint: '',
|
||||
gender: -1,
|
||||
good_comment: '',
|
||||
id: -1,
|
||||
introduction: '',
|
||||
is_collect: 0,
|
||||
life_photo: [],
|
||||
name: '',
|
||||
nation: '',
|
||||
order_num: -1,
|
||||
shop_name: '',
|
||||
skill_id: -1,
|
||||
skill_name: '',
|
||||
work_photo: '',
|
||||
work_status: -1,
|
||||
goods_lists: [],
|
||||
comment_lists: [],
|
||||
health_certificate: '',
|
||||
certification: '',
|
||||
id_card: '',
|
||||
shop: {}
|
||||
})
|
||||
|
||||
const isLogin = computed(() => userStore.token)
|
||||
const coachId = ref<number | string>('')
|
||||
const scrollTop = ref<number>(0)
|
||||
const percent = ref<number>(0)
|
||||
const isEmpty = ref<boolean>(true)
|
||||
|
||||
// 去到认证页面
|
||||
const goTocredential = () => {
|
||||
router.navigateTo({
|
||||
path: '/bundle/pages/master_worker_credential/index',
|
||||
query: coachData
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取商品详情
|
||||
*/
|
||||
const initializeCoachDetails = async (): Promise<void> => {
|
||||
try {
|
||||
const res: CoachType = await apiCoachDetail({
|
||||
id: coachId.value,
|
||||
longitude: locationState.longitude,
|
||||
latitude: locationState.latitude,
|
||||
city_id: locationState.city_id
|
||||
})
|
||||
if (res.life_photo) {
|
||||
res.life_photo = res.life_photo.map((item: string) => {
|
||||
return { url: item }
|
||||
})
|
||||
}
|
||||
// @ts-ignore
|
||||
Reflect.ownKeys(coachData).map((key: string) => (coachData[key] = res?.[key]))
|
||||
console.log(coachData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
isEmpty.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏
|
||||
const handleCollection = async (is_collect: number): Promise<void> => {
|
||||
if (!isLogin.value) {
|
||||
router.navigateTo('/pages/login/login')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await putCollect({
|
||||
id: unref(coachId) as number,
|
||||
type: 1
|
||||
})
|
||||
if (is_collect) {
|
||||
uni.$u.toast('取消收藏成功')
|
||||
} else {
|
||||
uni.$u.toast('收藏成功')
|
||||
}
|
||||
await initializeCoachDetails()
|
||||
} catch (error) {
|
||||
//TODO handle the exception
|
||||
console.log('收藏请求错误', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const handleRefresh = async () => {
|
||||
try {
|
||||
await getLocationData()
|
||||
} catch (error: any) {
|
||||
// uni.$u.toast('获取定位失败,可能影响距离计算')
|
||||
}
|
||||
await initializeCoachDetails()
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
|
||||
if (!locationState.latitude || !locationState.longitude) {
|
||||
try {
|
||||
await getLocationData()
|
||||
} catch (error: any) {
|
||||
// uni.$u.toast('获取定位失败,可能影响距离计算')
|
||||
}
|
||||
}
|
||||
|
||||
coachId.value = options?.id || 0
|
||||
|
||||
nextTick(() => {
|
||||
initializeCoachDetails()
|
||||
})
|
||||
})
|
||||
|
||||
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>
|
||||
.coach-detail {
|
||||
padding-bottom: 200rpx;
|
||||
|
||||
:deep(.u-swiper-indicator) {
|
||||
bottom: 100rpx !important;
|
||||
}
|
||||
|
||||
.coach-detail-info {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
margin: -70rpx 30rpx 0 30rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.introduct {
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 14rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #f6f7f8;
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
text {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
&:not(:first-child) {
|
||||
::before {
|
||||
content: '·';
|
||||
position: absolute;
|
||||
left: -15rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏图标
|
||||
.collection {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
.footer {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 -4px 48px 0 #141a231f;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
|
||||
}
|
||||
|
||||
// 服务下架或不存在时
|
||||
.empty {
|
||||
padding-top: 200rpx;
|
||||
|
||||
.empty-bottom {
|
||||
width: 90vw;
|
||||
margin-top: 130rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
uniapp/src/bundle/pages/collection_list/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="collect-list flex flex-col min-h-0 h-full">
|
||||
<u-tabs
|
||||
:list="tabList"
|
||||
bg-color="#ffffff"
|
||||
height="80"
|
||||
:active-color="$theme.primaryColor"
|
||||
inactive-color="#666666"
|
||||
:is-scroll="false"
|
||||
@change="change"
|
||||
:current="current - 1"
|
||||
></u-tabs>
|
||||
|
||||
<view class="flex-1">
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:fixed="false"
|
||||
:empty-view-img="EmptyOrder"
|
||||
height="100%"
|
||||
@query="queryList"
|
||||
>
|
||||
<template v-for="item in dataList" :key="item.id值">
|
||||
<view v-if="current == 1" class="">
|
||||
<!-- 技师卡片 -->
|
||||
<coach-card :data="item" type="1" @confirm="navigateToCoach"></coach-card>
|
||||
</view>
|
||||
<view v-if="current == 2" class="mx-[30rpx] mt-[30rpx] p-[12rpx] rounded-[20rpx] bg-white">
|
||||
<!-- 项目卡片 -->
|
||||
<project-card :data="item"></project-card>
|
||||
</view>
|
||||
<view v-if="current == 3" class="">
|
||||
<shopLists :data="item" type="1"></shopLists>
|
||||
</view>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, shallowRef } from 'vue'
|
||||
import { apiCollectLists } from '@/api/user'
|
||||
import EmptyOrder from '@/static/images/empty/order.png'
|
||||
import { useRoute, useRouter } from 'uniapp-router-next'
|
||||
import { locationState } from '@/hooks/useLocation'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import CoachCard from '@/components/master-worker-card/index.vue'
|
||||
import ProjectCard from '@/components/project-card/index.vue'
|
||||
import shopLists from '@/components/shop-lists/index.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dataList = ref<any>([])
|
||||
const paging = shallowRef<any>(null)
|
||||
const tabList = ref<any>([
|
||||
{
|
||||
name: '技师',
|
||||
type: 1
|
||||
},
|
||||
{
|
||||
name: '项目',
|
||||
type: 2
|
||||
},
|
||||
{
|
||||
name: '店铺',
|
||||
type: 3
|
||||
}
|
||||
])
|
||||
const current = ref<number>(1)
|
||||
|
||||
const change = (index: any) => {
|
||||
current.value = Number(index + 1)
|
||||
console.log(current.value)
|
||||
|
||||
paging.value?.reload()
|
||||
}
|
||||
|
||||
// 初始化收藏
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
console.log(456789, current.value)
|
||||
|
||||
const { lists } = await apiCollectLists({
|
||||
type: current.value,
|
||||
page_no: page_no || 1,
|
||||
page_size: page_size || 10,
|
||||
longitude: locationState.longitude,
|
||||
latitude: locationState.latitude
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToCoach = (id: number) => {
|
||||
router.navigateTo({
|
||||
path: '/bundle/pages/coach_details/index',
|
||||
query: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
if (Number(route.query.type)) {
|
||||
current.value = Number(route.query.type)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.collect {
|
||||
border-radius: 14rpx;
|
||||
background-color: white;
|
||||
margin: 20rpx 20rpx 0 20rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding-top: 300rpx;
|
||||
}
|
||||
</style>
|
||||
254
uniapp/src/bundle/pages/contact_service/index.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<view class="service pt-20">
|
||||
<view class="service-contain">
|
||||
<view class="header-image">
|
||||
<u-image
|
||||
:src="getImageUrl('/resource/image/shopapi/default/service.png')"
|
||||
width="100rpx"
|
||||
height="100rpx"
|
||||
shape="circle"
|
||||
style="margin-top: -50rpx"
|
||||
class="circle"
|
||||
></u-image>
|
||||
</view>
|
||||
|
||||
<!-- way==1 二维码客服 -->
|
||||
<template v-if="serviceData.way == 1">
|
||||
<view class="lg mt-[40rpx] flex justify-center" v-if="serviceData.remarks">
|
||||
{{ serviceData.remarks }}
|
||||
</view>
|
||||
|
||||
<view class="code flex justify-center">
|
||||
<u-image
|
||||
:src="serviceData.qr_code"
|
||||
width="320rpx"
|
||||
height="320rpx"
|
||||
border-radius="20"
|
||||
></u-image>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="mt-[20rpx] mb-[20rpx] xs muted flex justify-center"
|
||||
v-if="serviceData.phone"
|
||||
>
|
||||
客服电话:{{ serviceData.phone }}
|
||||
</view>
|
||||
<view
|
||||
class="xs mt-[20rpx] muted mb-[20rpx] flex justify-center"
|
||||
v-if="serviceData.business_time"
|
||||
>
|
||||
服务时间: {{ serviceData.business_time }}
|
||||
</view>
|
||||
</template>
|
||||
<!-- logo -->
|
||||
<template v-if="serviceData.way != 1">
|
||||
<view class="flex lg justify-center mt-[40rpx]">
|
||||
{{ appStore.config.shop_name }}
|
||||
</view>
|
||||
<view class="code flex justify-center">
|
||||
<u-image
|
||||
:src="appStore.config.shop_logo"
|
||||
width="320rpx"
|
||||
height="320rpx"
|
||||
border-radius="20"
|
||||
></u-image>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- way==2 电话客服 -->
|
||||
|
||||
<template v-if="serviceData.way == 2">
|
||||
<view class="mt-[40rpx] flex justify-center" v-if="serviceData.phone">
|
||||
<view style="text-align: center">
|
||||
<view> 拨打客服热线 </view>
|
||||
<view style="text-decoration: underline" @click="handleCall">
|
||||
{{ serviceData.service_phone }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="pt-[20rpx]" v-if="serviceData.way == 4">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
open-type="contact"
|
||||
class="copy-btn icon-item text-white rounded-[20rpx] flex justify-center items-center"
|
||||
style="line-height: 88rpx"
|
||||
>
|
||||
<text style="line-height: 32rpx" class="text-[28rpx]">联系微信小程序客服</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="pt-[20rpx]" v-else>
|
||||
<view
|
||||
class="rounded-[20rpx] copy-btn flex justify-center items-center text-white lg"
|
||||
v-if="serviceData.way == 1"
|
||||
@click="saveImageQr"
|
||||
>
|
||||
<text class="">保存二维码</text>
|
||||
</view>
|
||||
<view
|
||||
class="rounded-[20rpx] copy-btn flex justify-center items-center text-white lg"
|
||||
v-if="serviceData.way == 2"
|
||||
@click="handleCall"
|
||||
>
|
||||
<text class="">拨打电话</text>
|
||||
</view>
|
||||
<view
|
||||
class="rounded-[20rpx] copy-btn flex justify-center items-center text-white lg"
|
||||
v-if="serviceData.way == 3"
|
||||
@click="handleService"
|
||||
>
|
||||
<text class="">联系企业微信客服</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { toast } from '@/utils/util'
|
||||
import { getKefuConfig } from '@/api/user'
|
||||
import { getClient } from '@/utils/client'
|
||||
import { serviceEnum } from '@/enums/appEnums'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
interface serviceDataObj {
|
||||
business_time: string
|
||||
enterprise_id: string
|
||||
kefu_link: string
|
||||
name: string
|
||||
phone: string
|
||||
qr_code: string
|
||||
remarks: string
|
||||
service_phone: string
|
||||
way: string | number
|
||||
}
|
||||
|
||||
const { getImageUrl } = useAppStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const serviceData = ref<serviceDataObj>({
|
||||
business_time: '',
|
||||
enterprise_id: '',
|
||||
kefu_link: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
qr_code: '',
|
||||
remarks: '',
|
||||
service_phone: '',
|
||||
way: ''
|
||||
})
|
||||
|
||||
// 获取客服信息
|
||||
const getContactService = async (): Promise<void> => {
|
||||
const res = await getKefuConfig()
|
||||
serviceData.value = res[serviceEnum[getClient()]]
|
||||
}
|
||||
|
||||
// 保存二维码
|
||||
const saveImageQr = async (): Promise<void> => {
|
||||
//#ifdef H5
|
||||
toast('长按图片保存')
|
||||
//#endif
|
||||
|
||||
//#ifndef H5
|
||||
try {
|
||||
const res = await uni.getImageInfo({ src: serviceData.value.qr_code })
|
||||
try {
|
||||
await uni.saveImageToPhotosAlbum({ filePath: res.path })
|
||||
toast('保存成功')
|
||||
} catch (e) {
|
||||
const modelRes = await uni.showModal({
|
||||
title: '图片保存失败',
|
||||
content: '请确认是否已开启授权'
|
||||
})
|
||||
if (modelRes.confirm) uni.openSetting()
|
||||
}
|
||||
} catch (err) {
|
||||
toast('请在小程序后台配置downloadFile')
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
//拨打电话
|
||||
const handleCall = () => {
|
||||
if (!serviceData.value.service_phone) {
|
||||
toast('请在后台配置客服电话号码')
|
||||
return
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: serviceData.value.service_phone,
|
||||
success(res) {
|
||||
console.log(res)
|
||||
},
|
||||
fail(err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleService = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.openCustomerServiceChat({
|
||||
extInfo: { url: serviceData.value.kefu_link },
|
||||
corpId: serviceData.value.enterprise_id,
|
||||
success(res: any) {
|
||||
console.log(res)
|
||||
},
|
||||
fail(err: any) {
|
||||
console.log(err)
|
||||
toast('请在后台配置企业微信客服')
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (!serviceData.value.kefu_link) {
|
||||
toast('请在后台配置企业微信客服')
|
||||
return
|
||||
}
|
||||
window.open(serviceData.value.kefu_link, '_self')
|
||||
// #endif
|
||||
}
|
||||
|
||||
getContactService()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid #ffffff;
|
||||
}
|
||||
}
|
||||
.service {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-image: url(../../static/images/service_bg.jpg);
|
||||
background-size: 100% auto;
|
||||
&-contain {
|
||||
width: 620rpx;
|
||||
// height: 750rpx;
|
||||
margin: auto;
|
||||
margin-bottom: 40rpx;
|
||||
padding-bottom: 80rpx;
|
||||
border-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
.code {
|
||||
border-radius: 20rpx;
|
||||
padding-top: 60rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
padding: 0 20rpx;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
.copy-btn {
|
||||
margin: 0 80rpx;
|
||||
height: 88rpx;
|
||||
// @include background_linear(90deg, 0, 100%);
|
||||
background: #000722;
|
||||
}
|
||||
</style>
|
||||
33
uniapp/src/bundle/pages/evaluate_detail/index.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<view class="card">
|
||||
<evaluate-card :data="commentData" :border="false"> </evaluate-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiEvaluateGoodsDetail } from '@/api/goods'
|
||||
import EvaluateCard from '@/components/evaluate-card/index.vue'
|
||||
|
||||
const commentId = ref<number>(0)
|
||||
const commentData = ref<any>([])
|
||||
|
||||
const getData = async () => {
|
||||
commentData.value = await apiEvaluateGoodsDetail({ id: commentId.value })
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
commentId.value = options.id || 0
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.card {
|
||||
border-radius: 20rpx;
|
||||
background-color: white;
|
||||
margin: 20rpx 20rpx 0 20rpx;
|
||||
padding: 10rpx 30rpx 30rpx 30rpx;
|
||||
}
|
||||
</style>
|
||||
118
uniapp/src/bundle/pages/evaluate_goods/components/card.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="card">
|
||||
<view class="card--header flex justify-between col-start">
|
||||
<view class="flex">
|
||||
<u-image :src="user.avatar" width="80" height="80" border-radius="50%"></u-image>
|
||||
<view class="ml-[20rpx]">
|
||||
<view class="text-base normal font-medium">{{ user.nickname }}</view>
|
||||
<view class="text-muted text-xs mt-[10rpx]">{{ create_time }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex">
|
||||
<!-- <u-rate
|
||||
:count="5"
|
||||
v-model="service_comment"
|
||||
size="28"
|
||||
inactive-icon="star-fill"
|
||||
active-color="#ff9600"
|
||||
></u-rate> -->
|
||||
<!-- <view class="ml-[20rpx] lighter text-xs">
|
||||
<text v-if="service_comment == 5">非常好</text>
|
||||
<text v-if="service_comment == 4">好</text>
|
||||
<text v-if="service_comment == 3">一般</text>
|
||||
<text v-if="service_comment == 2">差</text>
|
||||
<text v-if="service_comment == 1">非常差</text>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card--main">
|
||||
<view class="content">
|
||||
{{ comment }}
|
||||
</view>
|
||||
|
||||
<view class="flex flex-wrap">
|
||||
<block v-for="(item3, index) in goods_comment_image" :key="index">
|
||||
<view
|
||||
class="mt-[10rpx]"
|
||||
:class="{ 'mr-[10rpx]': (index + 1) % 4 != 0 }"
|
||||
@click.stop="previewImage(index)"
|
||||
>
|
||||
<u-image
|
||||
:src="item3.uri"
|
||||
width="150"
|
||||
height="150"
|
||||
border-radius="14rpx"
|
||||
></u-image>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="reply normal text-base mt-[20rpx]" v-if="reply">
|
||||
<text class="font-medium">商家回复: </text>
|
||||
<text class="text-[#909399]">
|
||||
{{ reply }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
goods_id: string | number
|
||||
comment: string | null
|
||||
goods_comment_image: string | null
|
||||
reply: string | null
|
||||
create_time: string | null
|
||||
service_comment: string | number | any
|
||||
user: any
|
||||
}>(),
|
||||
{
|
||||
goods_id: '',
|
||||
comment: '',
|
||||
goods_comment_image: '',
|
||||
reply: '',
|
||||
create_time: '',
|
||||
service_comment: '',
|
||||
user: {
|
||||
avatar: '',
|
||||
nickname: ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 查看评价图片
|
||||
const previewImage = (current: number) => {
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls: props.goods_comment_image.map((el) => el.uri)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
border-radius: 20rpx;
|
||||
background-color: white;
|
||||
margin: 20rpx 20rpx 0 20rpx;
|
||||
padding: 30rpx;
|
||||
&--header {
|
||||
width: 100%;
|
||||
}
|
||||
&--main {
|
||||
.content {
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #222222;
|
||||
}
|
||||
.reply {
|
||||
word-break: break-all;
|
||||
padding: 24rpx 20rpx;
|
||||
background-color: #f6f6f6;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
77
uniapp/src/bundle/pages/evaluate_goods/components/list.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
:auto="i == index"
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:data-key="i"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<block v-for="(item, index) in dataList" :key="index">
|
||||
<Card
|
||||
:comment="item.comment"
|
||||
:goods_comment_image="item.goods_comment_image"
|
||||
:reply="item.reply"
|
||||
:create_time="item.create_time"
|
||||
:goods_id="item.goods_id"
|
||||
:service_comment="item.service_comment"
|
||||
:user="item.user"
|
||||
></Card>
|
||||
</block>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, shallowRef, unref } from 'vue'
|
||||
import Card from './card.vue'
|
||||
import { apiEvaluateGoodsLists } from '@/api/goods'
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
const isFirst = ref<boolean>(true)
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
goods_id: number
|
||||
i: number
|
||||
index: number
|
||||
cid: number
|
||||
}>(),
|
||||
{
|
||||
goods_id: 0,
|
||||
cid: 0
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (props.i == props.index && unref(isFirst)) {
|
||||
isFirst.value = false
|
||||
paging.value?.reload()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const queryList = async (pageNo: number, pageSize: number) => {
|
||||
try {
|
||||
const { lists } = await apiEvaluateGoodsLists({
|
||||
goods_id: props.goods_id,
|
||||
pageNo,
|
||||
pageSize,
|
||||
id: props.cid
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
188
uniapp/src/bundle/pages/evaluate_goods/index.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="goods-comment flex flex-col min-h-0 h-full">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="用户评价"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, 0)` }"
|
||||
:title-color="'#000'"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<view class="flex flex-wrap gap-y-2 gap-x-2 p-[30rpx]">
|
||||
<view
|
||||
v-for="(item, index) in tabList"
|
||||
:key="item.id"
|
||||
class="px-[26rpx] py-[8rpx] rounded-full"
|
||||
:class="{
|
||||
'bg-primary-light-9 text-primary': current == index,
|
||||
'bg-white text-main': current != index
|
||||
}"
|
||||
@click="handleChange(index)"
|
||||
>
|
||||
<u-icon size="28" name="photo" v-if="item.name == '有图'"></u-icon>
|
||||
<u-icon size="28" name="thumb-up" v-if="item.name == '好评'"></u-icon>
|
||||
{{ item.name }}
|
||||
{{ item.count }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex-1">
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
:auto="true"
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
:empty-view-img="EmptyEvaluate"
|
||||
@query="queryList"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
class="m-[24rpx] px-[24rpx] py-[30rpx] bg-white rounded-[20rpx]"
|
||||
>
|
||||
<view class="flex items-center w-full">
|
||||
<u-image
|
||||
:src="item.user.avatar"
|
||||
width="80"
|
||||
height="80"
|
||||
borderRadius="50%"
|
||||
class="flex-none"
|
||||
></u-image>
|
||||
<view class="flex flex-col justify-between ml-2 w-full">
|
||||
<view class="text-base font-medium w-full">
|
||||
{{ item.user.nickname }}
|
||||
</view>
|
||||
<view class="mt-1 text-muted text-xs">
|
||||
{{ item.create_time }}
|
||||
</view>
|
||||
</view>
|
||||
<u-rate
|
||||
:count="5"
|
||||
v-model="item.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"> {{ item.comment }}</view>
|
||||
<view class="mt-3 grid gap-2 grid-cols-4">
|
||||
<view
|
||||
v-for="(commentImage, index) in item.goods_comment_image"
|
||||
:key="index"
|
||||
class="mt-[10rpx]"
|
||||
:class="{ 'mr-[10rpx]': (index + 1) % 4 != 0 }"
|
||||
@click.stop="previewImage(item.goods_comment_image, index)"
|
||||
>
|
||||
<u-image
|
||||
:src="commentImage.uri"
|
||||
width="150"
|
||||
height="150"
|
||||
border-radius="14rpx"
|
||||
></u-image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed, shallowRef} from 'vue'
|
||||
import {onLoad} from '@dcloudio/uni-app'
|
||||
import {apiEvaluateGoodsCategory, apiEvaluateGoodsLists} from '@/api/goods'
|
||||
import {useUserStore} from '@/stores/user'
|
||||
import {useRoute} from "uniapp-router-next";
|
||||
import EmptyEvaluate from '@/static/images/empty/evaluate.png'
|
||||
|
||||
const route = useRoute()
|
||||
// 是否登录
|
||||
const userStore = useUserStore()
|
||||
const isLogin = computed(() => userStore.token)
|
||||
|
||||
const tabList = ref<any>([])
|
||||
const current = ref<number>(0)
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
|
||||
const handleChange = (index: number) => {
|
||||
current.value = Number(index)
|
||||
paging.value.reload()
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const data = await apiEvaluateGoodsCategory({
|
||||
goods_id: route.query.id
|
||||
})
|
||||
tabList.value = [...data.comment]
|
||||
paging.value.reload()
|
||||
}
|
||||
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const {lists} = await apiEvaluateGoodsLists({
|
||||
goods_id: route.query.id,
|
||||
page_no: page_no || 1,
|
||||
page_size: page_size || 10,
|
||||
id: tabList.value[current.value].id
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
const previewImage = (url: any, index: number | string) => {
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: url.map((item: any) => item.uri)
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: scroll;
|
||||
|
||||
swiper {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.List {
|
||||
height: calc(100vh - 86px - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
198
uniapp/src/bundle/pages/evaluate_list/components/list.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
:auto="i == index"
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:data-key="i"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<block v-for="(item2, index) in dataList" :key="index">
|
||||
<view class="card">
|
||||
<view class="u-flex justify-between">
|
||||
<view class="text-[#909399]">上门服务</view>
|
||||
<view>{{ item2.is_comment ? '已评价' : '待评价' }}</view>
|
||||
</view>
|
||||
<view class="u-flex justify-between mt-[20rpx] mb-[20rpx]">
|
||||
<view class="font-bold text-3xl">
|
||||
<text>{{ item2.appoint_date }}</text>
|
||||
<text class="text-primary ml-[20rpx]">
|
||||
{{ item2.appoint_time }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="u-flex"
|
||||
style="background-color: #f6f7f8; padding: 6rpx 10rpx; border-radius: 40rpx"
|
||||
>
|
||||
<u-image
|
||||
:src="item2.coach_info.work_photo"
|
||||
width="40"
|
||||
height="40"
|
||||
shape="circle"
|
||||
></u-image>
|
||||
<view class="ml-[16rpx]">
|
||||
{{ item2.coach_info.name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="goods flex">
|
||||
<u-image
|
||||
:src="item2.goods_snap.image"
|
||||
width="136"
|
||||
height="136"
|
||||
border-radius="20rpx"
|
||||
></u-image>
|
||||
|
||||
<view class="flex-1 ml-[20rpx]">
|
||||
<view class="text-base font-medium text-main line-clamp-1">
|
||||
{{ item2.goods_snap.name }}
|
||||
</view>
|
||||
<view class="flex justify-between items-center flex-1 mt-1">
|
||||
<view class="text-xs text-muted">
|
||||
服务时间:{{ item2.goods_snap.duration }}分钟
|
||||
</view>
|
||||
<view class="text-muted"> x{{ item2.goods_snap.goods_num || 1 }} </view>
|
||||
</view>
|
||||
<view class="mt-1">
|
||||
<price
|
||||
:content="item2.goods_snap.price"
|
||||
main-size="32rpx"
|
||||
minor-size="20rpx"
|
||||
font-weight="900"
|
||||
color="#E86016"
|
||||
></price>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer flex justify-between items-center">
|
||||
<view class="text-primary font-bold text-2xl">¥{{ item2.goods_price }}</view>
|
||||
<view>
|
||||
<u-button
|
||||
v-if="item2.is_comment == 0"
|
||||
type="primary"
|
||||
size="medium"
|
||||
@click="
|
||||
router.navigateTo(
|
||||
`/bundle/pages/evaluate_submit/index?order_goods_id=${item2.id}`
|
||||
)
|
||||
"
|
||||
>
|
||||
去评价
|
||||
</u-button>
|
||||
<u-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="medium"
|
||||
@click="
|
||||
router.navigateTo(
|
||||
`/bundle/pages/evaluate_detail/index?id=${item2.id}`
|
||||
)
|
||||
"
|
||||
>
|
||||
查看评价
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, shallowRef, unref } from 'vue'
|
||||
import { apiEvaluateLists } from '@/api/user'
|
||||
import { useRouter } from 'uniapp-router-next'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
type: number
|
||||
count: number
|
||||
i: number
|
||||
index: number
|
||||
}>(),
|
||||
{
|
||||
type: 0,
|
||||
count: 0
|
||||
}
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
const isFirst = ref<boolean>(true)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (props.i == props.index && unref(isFirst)) {
|
||||
// isFirst.value = false
|
||||
paging.value?.reload()
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const { lists } = await apiEvaluateLists({
|
||||
type: props.type,
|
||||
page_no,
|
||||
page_size
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
onShow(async () => {
|
||||
// 已评价onShow 不刷新
|
||||
if (Number(props.index) !== 1) {
|
||||
await nextTick()
|
||||
paging.value?.reload()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
border-radius: 20rpx;
|
||||
background-color: white;
|
||||
margin: 20rpx 20rpx 0 20rpx;
|
||||
padding: 30rpx 24rpx;
|
||||
|
||||
.goods {
|
||||
padding: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
.tag {
|
||||
color: #909399;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 10rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 4rpx;
|
||||
display: inline-block;
|
||||
margin-right: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tag:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
border-top: 1px solid #f7f7f7;
|
||||
margin-top: 30rpx;
|
||||
padding: 20rpx 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
474
uniapp/src/bundle/pages/evaluate_list/components/tabs.vue
Normal file
@@ -0,0 +1,474 @@
|
||||
<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
|
||||
class="tab-item line1"
|
||||
:id="'tab-item-' + index"
|
||||
v-for="(item, index) in list"
|
||||
: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 v-if="isLogin && auth"></slot>
|
||||
|
||||
<u-empty
|
||||
v-else
|
||||
:src="'/static/images/empty/order.png'"
|
||||
text="您还没有登录~"
|
||||
mode="data"
|
||||
:icon-size="300"
|
||||
margin-top="300"
|
||||
color="#888888"
|
||||
>
|
||||
<template #bottom>
|
||||
<view class="mt-4">
|
||||
<u-button
|
||||
shape="circle"
|
||||
@click="goPage('/pages/login/login')"
|
||||
:ripple="true"
|
||||
:hair-line="false"
|
||||
type="info"
|
||||
>
|
||||
去登录</u-button
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
</u-empty>
|
||||
</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'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 是否登录
|
||||
const isLogin = computed(() => userStore.token)
|
||||
|
||||
// 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 // 切换动画
|
||||
auth?: boolean // 权限登录验证
|
||||
}>(),
|
||||
{
|
||||
isScroll: true,
|
||||
current: 0,
|
||||
height: 80,
|
||||
fontSize: 28,
|
||||
duration: 0.3,
|
||||
activeColor: '#2073F4',
|
||||
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
|
||||
auth: false
|
||||
}
|
||||
)
|
||||
|
||||
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 goPage = (url: any) => {
|
||||
uni.navigateTo({ url: url })
|
||||
}
|
||||
|
||||
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>
|
||||
81
uniapp/src/bundle/pages/evaluate_list/index.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="container">
|
||||
<!-- :auth="true" 是表示需要权限登录的 -->
|
||||
<tabs
|
||||
:current="current"
|
||||
@change="handleChange"
|
||||
height="80"
|
||||
bar-width="60"
|
||||
:barStyle="{ bottom: '0' }"
|
||||
:auth="true"
|
||||
:isScroll="false"
|
||||
:activeColor="$theme.primaryColor"
|
||||
>
|
||||
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
|
||||
<view class="List pt-[20rpx]" v-if="isLogin">
|
||||
<List :type="item.type" :count="item.count" :i="i" :index="current"></List>
|
||||
</view>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import List from './components/list.vue'
|
||||
import tab from '@/components/tab/tab.vue'
|
||||
import tabs from './components/tabs.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const tabList = ref([
|
||||
{
|
||||
name: '待评价',
|
||||
type: 0,
|
||||
count: 0
|
||||
},
|
||||
{
|
||||
name: '已评价',
|
||||
type: 1,
|
||||
count: 0
|
||||
}
|
||||
])
|
||||
const current = ref<number>(0)
|
||||
const userStore = useUserStore()
|
||||
// 是否登录
|
||||
const isLogin = computed(() => userStore.token)
|
||||
|
||||
const handleChange = (index: number) => {
|
||||
current.value = Number(index)
|
||||
}
|
||||
|
||||
onLoad(async (options: { type?: any }) => {
|
||||
current.value = options?.type * 1 || 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: scroll;
|
||||
swiper {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.List {
|
||||
height: calc(100vh - 80rpx - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
154
uniapp/src/bundle/pages/evaluate_submit/index.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="comment-submit flex flex-col min-h-0 h-full">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="评价"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, 0)` }"
|
||||
:title-color="'#000'"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<!-- Header Start -->
|
||||
<view class="comment-header flex flex-col justify-center items-center">
|
||||
<view class="font-bold text-base mb-4">您觉得这次服务怎么样?</view>
|
||||
<u-rate
|
||||
v-model="formData.service_comment"
|
||||
:count="5"
|
||||
:min-count="1"
|
||||
inactive-icon="star-fill"
|
||||
size="56"
|
||||
active-color="#d86930"
|
||||
inactive-color="#eaeaeb"
|
||||
></u-rate>
|
||||
<view class="mt-3 text-content text-xs">
|
||||
<text v-if="formData.service_comment == 5">非常好,很满意</text>
|
||||
<text v-else-if="formData.service_comment == 4">好,比较满意</text>
|
||||
<text v-else-if="formData.service_comment == 3">一般,有待改进</text>
|
||||
<text v-else-if="formData.service_comment == 2">差,体验不好</text>
|
||||
<text v-else-if="formData.service_comment == 1">非常差,不喜欢</text>
|
||||
<text v-else>请选择评价</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Header End -->
|
||||
|
||||
<!-- Main Start -->
|
||||
<view class="comment-main flex-1">
|
||||
<view class="card">
|
||||
<view class="content">
|
||||
<u-input
|
||||
v-model="formData.comment"
|
||||
type="textarea"
|
||||
height="200"
|
||||
placeholder="技师服务如何?与您的期待相符嘛?快来评价吧!"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="mt-4">
|
||||
<uploader
|
||||
v-model="formData.image"
|
||||
:deletable="true"
|
||||
preview-size="140rpx"
|
||||
:maxUpload="4"
|
||||
image-fit="aspectFill"
|
||||
:mutiple="true"
|
||||
:showProgress="true"
|
||||
v-model:isUploaded="isUploaded"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="comment-footer safe-area-inset-bottom">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="submit"
|
||||
>
|
||||
提交评价
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive,ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiEvaluateAdd } from '@/api/user'
|
||||
import Uploader from '@/components/uploader/index.vue'
|
||||
|
||||
interface FormDataType {
|
||||
order_goods_id: number
|
||||
service_comment: number | string
|
||||
comment: string | number
|
||||
image: Array<string | null>
|
||||
}
|
||||
|
||||
const formData = reactive<FormDataType>({
|
||||
order_goods_id: 0,
|
||||
service_comment: 5,
|
||||
comment: '',
|
||||
image: []
|
||||
})
|
||||
const isUploaded = ref(true)
|
||||
|
||||
const submit = async (): Promise<void> => {
|
||||
if (!isUploaded.value) {
|
||||
uni.$u.toast('图片上传中,请稍后')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await apiEvaluateAdd(formData)
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.log('提交评价: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoad((options: any) => {
|
||||
formData.order_goods_id = options.order_goods_id || 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.comment-submit {
|
||||
.comment-header {
|
||||
margin: 50rpx 30rpx 0 30rpx;
|
||||
padding: 28rpx 0;
|
||||
border-radius: 24rpx;
|
||||
background: linear-gradient(180deg, #fee5d766 -20.8%, #FFF 57.99%);
|
||||
}
|
||||
|
||||
.comment-main {
|
||||
margin: 30rpx;
|
||||
.card {
|
||||
border-radius: 24rpx;
|
||||
background-color: #ffffff;
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-footer {
|
||||
padding: 20rpx 30rpx;
|
||||
flex-shrink: 0;
|
||||
background: #FFF;
|
||||
padding-bottom: calc(constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(env(safe-area-inset-bottom));
|
||||
box-shadow: 0 -4rpx 48rpx 0 #141a231f;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
304
uniapp/src/bundle/pages/location/index.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 搜索框 -->
|
||||
<view class="flex search">
|
||||
<!-- 左侧城市 -->
|
||||
<view class="flex search--city" @click="goPage('/bundle/pages/city/index')">
|
||||
<text class="mr-[15rpx]">{{ locationState.name }}</text>
|
||||
<u-icon name="arrow-down" size="20"></u-icon>
|
||||
</view>
|
||||
<!-- 右侧搜索城市 -->
|
||||
<input
|
||||
type="text"
|
||||
class="flex-1 search--search"
|
||||
placeholder="搜索地点"
|
||||
v-model="keyword"
|
||||
@confirm="search"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 地图组件 -->
|
||||
<view class="mt-[30rpx] mb-[30rpx] map">
|
||||
<map
|
||||
id="myMap"
|
||||
theme="normal"
|
||||
style="width: 100%; height: 400rpx; border-radius: 20rpx; overflow: hidden"
|
||||
show-location
|
||||
:enable-overlooking="false"
|
||||
:scale="13"
|
||||
@regionchange="regionchange"
|
||||
:latitude="latitude"
|
||||
:longitude="longitude"
|
||||
:markers="markersList"
|
||||
>
|
||||
<cover-view class="iconImg">
|
||||
<cover-image
|
||||
class="img"
|
||||
src="https://hellouniapp.dcloud.net.cn/static/location.png"
|
||||
></cover-image>
|
||||
</cover-view>
|
||||
|
||||
<!-- #ifdef MP -->
|
||||
<!-- <cover-view class="origin" @click="originLocation">
|
||||
<cover-image
|
||||
class="origin_icon"
|
||||
src="@/bundle/static/images/map/origin.png"
|
||||
></cover-image>
|
||||
<cover-view class="text-xs mt-[15rpx]">定位</cover-view>
|
||||
</cover-view> -->
|
||||
<!-- #endif -->
|
||||
</map>
|
||||
</view>
|
||||
|
||||
<view class="address">
|
||||
<!-- 地址列表 -->
|
||||
<view v-if="addressList?.length">
|
||||
<scroll-view scroll-y="true" class="scroll-Y" :show-scrollbar="false">
|
||||
<view
|
||||
class="address-item"
|
||||
v-for="(item, index) in addressList"
|
||||
:key="index"
|
||||
@click="choiceAddress(item)"
|
||||
>
|
||||
<view class="u-flex justify-between">
|
||||
<view class="u-flex">
|
||||
<u-icon name="map"></u-icon>
|
||||
<view class="font-bold ml-[10rpx] w-[500rpx] truncate">{{
|
||||
item.title
|
||||
}}</view>
|
||||
</view>
|
||||
<view class="text-[#909399] text-[24rpx]">{{ computeDistance(item._distance) }}</view>
|
||||
</view>
|
||||
<view
|
||||
class="text-[#909399] text-[24rpx] ml-[40rpx] mt-[10rpx] w-[500rpx] truncate"
|
||||
>{{ item.address }}</view
|
||||
>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 无地址 -->
|
||||
<view class="flex flex-col items-center pb-3" v-else>
|
||||
<view class="mt-[40rpx]">
|
||||
<u-image src="@/bundle/static/images/map/good.png" width="290" height="200" />
|
||||
</view>
|
||||
<view class="my-[30rpx]">
|
||||
<text>没有数据哦</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 定位提示弹窗 -->
|
||||
<modal-popup
|
||||
v-model:show="showLocationModal"
|
||||
:title="errorTitle"
|
||||
:content="errorContent"
|
||||
@refresh=""
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { getAddress } from '@/api/app'
|
||||
import { locationState, useLocation } from '@/hooks/useLocation'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const {
|
||||
showLocationModal,
|
||||
errorTitle,
|
||||
errorContent,
|
||||
getLocationData,
|
||||
setLocationData
|
||||
} = useLocation()
|
||||
//经纬度
|
||||
const latitude = ref()
|
||||
const longitude = ref()
|
||||
//搜索关键词
|
||||
const keyword = ref('')
|
||||
|
||||
// 创建map的上下文对象, 从而操控map组件
|
||||
const mapCtx = uni.createMapContext('myMap')
|
||||
|
||||
// 地图标记点
|
||||
const markersList = ref<any>([])
|
||||
|
||||
// 获取地址列表
|
||||
const addressList = ref<any>([])
|
||||
const getAddressList = async () => {
|
||||
const { data } = await getAddress({
|
||||
keyword: keyword.value,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value
|
||||
})
|
||||
addressList.value = data
|
||||
renderPointToMap(addressList.value)
|
||||
}
|
||||
|
||||
//计算地址距离
|
||||
const computeDistance = (distance:number) => {
|
||||
let res = distance + 'm'
|
||||
if (distance > 1000) {
|
||||
res = (distance / 1000).toFixed(3) + 'km'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 渲染所有地址
|
||||
const renderPointToMap = (arr: any[]) => {
|
||||
if (!arr.length) return
|
||||
markersList.value = []
|
||||
arr.forEach((item) => {
|
||||
if (!item.status) return
|
||||
markersList.value.push({
|
||||
id: item.id,
|
||||
latitude: +item.latitude,
|
||||
longitude: +item.longitude,
|
||||
iconPath: '/bundle/static/images/map/end_point_icon.png',
|
||||
height: 30,
|
||||
width: 20
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 滑动地图更新当前定位
|
||||
const regionchange = (e: any) => {
|
||||
// #ifdef WEB
|
||||
if (e.detail.type == 'end' && (e.detail.causedBy == 'update' || e.detail.causedBy == 'drag')) {
|
||||
mapCtx.getCenterLocation({
|
||||
success: async (res: any) => {
|
||||
latitude.value = res.latitude
|
||||
longitude.value = res.longitude
|
||||
getAddressList()
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
if (e.type == 'end' && (e.causedBy == 'update' || e.causedBy == 'drag')) {
|
||||
mapCtx.getCenterLocation({
|
||||
success: async (res: any) => {
|
||||
latitude.value = res.latitude
|
||||
longitude.value = res.longitude
|
||||
getAddressList()
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 回到我的位置
|
||||
// const originLocation = async () => {
|
||||
// mapCtx.moveToLocation({latitude:latitude.value,longitude:longitude.value})
|
||||
// await getLocationData()
|
||||
// getAddressList()
|
||||
// }
|
||||
|
||||
//搜索
|
||||
const search = async () => {
|
||||
uni.showLoading({
|
||||
title: '搜索中...',
|
||||
mask: true
|
||||
})
|
||||
await getAddressList()
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
//选择地址
|
||||
const choiceAddress = (res: any) => {
|
||||
const latitude = res.location.lat
|
||||
const longitude = res.location.lng
|
||||
const info = {
|
||||
name: locationState.name,
|
||||
city_id: locationState.city_id,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
addressName: res.title
|
||||
}
|
||||
setLocationData(info)
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 监听位置变化并更新
|
||||
watch(
|
||||
() => locationState.latitude,
|
||||
(val) => {
|
||||
if (val) {
|
||||
latitude.value = locationState.latitude
|
||||
longitude.value = locationState.longitude
|
||||
getAddressList()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const goPage = (url: string) => {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
|
||||
.search {
|
||||
height: 100%;
|
||||
padding: 15rpx 30rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: #ffffff;
|
||||
|
||||
&--city {
|
||||
color: #222;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
&--search {
|
||||
margin-left: 30rpx;
|
||||
padding-left: 30rpx;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
}
|
||||
.map {
|
||||
position: relative;
|
||||
.iconImg {
|
||||
position: absolute;
|
||||
top: 46%;
|
||||
left: 47%;
|
||||
.img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
.origin {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
text-align: center;
|
||||
border: 1rpx solid rgba(187, 187, 187, 1);
|
||||
.origin_icon {
|
||||
position: relative;
|
||||
top: 15%;
|
||||
left: 30%;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.address {
|
||||
background-color: #ffffff;
|
||||
padding: 30rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
.address-item {
|
||||
padding-bottom: 30rpx;
|
||||
margin: 20rpx 0;
|
||||
border-bottom: 1px solid #f4f4f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
uniapp/src/bundle/pages/master_worker_credential/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 认证信息 -->
|
||||
<template>
|
||||
<view class="px-[30rpx] py-[20rpx] real_name_info">
|
||||
|
||||
<view class="bg-white rounded-lg">
|
||||
<view class="flex items-center flex-1 p-[32rpx]">
|
||||
<u-image :src="info?.work_photo" width="124" height="124" shape="circle"></u-image>
|
||||
<view class="ml-3">
|
||||
<view class="text-xl font-black line-clamp-1">
|
||||
{{ info?.name || '-' }}
|
||||
</view>
|
||||
<view class="mt-[16rpx]">
|
||||
{{ info?.id_card || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 职业资格证 -->
|
||||
<view class="bg-white rounded-lg px-[20rpx] py-[30rpx] mt-[20rpx]" v-if="info.certification">
|
||||
<view class="title mb-2 flex items-center text-center">
|
||||
<view class="block"></view>
|
||||
<view class="ml-2">职业资格证</view>
|
||||
</view>
|
||||
<u-image :src="info.certification" width="650" height="340" mode="aspectFit"></u-image>
|
||||
</view>
|
||||
|
||||
<!-- 健康证 -->
|
||||
<view class="bg-white rounded-lg px-[20rpx] py-[30rpx] mt-[20rpx]" v-if="info.health_certificate">
|
||||
<view class="title mb-2 flex items-center text-center">
|
||||
<view class="block"></view>
|
||||
<view class="ml-2">健康证</view>
|
||||
</view>
|
||||
<u-image :src="info.health_certificate" width="650" height="340" mode="aspectFit"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useRoute } from 'uniapp-router-next'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const info = ref({
|
||||
work_photo: '',
|
||||
name: '',
|
||||
id_card: '',
|
||||
certification: '',
|
||||
health_certificate: '',
|
||||
})
|
||||
|
||||
|
||||
onLoad(() => {
|
||||
console.log('route.query =>', route.query)
|
||||
info.value = route.query
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.real_name_info {
|
||||
.title {
|
||||
.block {
|
||||
width: 4rpx;
|
||||
height: 28rpx;
|
||||
background-color: #25C373;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="bottom fixed z-50 bottom-0 bg-white w-full pt-[20rpx] px-[30rpx]">
|
||||
<!-- <u-button v-if="true" type="primary" @click="beClick">加入商家</u-button> -->
|
||||
<!-- <u-button v-if="true" type="primary" plain @click="beClick">重新申请</u-button> -->
|
||||
<u-button v-if="true" type="primary" @click="beClick">加收藏</u-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
//点击
|
||||
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>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<view>
|
||||
<evaluateCard></evaluateCard>
|
||||
<evaluateCard></evaluateCard>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import evaluateCard from '@/components/evaluate-card/index.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<view class="py-[30rpx] px-[24rpx] bg-white rounded-lg mt-4 h-[1000rpx]">
|
||||
<view class="text-lg font-normal font-bold">自我介绍</view>
|
||||
<u-parse html=""></u-parse>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<!-- <u-sticky h5-nav-height="0" bg-color="transparent"> -->
|
||||
<!-- <u-navbar
|
||||
:is-back="false"
|
||||
title=""
|
||||
:border-bottom="false"
|
||||
:title-bold="true"
|
||||
:fixed="false"
|
||||
:background="{ background: 'rgba(256,256, 256, 1)' }"
|
||||
> -->
|
||||
<view>
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
title="技师详情"
|
||||
:border-bottom="false"
|
||||
:title-bold="true"
|
||||
:fixed="false"
|
||||
title-color="#000"
|
||||
back-icon-color="#000"
|
||||
:background="{
|
||||
background: percent == 0 ? 'rgba(256,256, 256, 0)' : 'rgba(256,256, 256, 1)'
|
||||
}"
|
||||
/>
|
||||
<!-- <view class="absolute w-full z-[999] p-2">
|
||||
<tips></tips>
|
||||
</view> -->
|
||||
</view>
|
||||
<!-- </u-sticky> -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue'
|
||||
// import tips from '../tips/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
percent: {
|
||||
type: Number,
|
||||
defualt: 0
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<view>
|
||||
<projectCard></projectCard>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import projectCard from '@/components/project-card/index.vue'
|
||||
</script>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="flex items-center bg-[#FEF4EB] px-[30rpx] py-[12rpx] rounded-lg">
|
||||
<u-image mode="aspectFit" height="36" width="120" :src="anxingouLogo"></u-image>
|
||||
<view class="text-[#CB9F5D] text-sm ml-4 tips">
|
||||
<text>资质认证</text>
|
||||
<text>资质认证</text>
|
||||
<text>资质认证</text>
|
||||
</view>
|
||||
<u-icon color="#CB9F5D" class="ml-auto" size="28" name="arrow-right"></u-icon>
|
||||
</view>
|
||||
<view class="px-[24rpx] rounded-lg bg-white">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="text-xl font-bold">王芳芳</view>
|
||||
<view class="text-[#C38925]">最早可约16:00</view>
|
||||
</view>
|
||||
<view class="text-info mt-2 introduct">
|
||||
<text>草本外敷</text>
|
||||
<text>草本外敷</text>
|
||||
<text>草本外敷</text>
|
||||
</view>
|
||||
<view class="text-xs mt-2 flex justify-between">
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="user"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">已服务</text>
|
||||
<text class="text-primary">688人</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="favor"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">好评率</text>
|
||||
<text class="text-primary">98%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<u-image height="24" width="24" :src="distance"></u-image>
|
||||
<view class="ml-1">
|
||||
<text class="text-info">距离</text>
|
||||
<text class="text-primary">3.6km</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-lg p-2 bg-page mt-4 flex">
|
||||
<u-image src="" width="80" height="80" borderRadius="40"></u-image>
|
||||
<view class="ml-2 flex flex-col justify-between flex-1 min-w-0">
|
||||
<view class="text-sm">为乳糖中医馆</view>
|
||||
<view class="text-xs text-info">
|
||||
<text>评分:4.5</text>
|
||||
<text class="ml-4">人均:¥188</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex flex-col justify-between items-center text-info">
|
||||
<view class="text-sm">4.2km</view>
|
||||
<view class="text-xs">距离</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import price from '@/components/price/price.vue'
|
||||
import user from '@/bundle/static/icon/user.png'
|
||||
import distance from '@/bundle/static/icon/distance.png'
|
||||
import favor from '@/bundle/static/icon/favor.png'
|
||||
import anxingouLogo from '@/bundle/static/images/anxingouLogo.png'
|
||||
</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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
text {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
&:not(:first-child) {
|
||||
::before {
|
||||
content: '·';
|
||||
position: absolute;
|
||||
left: -15rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-swiper height="750" mode="number" indicatorPos="bottomRight" :list="list"></u-swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
image: 'https://cdn.uviewui.com/uview/swiper/1.jpg',
|
||||
title: '昨夜星辰昨夜风,画楼西畔桂堂东'
|
||||
},
|
||||
{
|
||||
image: 'https://cdn.uviewui.com/uview/swiper/2.jpg',
|
||||
title: '身无彩凤双飞翼,心有灵犀一点通'
|
||||
},
|
||||
{
|
||||
image: 'https://cdn.uviewui.com/uview/swiper/3.jpg',
|
||||
title: '谁念西风独自凉,萧萧黄叶闭疏窗,沉思往事立残阳'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="bg-warning-light-7 text-warning w-full p-2 rounded-lg">
|
||||
温馨提示:商家正在审核中
|
||||
</view>
|
||||
<view class="bg-error-light-7 text-error w-full p-2 rounded-lg">
|
||||
拒绝原因:的阿法甲联赛独立思考理解了啊极乐世界阿里贱啊弗雷斯科打卡了 历史简单快乐啊
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
58
uniapp/src/bundle/pages/master_worker_detail/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<view class="relative">
|
||||
<navbar :percent="percent"></navbar>
|
||||
<view class="absolute top-0 left-0 w-full z-[-1]">
|
||||
<view class="top-0 left-0 w-full">
|
||||
<wokerSwiper />
|
||||
<view class="absolute w-full z-10 mt-[-50rpx] px-[30rpx] pb-[200rpx]">
|
||||
<side></side>
|
||||
<introduce></introduce>
|
||||
<view class="rounded-lg bg-white p-2 mt-[24rpx] px-[20px]">
|
||||
<tabs
|
||||
:isScroll="false"
|
||||
activeColor="#F36161"
|
||||
inactiveColor="#666"
|
||||
:current="current"
|
||||
height="80"
|
||||
bar-width="60"
|
||||
>
|
||||
<tab name="服务项目">
|
||||
<project />
|
||||
</tab>
|
||||
<tab name="用户评价">
|
||||
<evaluate />
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<bottom></bottom>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import navbar from './components/nav/index.vue'
|
||||
import wokerSwiper from './components/swiper/index.vue'
|
||||
import side from './components/side/index.vue'
|
||||
import introduce from './components/introduce/index.vue'
|
||||
import tab from '@/components/tab/tab.vue'
|
||||
import tabs from '@/components/tabs/tabs.vue'
|
||||
import project from './components/project/index.vue'
|
||||
import evaluate from './components/evaluate/index.vue'
|
||||
import bottom from './components/bottom/index.vue'
|
||||
import { onPageScroll } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const scrollTop = ref<number>(0)
|
||||
const percent = ref<number>(0)
|
||||
|
||||
const current = 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
|
||||
// console.log(percent.value)
|
||||
})
|
||||
</script>
|
||||
317
uniapp/src/bundle/pages/order_pay/order_pay.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="order-pay flex flex-col min-h-0 h-full bg-white">
|
||||
<view class="header flex justify-center items-center flex-col">
|
||||
<view class="order-amount ">
|
||||
<text style="font-size: 76rpx">¥</text>
|
||||
{{ payState.order_amount || '100.00' }}
|
||||
</view>
|
||||
<view class="mt-2 text-muted text-xs">需要支付</view>
|
||||
<!-- <view-->
|
||||
<!-- class="box-time flex"-->
|
||||
<!-- v-if="timeStamp >= 0 && params.from === 'order'"-->
|
||||
<!-- >-->
|
||||
<!-- 支付剩余时间-->
|
||||
<!-- <u-count-down-->
|
||||
<!-- :timestamp="timeStamp"-->
|
||||
<!-- @end="handleCountDownEnd"-->
|
||||
<!-- format="mm:ss"-->
|
||||
<!-- :font-size="26"-->
|
||||
<!-- :separator-size="26"-->
|
||||
<!-- />-->
|
||||
<!-- </view>-->
|
||||
</view>
|
||||
<view class="main">
|
||||
<view class="text-main text-lg font-medium">支付方式</view>
|
||||
<u-radio-group v-model="payWay" :key="payWay" class="w-full" :active-color="$theme.primaryColor">
|
||||
<view
|
||||
v-for="item in payData"
|
||||
:key="item.id"
|
||||
class="payment-item w-full"
|
||||
@click="selectPayWay(item.pay_way)"
|
||||
>
|
||||
<label class="flex items-center justify-between w-full">
|
||||
<view class="flex items-center">
|
||||
<u-image
|
||||
:src="item.image"
|
||||
width="48"
|
||||
height="48"
|
||||
shape="circle"
|
||||
/>
|
||||
<view class="pl-[20rpx] u-flex">
|
||||
<view class="text-[28rpx] text-[#333]">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view class="pl-[10rpx] text-[24rpx] text-muted">
|
||||
{{ item.extra || '' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<u-radio :name="item.pay_way"></u-radio>
|
||||
</view>
|
||||
</label>
|
||||
</view>
|
||||
</u-radio-group>
|
||||
</view>
|
||||
<view class="footer w-full p-[20rpx] mb-[80rpx]">
|
||||
<u-button
|
||||
type="primary"
|
||||
:loading="isLock"
|
||||
@click="handlePay"
|
||||
>
|
||||
立即支付
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付查询弹窗 -->
|
||||
<payment-check
|
||||
v-model:show="payState.showCheck"
|
||||
:from="payState.from"
|
||||
:order-id="payState.orderId"
|
||||
@fail="handlePayResult(PayStatusEnum.FAIL)"
|
||||
@success="handlePayResult(PayStatusEnum.SUCCESS)"
|
||||
/>
|
||||
|
||||
<!-- 支付宝弹窗 -->
|
||||
<payment-alipay
|
||||
v-model:show="payState.showAlipay"
|
||||
:alipay-link="alipayLink"
|
||||
@check="payState.showCheck = true"
|
||||
/>
|
||||
|
||||
<!-- 提示绑定微信弹窗 -->
|
||||
<modal-popup
|
||||
v-model:show="payState.showBindWx"
|
||||
title="温馨提示"
|
||||
content="当前账号未微信授权,请前往个人设置授权"
|
||||
@confirm="router.navigateTo('/pages/user_set/user_set')"
|
||||
>
|
||||
</modal-popup>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive} from 'vue'
|
||||
import {onLoad} from '@dcloudio/uni-app'
|
||||
import {getToken} from '@/utils/auth'
|
||||
import {apiPayPayWay, apiPayPrepay} from "@/api/app";
|
||||
import {getClient} from "@/utils/client";
|
||||
import {useLockFn} from "@/hooks/useLockFn";
|
||||
import {pay, PayWayEnum} from "@/utils/pay/index";
|
||||
import {series} from "@/utils/util";
|
||||
import {useRoute, useRouter} from "uniapp-router-next";
|
||||
import {useUserStore} from "@/stores/user";
|
||||
import {PayStatusEnum} from "@/enums/appEnums";
|
||||
import PaymentCheck from "@/components/payment/check.vue";
|
||||
import PaymentAlipay from "@/components/payment/alipay.vue";
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const token = getToken()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const params = ref<any>({
|
||||
from: 'order',
|
||||
order_id: '',
|
||||
icon: '',
|
||||
order_amount: ''
|
||||
})
|
||||
const payData: any = ref({
|
||||
cancel_time: '',
|
||||
order_amount: ''
|
||||
})
|
||||
// 支付方式:1-微信支付;2-支付宝支付;3-余额支付;
|
||||
const payWay = ref()
|
||||
const timeStamp = ref<number | string | undefined>()
|
||||
|
||||
//支付宝支付链接
|
||||
const alipayLink = ref('')
|
||||
// 支付状态
|
||||
const payState = reactive({
|
||||
order_amount: '',
|
||||
orderId: '',
|
||||
from: 'order',
|
||||
showBindWx: false,
|
||||
showCheck: false,
|
||||
showAlipay: false,
|
||||
showPaySuccess: false
|
||||
})
|
||||
|
||||
// 选择支付方式
|
||||
const selectPayWay = (pay: number) => {
|
||||
// console.log('pay', pay)
|
||||
payWay.value = pay
|
||||
}
|
||||
|
||||
const initPlaceOrder = async () => {
|
||||
let payInfo = await apiPayPayWay({
|
||||
from: payState.from || 'order',
|
||||
order_id: payState.orderId,
|
||||
scene: getClient()
|
||||
})
|
||||
payData.value = payInfo.lists
|
||||
payState.order_amount = payInfo.order_amount;
|
||||
// timeStamp.value = (payData.value.cancel_time - Date.now() / 1000) * 1000
|
||||
const checkPay = payData.value.find((item: any) => item.is_default) || payData.value[0]
|
||||
payWay.value = checkPay?.pay_way
|
||||
}
|
||||
|
||||
const placeOrderAfter = (() => {
|
||||
const checkIsBindWx = async () => {
|
||||
if (
|
||||
userStore.userInfo.pay_auth == 0 &&
|
||||
payWay.value == PayWayEnum.WECHAT
|
||||
) {
|
||||
payState.showBindWx = true
|
||||
return Promise.reject('请先绑定微信后支付')
|
||||
}
|
||||
}
|
||||
|
||||
const alipayment = async () => {
|
||||
//支付宝支付 --- 逻辑
|
||||
if (payWay.value == PayWayEnum.ALIPAY) {
|
||||
// #ifdef MP-WEIXIN
|
||||
alipayLink.value = `${import.meta.env.VITE_APP_BASE_URL || ''}mobile/bundle/pages/toAlipay/toAlipay?order_id=${payState.orderId}&from=${payState.from}&pay_way=${payWay.value}&key=${token}`
|
||||
payState.showAlipay = true
|
||||
|
||||
return Promise.reject('微信不允许使用支付宝支付')
|
||||
// #endif
|
||||
|
||||
//#ifdef H5
|
||||
const ua = navigator.userAgent.toLowerCase()
|
||||
if (ua.match(/MicroMessenger/i)) {
|
||||
//微信浏览器环境
|
||||
router.reLaunch({
|
||||
path: '/bundle/pages/toAlipay/toAlipay',
|
||||
query: {
|
||||
order_id: payState.orderId,
|
||||
from: payState.from,
|
||||
pay_way: payWay.value,
|
||||
key: token
|
||||
}
|
||||
})
|
||||
return Promise.reject('微信不允许使用支付宝支付')
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
const payment = async () => {
|
||||
try {
|
||||
// uni.showLoading({
|
||||
// title: '正在支付中'
|
||||
// })
|
||||
const data = await apiPayPrepay({
|
||||
from: payState.from,
|
||||
pay_way: payWay.value,
|
||||
order_id: payState.orderId
|
||||
})
|
||||
|
||||
const res = await pay.payment(
|
||||
data.pay_way,
|
||||
data?.config || data?.payurl || data?.qrcode
|
||||
)
|
||||
console.log('res', res)
|
||||
// 支付结果
|
||||
handlePayResult(res)
|
||||
} catch (error) {
|
||||
return Promise.reject(error)
|
||||
} finally {
|
||||
// uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
return series(checkIsBindWx, alipayment, payment)
|
||||
})()
|
||||
|
||||
const {isLock, lockFn: handlePay} = useLockFn(async () => {
|
||||
try {
|
||||
// 支付处理
|
||||
await placeOrderAfter()
|
||||
} catch (err) {
|
||||
console.log('支付', err)
|
||||
}
|
||||
})
|
||||
|
||||
const handlePayResult = (status: PayStatusEnum) => {
|
||||
switch (status) {
|
||||
case PayStatusEnum.SUCCESS:
|
||||
router.redirectTo({
|
||||
path: '/bundle/pages/payment_result/payment_result',
|
||||
query: {
|
||||
order_id: payState.orderId,
|
||||
from: payState.from
|
||||
}
|
||||
})
|
||||
break
|
||||
case PayStatusEnum.FAIL:
|
||||
router.redirectTo({
|
||||
path: '/bundle/pages/payment_result/payment_result',
|
||||
query: {
|
||||
order_id: payState.orderId,
|
||||
from: payState.from
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async (option: any) => {
|
||||
if (option?.order_amount) {
|
||||
payState.order_amount = option?.order_amount
|
||||
payWay.value = 2
|
||||
}
|
||||
payState.orderId = option.order_id
|
||||
payState.from = option.from
|
||||
|
||||
console.log(option)
|
||||
await initPlaceOrder()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
margin: 20rpx 20rpx 0;
|
||||
height: 340rpx;
|
||||
|
||||
.order-amount {
|
||||
color: #ea6a57;
|
||||
font-size: 80rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&-time {
|
||||
border-radius: 200rpx;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
padding: 10rpx 48rpx;
|
||||
margin-top: 24rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 24rpx;
|
||||
|
||||
.payment-item {
|
||||
margin-top: 30rpx;
|
||||
padding: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #f6f7f8;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
// background-color: #ffffff;
|
||||
//box-shadow: 0 -4px 48px 0 #141a231f;
|
||||
}
|
||||
</style>
|
||||
154
uniapp/src/bundle/pages/payment_result/payment_result.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<page-status :status="status">
|
||||
<view class="payment-result flex flex-col min-h-0 h-full bg-white">
|
||||
<view
|
||||
class="flex flex-col justify-center"
|
||||
style="height: 40vh;"
|
||||
>
|
||||
<!-- Header -->
|
||||
<view class="text-center py-[60rpx]">
|
||||
<image
|
||||
style="width: 150rpx; height: 150rpx;"
|
||||
:src="resultStatus[payResult.pay_status].image"
|
||||
/>
|
||||
<view
|
||||
style="font-size: 40rpx;"
|
||||
class="mt-4 text-main font-medium"
|
||||
>
|
||||
{{ resultStatus[payResult.pay_status].text }}
|
||||
</view>
|
||||
<view class="mt-2 text-content text-xs">
|
||||
{{ resultStatus[payResult.pay_status].desc }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Footer -->
|
||||
<view
|
||||
class="mx-[30rpx] pt-[30rpx]"
|
||||
style="padding: 50rpx 40px;"
|
||||
>
|
||||
<template v-if="payForm.from !== PayFromType.USERRECHARGE">
|
||||
<view class="flex-1">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="router.switchTab('/pages/order/index')"
|
||||
>
|
||||
查看订单
|
||||
</u-button>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="flex-1">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="router.redirectTo('/bundle/pages/user_wallet/user_wallet')"
|
||||
>
|
||||
我的钱包
|
||||
</u-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="flex-1 mt-4">
|
||||
<u-button
|
||||
type="default"
|
||||
@click="router.reLaunch('/pages/index/index')"
|
||||
>
|
||||
返回首页
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Component -->
|
||||
<!-- <page-status ref="pageRef"></page-status>-->
|
||||
</view>
|
||||
</page-status>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onLoad} from '@dcloudio/uni-app'
|
||||
import {PageStatusEnum, PayFromType} from '@/enums/appEnums'
|
||||
import {reactive, shallowRef, unref, ref} from 'vue'
|
||||
import GoodsAbnormalImage from '@/static/images/empty/order.png'
|
||||
import IconWait from '@/static/images/icon/icon_wait.png'
|
||||
import IconSuccess from '@/static/images/icon/icon_pay_success.png'
|
||||
import {useUserStore} from "@/stores/user";
|
||||
import {useRoute, useRouter} from "uniapp-router-next";
|
||||
import {getPayResult} from "@/api/app";
|
||||
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const status = ref(PageStatusEnum.LOADING)
|
||||
const pageRef = shallowRef()
|
||||
const payForm = reactive({
|
||||
from: '',
|
||||
order_id: '',
|
||||
})
|
||||
const payResult = reactive<any>({
|
||||
pay_status: 1, // 0-待支付 1-已支付
|
||||
total_amount: 0,
|
||||
pay_time: '-',
|
||||
pay_way_desc: '-',
|
||||
sn: '-',
|
||||
})
|
||||
|
||||
const resultStatus = [
|
||||
{
|
||||
text: '等待支付',
|
||||
desc: '超时订单将自动取消',
|
||||
image: IconWait,
|
||||
},
|
||||
{
|
||||
text: '支付成功',
|
||||
desc: '您已完成支付',
|
||||
image: IconSuccess,
|
||||
},
|
||||
]
|
||||
|
||||
// 初始化订单详情
|
||||
const initOrderDetail = async (): Promise<void> => {
|
||||
try {
|
||||
const data = await getPayResult({
|
||||
order_id: payForm.order_id,
|
||||
from: payForm.from
|
||||
})
|
||||
|
||||
Reflect.ownKeys(payResult).map((item: any) => {
|
||||
payResult[item] = data[item]
|
||||
})
|
||||
status.value = PageStatusEnum.NORMAL
|
||||
} catch (error) {
|
||||
console.log('查询支付结果', error)
|
||||
status.value = PageStatusEnum.ERROR
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
try {
|
||||
|
||||
if (!route.query.order_id) {
|
||||
throw new Error('参数有误')
|
||||
}
|
||||
payForm['from'] = route.query['from'] as string
|
||||
payForm['order_id'] = route.query['order_id'] as string
|
||||
|
||||
await initOrderDetail()
|
||||
} catch (error) {
|
||||
unref(pageRef).show({
|
||||
text: error,
|
||||
src: GoodsAbnormalImage,
|
||||
})
|
||||
console.log('初始化支付页面', error)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
68
uniapp/src/bundle/pages/recharge_record/recharge_record.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<view class="user-recharge">
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<template v-for="(item, index) in dataList" :key="index">
|
||||
<view class="wrapper">
|
||||
<view class="left">
|
||||
<view class="title text-lg">余额充值</view>
|
||||
<view class="time">{{ item.create_time }}</view>
|
||||
</view>
|
||||
<view class="right"> +{{ item.order_amount }} </view>
|
||||
</view>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { apiRechargeLogLists } from '@/api/wallet'
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const { lists } = await apiRechargeLogLists({
|
||||
page_no,
|
||||
page_size
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-recharge {
|
||||
height: calc(100vh - env(safe-area-inset-bottom));
|
||||
padding-top: 15rpx;
|
||||
.wrapper {
|
||||
padding: 20rpx 21rpx;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
border-top: 1px solid rgba(234, 234, 234, 1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.left {
|
||||
.time {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
color: #ff2c3c;
|
||||
font-size: 38rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
uniapp/src/bundle/pages/search/component/suggest.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<view class="suggest">
|
||||
<!-- 历史搜索 -->
|
||||
<view class="history" v-if="his_search.length">
|
||||
<view class="flex justify-between pb-[6rpx] pt-[26rpx]">
|
||||
<view class="text-lg font-medium">历史搜索</view>
|
||||
<view class="text-xs text-muted" @click="() => emit('clear')">清空</view>
|
||||
</view>
|
||||
|
||||
<view class="w-full">
|
||||
<block v-for="(hisItem, index) in his_search" :key="index">
|
||||
<view class="keyword truncate" @click="handleHistoreSearch(hisItem)">{{
|
||||
hisItem
|
||||
}}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门搜索 -->
|
||||
<view class="hot" v-if="hot_search.status == 1 && searchData.length">
|
||||
<view
|
||||
class="flex items-center font-medium pb-[6rpx] text-lg"
|
||||
style="color: #DE3927;"
|
||||
>
|
||||
<image
|
||||
style="width: 44rpx; height: 44rpx"
|
||||
src="@/bundle/static/images/search_hot.png"
|
||||
>
|
||||
</image>
|
||||
<text class="ml-2">热门搜索</text>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-2 gap-y-4 w-full mt-2 p-[30rpx] bg-white rounded-lg">
|
||||
<block v-for="(hotItem, index) in searchData" :key="index">
|
||||
<view
|
||||
class="w-full truncate max-w-full"
|
||||
@click="handleHistoreSearch(hotItem.name)"
|
||||
>
|
||||
<text
|
||||
class="text-base font-bold"
|
||||
:style="{
|
||||
'color': index<=2 ? '#DE3927' : '#999999'
|
||||
}"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</text>
|
||||
<text class="ml-2">
|
||||
{{ hotItem.name }}
|
||||
</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'search', value: string): void
|
||||
(event: 'clear', value: void): void
|
||||
}>()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
hot_search?: {
|
||||
data: any[]
|
||||
status: number
|
||||
}
|
||||
his_search?: string[]
|
||||
}>(),
|
||||
{
|
||||
hot_search: () => ({
|
||||
data: [],
|
||||
status: 0
|
||||
}),
|
||||
his_search: () => []
|
||||
}
|
||||
)
|
||||
|
||||
const searchData = computed(() => {
|
||||
return props.hot_search.data.filter((item) => item.name)
|
||||
})
|
||||
|
||||
const handleHistoreSearch = (text: string) => {
|
||||
emit('search', text)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.suggest {
|
||||
height: 100%;
|
||||
margin: 0 30rpx;
|
||||
.keyword {
|
||||
display: inline-block;
|
||||
margin: 24rpx 16rpx 0 0;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 26rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.hot {
|
||||
margin: 30rpx 0;
|
||||
padding: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background: url("@/bundle/static/images/search_bg.png");
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
410
uniapp/src/bundle/pages/search/search.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="search flex flex-col min-h-0 h-full">
|
||||
<!-- #ifndef H5 -->
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
:border-bottom="false"
|
||||
title="搜索"
|
||||
title-color="#000"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, ${search.searching ? 1 : 0})` }"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 搜索框 --- 未搜索 -->
|
||||
<view
|
||||
class="p-[30rpx]"
|
||||
:style="{
|
||||
'background-color': !search.searching ? 'transparent' : '#ffffff'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="search-input flex"
|
||||
:style="{
|
||||
'background-color': !search.searching ? '#ffffff' : '#F6F7F8'
|
||||
}"
|
||||
>
|
||||
<view class="search-input-icon flex items-center flex-none">
|
||||
<u-icon name="search"></u-icon>
|
||||
</view>
|
||||
<view class="search-input-wrapper flex items-center flex-1">
|
||||
<input
|
||||
class="search-input-inner flex-1"
|
||||
placeholder="请输入关键词"
|
||||
v-model="keyword"
|
||||
@confirm="handleSearch"
|
||||
@change="handleSearch"
|
||||
:focus="true"
|
||||
/>
|
||||
<view
|
||||
v-if="keyword.length"
|
||||
class="flex-none px-3 text-muted"
|
||||
@click="clearSearch"
|
||||
>
|
||||
<u-icon name="close-circle" size="34"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="search-input-btn flex-none"
|
||||
@click="handleSearch"
|
||||
>
|
||||
搜索
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 搜索 -->
|
||||
<view class="search-content flex flex-col min-h-0 h-full">
|
||||
<!-- 未搜索 -->
|
||||
<suggest
|
||||
v-show="!search.searching"
|
||||
:hot_search="search.hot_search"
|
||||
:his_search="search.his_search"
|
||||
@clear="handleClear"
|
||||
@search="(val) => {
|
||||
keyword = val;
|
||||
handleSearch()
|
||||
}"
|
||||
></suggest>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view
|
||||
class="flex flex-col min-h-0 h-full"
|
||||
v-show="search.searching"
|
||||
>
|
||||
<!-- 检索下拉组件 列表 -->
|
||||
<view class="bg-white">
|
||||
<u-dropdown
|
||||
ref="uDropdownRef"
|
||||
menu-icon="arrow-down-fill"
|
||||
menu-icon-size="20"
|
||||
border-radius="18"
|
||||
duration="0"
|
||||
:active-color="$theme.primaryColor"
|
||||
>
|
||||
<u-dropdown-item :title="sortOptions[sort].label">
|
||||
<view class="bg-white rounded-b-xl pt-4">
|
||||
<view
|
||||
v-for="(item, index) in sortOptions"
|
||||
:key="index"
|
||||
class="pb-4 text-content"
|
||||
:class="{
|
||||
'text-primary': index === sort
|
||||
}"
|
||||
@click="handleFiltrate('sort', index)"
|
||||
>
|
||||
<text class="ml-4">
|
||||
{{ item.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-dropdown-item>
|
||||
<u-dropdown-item
|
||||
title="销量"
|
||||
>
|
||||
<view class="bg-white rounded-b-xl pt-4">
|
||||
<view
|
||||
v-for="(item, index) in salesOptions"
|
||||
:key="index"
|
||||
class="pb-4 text-content"
|
||||
:class="{
|
||||
'text-primary': item.value === order_sales
|
||||
}"
|
||||
@click="handleFiltrate('order_sales', item.value)"
|
||||
>
|
||||
<text class="ml-4">
|
||||
{{ item.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-dropdown-item>
|
||||
<u-dropdown-item
|
||||
title="价格"
|
||||
>
|
||||
<view class="bg-white rounded-b-xl pt-4">
|
||||
<view
|
||||
v-for="(item, index) in salesOptions"
|
||||
:key="index"
|
||||
class="pb-4 text-content"
|
||||
:class="{
|
||||
'text-primary': item.value === price
|
||||
}"
|
||||
@click="handleFiltrate('price', item.value)"
|
||||
>
|
||||
<text class="ml-4">
|
||||
{{ item.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-dropdown-item>
|
||||
<u-dropdown-item
|
||||
:title="listOptions[list].label"
|
||||
>
|
||||
<view class="bg-white rounded-b-xl pt-4">
|
||||
<view
|
||||
v-for="(item, index) in listOptions"
|
||||
:key="index"
|
||||
class="pb-4 pl-4 text-content"
|
||||
:class="{
|
||||
'text-primary': item.value === list
|
||||
}"
|
||||
@click="handleFiltrate('list', item.value)"
|
||||
>
|
||||
<u-icon
|
||||
:name="item.icon"
|
||||
size="28"
|
||||
></u-icon>
|
||||
<text class="ml-2">
|
||||
{{ item.label }}展示
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-dropdown-item>
|
||||
</u-dropdown>
|
||||
</view>
|
||||
|
||||
<!-- 显示页面 列表 -->
|
||||
<view class="h-full">
|
||||
<z-paging
|
||||
ref="paging"
|
||||
v-model="search.result.content.goods_list"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
:empty-view-img="EmptySearch"
|
||||
@query="queryList"
|
||||
>
|
||||
<area-goods
|
||||
:content="search.result.content"
|
||||
:styles="search.result.styles"
|
||||
/>
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, shallowRef} from 'vue'
|
||||
import Suggest from './component/suggest.vue'
|
||||
import {HISTORY} from '@/enums/constantEnums'
|
||||
import {getHotSearch, getServiceList} from '@/api/shop'
|
||||
import {locationState} from "@/hooks/useLocation";
|
||||
import cache from '@/utils/cache'
|
||||
import AreaGoods from "@/components/widgets/area-goods/area-goods.vue";
|
||||
import EmptySearch from "@/static/images/empty/search.png";
|
||||
|
||||
interface Search {
|
||||
hot_search: {
|
||||
data: any[]
|
||||
status: number
|
||||
}
|
||||
his_search: string[]
|
||||
result: any
|
||||
searching: boolean
|
||||
}
|
||||
|
||||
const search = reactive<Search>({
|
||||
hot_search: {
|
||||
data: [],
|
||||
status: 1
|
||||
},
|
||||
his_search: [],
|
||||
// 这里复用了首页装修的商品组组件数据。
|
||||
result: {
|
||||
content: {
|
||||
enabled: 1, // 是否启用
|
||||
type: 1, // 1-自动获取
|
||||
goods_type: 1, // 1-单列 2-双列 3-横向滑动 4-大图模式
|
||||
show_title: 1, // 商品标题
|
||||
show_server: 1, // 服务
|
||||
show_price: 1, // 商品价格
|
||||
show_scribing_price: 1, // 划线价格
|
||||
show_sales: 1, // 销量
|
||||
|
||||
btn_text: '立即下单', // 按钮文字
|
||||
show_num: 1, // 显示数量
|
||||
goods_list: [] // 商品数据
|
||||
},
|
||||
styles: {
|
||||
title_color: '#000000', // 标题颜色
|
||||
server_color: '#888888', // 服务颜色
|
||||
price_color: '#fc5447', // 价格颜色
|
||||
scribing_price_color: '#888888', // 划线价格颜色
|
||||
sales_color: '#888888', // 销量颜色
|
||||
btn_radius: 10, // 按钮圆角
|
||||
|
||||
root_bg_color: '', // 根背景颜色
|
||||
component_bg_color: '#ffffff', // 组件背景颜色
|
||||
goods_bg_color: '', // 商品背景颜色
|
||||
padding_top: 12, // 上内边距
|
||||
padding_horizontal: 12, // 左右内边距
|
||||
padding_bottom: 12, // 下内边距
|
||||
goods_horizontal: 12, // 商品左右内边距
|
||||
goods_vertical: 12, // 商品上下内边距
|
||||
|
||||
border_top_radius: 20,
|
||||
border_bottom_radius: 20,
|
||||
image_radius: 8
|
||||
}
|
||||
},
|
||||
searching: false
|
||||
})
|
||||
const keyword = ref<string>('')
|
||||
const order_sales = ref('') // 销量排序
|
||||
const price = ref('') // 价格排序
|
||||
const sort = ref('') // 综合排序
|
||||
const list = ref('card') // 列表展示
|
||||
const paging = shallowRef()
|
||||
const uDropdownRef = shallowRef()
|
||||
|
||||
const sortOptions = {
|
||||
'': {value: '', label: '综合排序'},
|
||||
// 'desc': { value: 'desc', label:'距离优先'},
|
||||
// 'asc': {value: 'asc', label: '销量优先'},
|
||||
'comment_sales': {value: 'desc', label: '好评优先'}
|
||||
}
|
||||
const salesOptions = [
|
||||
{value: '', label: '默认'},
|
||||
{value: 'desc', label: '从高到低'},
|
||||
{value: 'asc', label: '从低到高'}
|
||||
]
|
||||
const listOptions = {
|
||||
card: {value: 'card', icon: 'grid', label: '卡片'},
|
||||
list: {value: 'list', icon: 'list-dot', label: '列表'}
|
||||
}
|
||||
|
||||
const handleFiltrate = (type: string, value: string) => {
|
||||
switch (type) {
|
||||
case 'sort':
|
||||
sort.value = value
|
||||
paging.value.refresh()
|
||||
break
|
||||
case 'order_sales':
|
||||
order_sales.value = value
|
||||
paging.value.refresh()
|
||||
break
|
||||
case 'price':
|
||||
price.value = value
|
||||
paging.value.refresh()
|
||||
break
|
||||
case 'list':
|
||||
list.value = value
|
||||
value === 'card' ? (search.result.content.goods_type = 1) : (search.result.content.goods_type = 2)
|
||||
break
|
||||
}
|
||||
uDropdownRef.value?.close()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
if (keyword.value.trim() === '') {
|
||||
return
|
||||
}
|
||||
if (keyword.value) {
|
||||
if (!search.his_search.includes(keyword.value)) {
|
||||
search.his_search.unshift(keyword.value)
|
||||
cache.set(HISTORY, search.his_search)
|
||||
}
|
||||
}
|
||||
paging.value.reload()
|
||||
search.searching = true
|
||||
}
|
||||
|
||||
const clearSearch = () => {
|
||||
keyword.value = ''
|
||||
search.searching = false
|
||||
}
|
||||
|
||||
const getHotSearchFunc = async () => {
|
||||
try {
|
||||
search.hot_search = await getHotSearch()
|
||||
} catch (e) {
|
||||
//TODO handle the exception
|
||||
console.log('获取热门搜索失败=>', e)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClear = async (): Promise<void> => {
|
||||
const resModel: any = await uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: '是否清空历史记录?'
|
||||
})
|
||||
if (resModel.confirm) {
|
||||
cache.set(HISTORY, '')
|
||||
search.his_search = []
|
||||
}
|
||||
}
|
||||
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const {lists} = await getServiceList({
|
||||
keyword: keyword.value,
|
||||
city_id: locationState.city_id,
|
||||
order_sales: order_sales.value,
|
||||
price: price.value,
|
||||
comment_sales: sort.value,
|
||||
page_no,
|
||||
page_size
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
getHotSearchFunc()
|
||||
search.his_search = cache.get(HISTORY) || []
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
.search-input {
|
||||
background-color: #ffffff;
|
||||
border-radius: 50px;
|
||||
padding: 10rpx 20rpx;
|
||||
|
||||
.search-input-icon {
|
||||
color: #666666;
|
||||
padding: 0 14rpx 0 14rpx;
|
||||
}
|
||||
|
||||
.search-input-wrapper {
|
||||
|
||||
}
|
||||
|
||||
.search-input-inner {
|
||||
|
||||
}
|
||||
|
||||
.search-input-btn {
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
font-size: 28rpx;
|
||||
@apply bg-primary text-white rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
|
||||
&-s {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
125
uniapp/src/bundle/pages/select_coach/index.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<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-full pt-[120rpx]">
|
||||
<view class="top-search">
|
||||
<u-search
|
||||
v-model="keyword"
|
||||
placeholder="请输入关键词"
|
||||
:height="80"
|
||||
:animation="true"
|
||||
@search="search"
|
||||
@custom="search"
|
||||
@clear="queryList"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<view class="coachList">
|
||||
<z-paging
|
||||
:auto="true"
|
||||
auto-show-back-to-top
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
@query="queryList"
|
||||
>
|
||||
<view v-for="item in dataList" :key="item.id">
|
||||
<coach-card :data="item" type="1" @confirm="navigateToCoach"></coach-card>
|
||||
</view>
|
||||
|
||||
<!-- <template #empty>
|
||||
<view class="">
|
||||
暂无可服务技师
|
||||
</view>
|
||||
</template> -->
|
||||
</z-paging>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref, shallowRef } from 'vue'
|
||||
import { apiCollectLists } from '@/api/user'
|
||||
import { useRoute, useRouter } from 'uniapp-router-next'
|
||||
import { locationState } from '@/hooks/useLocation'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import CoachCard from '@/components/master-worker-card/index.vue'
|
||||
import ProjectCard from '@/components/project-card/index.vue'
|
||||
import shopLists from '@/components/shop-lists/index.vue'
|
||||
import { apiCoachLists } from '@/api/coach'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dataList = ref<any>([])
|
||||
const paging = shallowRef<any>(null)
|
||||
|
||||
// 搜索关键字
|
||||
const keyword = ref<string | number>('')
|
||||
|
||||
// 处理搜索
|
||||
const search = () => {
|
||||
paging.value?.reload()
|
||||
}
|
||||
|
||||
const shop_id = ref('')
|
||||
const goods_id = ref('')
|
||||
|
||||
// 初始化收藏
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const { lists } = await apiCoachLists({
|
||||
shop_id: shop_id.value,
|
||||
goods_id: goods_id.value,
|
||||
keyword: keyword.value,
|
||||
page_no: page_no || 1,
|
||||
page_size: page_size || 10,
|
||||
longitude: locationState.longitude,
|
||||
latitude: locationState.latitude
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToCoach = (id: number) => {
|
||||
router.navigateTo({
|
||||
path: '/pages/order_buy/index',
|
||||
query: {
|
||||
goods_id: goods_id.value,
|
||||
coach_id: id,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
shop_id.value = route.query.shop_id
|
||||
goods_id.value = route.query.goods_id
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty {
|
||||
padding-top: 300rpx;
|
||||
}
|
||||
|
||||
.top-search {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
padding: 20rpx 30rpx;
|
||||
height: 120rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.coachList {
|
||||
height: calc(100vh - 120rpx - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
114
uniapp/src/bundle/pages/service_order/components/order-card.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<view class="card" @click.stop="goPage">
|
||||
<view class="card--header flex justify-between">
|
||||
<view class="order-sn">订单编号:{{ orderInfo.sn }}</view>
|
||||
<view class="status">{{ orderInfo.order_status_desc }}</view>
|
||||
</view>
|
||||
|
||||
<block v-for="item3 in orderInfo.order_goods" :key="item3.id">
|
||||
<view class="card--main flex">
|
||||
<u-image :src="item3.goods_image" width="160" height="160"></u-image>
|
||||
<view class="ml-[20rpx] service-text">
|
||||
<view class="service-text--name truncate">{{ item3.goods_name }}</view>
|
||||
<view class="mt-[16rpx]"
|
||||
>预约时间: {{ orderInfo.appoint_time }} {{ orderInfo.appoint_week }}</view
|
||||
>
|
||||
<view class="mt-[16rpx]">实付金额: ¥{{ orderInfo.order_amount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="card--footer flex justify-between">
|
||||
<view class="text-primary">
|
||||
<template v-if="orderInfo.order_cancel_time">
|
||||
<view class="flex" v-if="timeStamp >= 0">
|
||||
<u-count-down
|
||||
:timestamp="timeStamp"
|
||||
format="mm:ss"
|
||||
:font-size="26"
|
||||
:separator-size="26"
|
||||
@end="timeStamp = 0"
|
||||
/>
|
||||
<text class="ml-[10rpx]">后自动取消</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watchEffect } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
orderInfo?: any // 底部
|
||||
}>(),
|
||||
{
|
||||
orderInfo: {}
|
||||
}
|
||||
)
|
||||
|
||||
const timeStamp = ref<number | null>(0)
|
||||
watchEffect(() => {
|
||||
// 获取倒计时段
|
||||
const endTimestamp = props.orderInfo.order_cancel_time
|
||||
const startTimestamp = new Date().getTime() / 1000
|
||||
timeStamp.value = (endTimestamp - startTimestamp) * 1000
|
||||
})
|
||||
|
||||
const goPage = () => {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/service_order_detail/index?id=${props.orderInfo.id}`
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
border-radius: 14rpx;
|
||||
background-color: #ffffff;
|
||||
margin: 20rpx 20rpx 0 20rpx;
|
||||
|
||||
&--header {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
.order-sn {
|
||||
color: #222222;
|
||||
}
|
||||
.status {
|
||||
color: #f36161;
|
||||
}
|
||||
}
|
||||
|
||||
&--main {
|
||||
padding: 30rpx;
|
||||
color: #555555;
|
||||
font-size: 26rpx;
|
||||
|
||||
.service-text {
|
||||
&--name {
|
||||
width: 460rpx;
|
||||
font-weight: 500;
|
||||
color: #222222;
|
||||
font-size: 32rpx;
|
||||
// line-1
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--footer {
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
:auto="i == index"
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
:data-key="i"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<block v-for="(item, index) in dataList" :key="index">
|
||||
<order-card :orderInfo="item">
|
||||
<order-footer
|
||||
:orderId="item?.id"
|
||||
:cancel="item.cancel_btn"
|
||||
:evaluate="item.comment_btn"
|
||||
:contact="item.contact_btn"
|
||||
:pay="item.pay_btn"
|
||||
:mobile="item?.staff?.mobile"
|
||||
:confirmService="item.confirm_service_btn"
|
||||
:verification="item.verification_btn"
|
||||
:orderStatus="item.order_status"
|
||||
:goods_image="item.order_goods[0].goods_image"
|
||||
:goods_name="item.order_goods[0].goods_name"
|
||||
:type="2"
|
||||
@refresh="queryList"
|
||||
/>
|
||||
</order-card>
|
||||
</block>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, shallowRef, unref } from 'vue'
|
||||
import orderCard from './order-card.vue'
|
||||
import orderFooter from '@/components/order-footer/index.vue'
|
||||
import { apiStaffOrderLists } from '@/api/order'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
order_status: number
|
||||
i: number
|
||||
index: number
|
||||
}>(),
|
||||
{
|
||||
order_status: 0
|
||||
}
|
||||
)
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
const isFirst = ref<boolean>(true)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
async () => {
|
||||
await nextTick()
|
||||
if (props.i == props.index && unref(isFirst)) {
|
||||
isFirst.value = false
|
||||
paging.value?.reload()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const queryList = async (pageNo: number, pageSize: number) => {
|
||||
try {
|
||||
const { lists } = await apiStaffOrderLists({
|
||||
order_status: props.order_status,
|
||||
pageNo,
|
||||
pageSize
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
91
uniapp/src/bundle/pages/service_order/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view class="order-list">
|
||||
<!-- :auth="true" 是表示需要权限登录的 -->
|
||||
<tabs
|
||||
:current="current"
|
||||
@change="handleChange"
|
||||
height="80"
|
||||
bar-width="60"
|
||||
:barStyle="{ bottom: '0' }"
|
||||
:auth="true"
|
||||
activeColor="#F36161"
|
||||
>
|
||||
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
|
||||
<view class="orderList pt-[20rpx]" v-if="isLogin">
|
||||
<orderList
|
||||
:order_status="item.order_status"
|
||||
:i="i"
|
||||
:index="current"
|
||||
></orderList>
|
||||
</view>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import orderList from './components/order-list.vue'
|
||||
import tab from '@/components/tab/tab.vue'
|
||||
import tabs from '@/components/tabs/tabs.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 是否登录
|
||||
const isLogin = computed(() => userStore.token)
|
||||
|
||||
const tabList = ref<any>([
|
||||
{
|
||||
name: '全部',
|
||||
order_status: 0
|
||||
},
|
||||
{
|
||||
name: '预约中',
|
||||
order_status: 1
|
||||
},
|
||||
{
|
||||
name: '服务中',
|
||||
order_status: 2
|
||||
},
|
||||
{
|
||||
name: '已完成',
|
||||
order_status: 3
|
||||
},
|
||||
{
|
||||
name: '已关闭',
|
||||
order_status: 4
|
||||
}
|
||||
])
|
||||
const current = ref<number>(0)
|
||||
|
||||
const handleChange = (index: number) => {
|
||||
current.value = Number(index)
|
||||
}
|
||||
|
||||
onLoad(async (options: { type?: any }) => {
|
||||
current.value = options?.type * 1 || 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: scroll;
|
||||
swiper {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.orderList {
|
||||
height: calc(100vh - 86px - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
216
uniapp/src/bundle/pages/service_order_detail/index.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<uni-transition
|
||||
mode-class="zoom-in"
|
||||
needLayout="true"
|
||||
:show="orderData.order_goods.length"
|
||||
:duration="500"
|
||||
>
|
||||
<view class="order_detail">
|
||||
<view class="flex pb-[30rpx]">
|
||||
<image
|
||||
v-if="orderData.order_status === 0"
|
||||
class="header-image"
|
||||
src="/static/images/icon_pay.png"
|
||||
/>
|
||||
<image
|
||||
v-if="orderData.order_status === 1 || orderData.order_status === 2"
|
||||
class="header-image"
|
||||
src="/static/images/icon_wait.png"
|
||||
/>
|
||||
<image
|
||||
v-if="orderData.order_status === 3"
|
||||
class="header-image"
|
||||
src="/static/images/icon_success.png"
|
||||
/>
|
||||
<image
|
||||
v-if="orderData.order_status === 4"
|
||||
class="header-image"
|
||||
src="@/static/images/icon_close.png"
|
||||
/>
|
||||
<text class="statusDec ml-[15rpx]">{{ orderData.order_status_desc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 地址卡片 -->
|
||||
<view class="card">
|
||||
<view class="card--header">
|
||||
<view class="title">{{ orderData.contact }} {{ orderData.mobile }}</view>
|
||||
</view>
|
||||
<view class="text-sm text-muted">
|
||||
{{ orderData.province }}
|
||||
{{ orderData.city }}
|
||||
{{ orderData.district }}
|
||||
{{ orderData.address }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品卡片 -->
|
||||
<view class="card">
|
||||
<view class="goods-item">
|
||||
<u-image
|
||||
:src="orderData.order_goods[0]?.goods_image"
|
||||
width="160"
|
||||
height="160"
|
||||
border-radius="4"
|
||||
/>
|
||||
<view class="ml-[20rpx] mt-[4rpx]">
|
||||
<view class="flex justify-between title">
|
||||
<view class="goods-item--name truncate">{{
|
||||
orderData.order_goods[0]?.goods_name
|
||||
}}</view>
|
||||
<text>x{{ orderData.order_goods[0]?.goods_num }}</text>
|
||||
</view>
|
||||
<view class="mt-[24rpx]">
|
||||
<price
|
||||
:price="orderData.order_goods[0]?.goods_price"
|
||||
:desc="orderData.order_goods[0]?.unit_name"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上门时间 -->
|
||||
<view class="card normal text-base flex justify-between">
|
||||
<view>上门时间</view>
|
||||
<view>
|
||||
{{ orderData.appoint_time }} {{ orderData.appoint_week }}
|
||||
{{ orderData.door_time }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务金额 -->
|
||||
<view class="card normal text-base">
|
||||
<view class="flex justify-between">
|
||||
<view>服务金额</view>
|
||||
<view>¥{{ orderData.order_amount }}</view>
|
||||
</view>
|
||||
<view class="mt-[30rpx] flex justify-between">
|
||||
<view>实付金额</view>
|
||||
<view class="text-primary">¥{{ orderData.total_amount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 备注 -->
|
||||
<view class="card flex justify-between flex-wrap normal text-base">
|
||||
<view>备注</view>
|
||||
<view style="width: 100%; word-wrap: break-word">{{ orderData.user_remark }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单信息 -->
|
||||
<view class="card normal text-base">
|
||||
<view class="flex justify-between">
|
||||
<view>订单编号</view>
|
||||
<view>{{ orderData.sn }}</view>
|
||||
</view>
|
||||
<view class="mt-[30rpx] flex justify-between">
|
||||
<view>支付方式</view>
|
||||
<view>{{ orderData.pay_way_desc }}</view>
|
||||
</view>
|
||||
<view class="mt-[30rpx] flex justify-between">
|
||||
<view>下单时间</view>
|
||||
<view>{{ orderData.create_time }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-transition>
|
||||
|
||||
<view
|
||||
class="footer flex justify-end"
|
||||
v-if="orderData.confirm_service_btn || orderData.verification_btn"
|
||||
>
|
||||
<order-footer
|
||||
:orderId="orderData.id"
|
||||
:confirmService="orderData.confirm_service_btn"
|
||||
:verification="orderData.verification_btn"
|
||||
:goods_image="orderData.order_goods[0].goods_image"
|
||||
:goods_name="orderData.order_goods[0].goods_name"
|
||||
@refresh="initOrderDetail"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiStaffOrderDetail } from '@/api/order'
|
||||
import OrderFooter from '@/components/order-footer/index.vue'
|
||||
|
||||
const orderData = ref<any>({
|
||||
order_goods: [],
|
||||
order_amount: '',
|
||||
total_amount: '',
|
||||
total_goods_price: ''
|
||||
})
|
||||
const orderId = ref<number | string>('')
|
||||
|
||||
const initOrderDetail = async (): Promise<void> => {
|
||||
orderData.value = await apiStaffOrderDetail({ id: orderId.value })
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
orderId.value = parseInt(options?.id)
|
||||
initOrderDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.order_detail {
|
||||
height: 100%;
|
||||
padding: 30rpx 24rpx;
|
||||
padding-bottom: 140rpx;
|
||||
background: linear-gradient(to bottom, #f36161 200rpx, transparent 0);
|
||||
|
||||
.statusDec {
|
||||
color: rgba(255, 255, 255, 1) !important;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 10rpx;
|
||||
|
||||
&--header {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
.title {
|
||||
color: #000;
|
||||
font-size: 28rpx;
|
||||
.num {
|
||||
color: #666;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
&--name {
|
||||
width: 430rpx;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 108rpx;
|
||||
position: fixed;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 2rpx 2rpx 22rpx rgba($color: #000, $alpha: 0.2);
|
||||
|
||||
.btn {
|
||||
width: 320rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
uniapp/src/bundle/pages/shop_credential/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<view class="main">
|
||||
<view class="credential mb-[30rpx]">
|
||||
<view class="flex items-center pt-[10rpx] pb-[30rpx]">
|
||||
<view class="w-[6rpx] h-[32rpx]" :style="{'background-color':$theme.primaryColor}"></view>
|
||||
<text class="ml-[10rpx] text-2xl">营业执照</text>
|
||||
</view>
|
||||
<u-image
|
||||
:src="business_license"
|
||||
height="260rpx"
|
||||
mode="heightFix"
|
||||
border-radius="16rpx"
|
||||
class="credential-img"
|
||||
@click="previewImage(business_license)"
|
||||
></u-image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import {onLoad} from '@dcloudio/uni-app'
|
||||
|
||||
const business_license = ref()
|
||||
|
||||
//预览图片
|
||||
const previewImage = (url:string) => {
|
||||
uni.previewImage({
|
||||
urls: [url]
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
business_license.value = options?.business_license || ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
padding: 20rpx 20rpx 40rpx;
|
||||
.info {
|
||||
padding: 30rpx 24rpx 40rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.credential {
|
||||
padding: 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
uniapp/src/bundle/pages/shop_detail/component/introduce.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="showPopup"
|
||||
mode="bottom"
|
||||
border-radius="12"
|
||||
:closeable="true"
|
||||
:mask-close-able="true"
|
||||
>
|
||||
<view style="padding: 30rpx; min-height: 100rpx">
|
||||
<view class="font-medium text-lg pb-4 text-center">
|
||||
{{ data.name }}
|
||||
</view>
|
||||
<view>
|
||||
<view class="flex items-center">
|
||||
<view class="w-[6rpx] h-[28rpx]" :style="{'background-color':$theme.primaryColor}"></view>
|
||||
<text class="ml-[10rpx]">商家介绍</text>
|
||||
</view>
|
||||
<view class="mt-[20rpx] text-muted text-sm">{{ data.synopsis }}</view>
|
||||
</view>
|
||||
<view class="mt-[30rpx] mb-[50rpx]">
|
||||
<view class="flex items-center">
|
||||
<view class="w-[6rpx] h-[28rpx]" :style="{'background-color':$theme.primaryColor}"></view>
|
||||
<text class="ml-[10rpx]">商家信息</text>
|
||||
</view>
|
||||
<view class="mt-[20rpx] text-muted text-sm">
|
||||
<view class="u-flex justify-between w-full mb-[24rpx]">
|
||||
<view class="u-flex flex-1 w-full truncate">
|
||||
<view><u-icon name="map" color="#909399"></u-icon></view>
|
||||
<view class="text-sm text-muted ml-[6rpx]">{{ data.shop_address_detail }}</view>
|
||||
</view>
|
||||
<!-- <view @click="openLocation()"><u-icon name="phone-fill" :color="$theme.primaryColor"></u-icon></view> -->
|
||||
</view>
|
||||
<view class="u-flex justify-between w-full mb-[24rpx]">
|
||||
<view class="u-flex flex-1 w-full truncate">
|
||||
<view><u-icon name="clock" color="#909399"></u-icon></view>
|
||||
<view class="text-sm text-muted ml-[6rpx]">{{ '营业时间:'+data.business_time_desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex justify-between w-full mb-[24rpx]">
|
||||
<view class="u-flex flex-1 w-full truncate" @click="router.navigateTo({path: '/bundle/pages/shop_credential/index?business_license='+data.business_license})">
|
||||
<view><u-icon name="order" color="#909399"></u-icon></view>
|
||||
<view class="text-sm text-muted mx-[6rpx]">查看营业资质</view>
|
||||
<view><u-icon name="arrow-right" color="#909399" size="20rpx"></u-icon></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import { useRoute, useRouter } from 'uniapp-router-next'
|
||||
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:show', show: boolean): void
|
||||
(event: 'update:data', value: any): void
|
||||
}>()
|
||||
|
||||
const showPopup = computed({
|
||||
get() {
|
||||
return props.show
|
||||
},
|
||||
set(val) {
|
||||
emit('update:show', val)
|
||||
}
|
||||
})
|
||||
|
||||
//查看位置
|
||||
const openLocation = () => {
|
||||
uni.openLocation({
|
||||
latitude: parseFloat(props.data.latitude as string),
|
||||
longitude: parseFloat(props.data.longitude as string),
|
||||
name: props.data.shop_address_detail,
|
||||
address: props.data.region_desc,
|
||||
success: function () {
|
||||
showPopup.value = false
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
392
uniapp/src/bundle/pages/shop_detail/index.vue
Normal file
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="coach-detail">
|
||||
<!-- #ifndef H5 -->
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="商家详情"
|
||||
:immersive="true"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, ${percent})` }"
|
||||
:title-color="percent > 0.5 ? '#000' : !isEmpty ? '#000' : '#fff'"
|
||||
:back-icon-color="percent > 0.5 ? '#000' : !isEmpty ? '#000' : '#fff'"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
<!-- #endif -->
|
||||
<view v-if="isEmpty">
|
||||
<!-- 轮播图 -->
|
||||
<l-swiper
|
||||
:content="{
|
||||
data: shopData.shop_image
|
||||
}"
|
||||
name="url"
|
||||
height="750"
|
||||
indicatorPos="bottomRight"
|
||||
mode="number"
|
||||
borderRadius="0"
|
||||
/>
|
||||
<view v-if="!shopData.shop_image?.length" class="h-[100rpx]"></view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="coach-detail-info">
|
||||
<view class="p-[24rpx] rounded-b-lg bg-white roun rounded-t-lg">
|
||||
<view
|
||||
class="flex items-center justify-between"
|
||||
@click="showIntroducePopup = true"
|
||||
>
|
||||
<view class="text-xl font-bold truncate">{{ shopData.name }}</view>
|
||||
<view><u-icon name="arrow-right"></u-icon></view>
|
||||
</view>
|
||||
<view class="py-[10rpx]">
|
||||
<view>
|
||||
<u-rate
|
||||
:count="5"
|
||||
v-model="shopData.good_comment"
|
||||
size="24"
|
||||
disabled
|
||||
active-color="#e86016"
|
||||
></u-rate>
|
||||
<text class="start-text ml-1 text-xs" v-if="shopData.good_comment">
|
||||
{{ shopData.good_comment.toFixed(1) }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="text-info mt-1"
|
||||
style="font-size: 24rpx"
|
||||
v-if="shopData.consumption"
|
||||
>{{ '¥' + shopData.consumption + '/人' }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="py-[10rpx]">
|
||||
<view class="u-flex">
|
||||
<view
|
||||
:style="{
|
||||
color: shopData.work_status ? $theme.primaryColor : '#dd6161'
|
||||
}"
|
||||
>{{ shopData.work_status_desc }}</view
|
||||
>
|
||||
<view class="text-muted ml-[20rpx]">{{
|
||||
shopData.business_time_desc
|
||||
}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-flex flex-wrap text-sm text-muted">
|
||||
<view
|
||||
class="service-desc u-flex"
|
||||
v-for="(item, index) in shopData.category_name"
|
||||
:key="index"
|
||||
>
|
||||
<text>{{ item }}</text
|
||||
><text class="line" v-if="item">|</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-xs mt-4 u-flex justify-between">
|
||||
<view class="flex-1 mr-[10rpx]">
|
||||
<view class="text-[26rpx] mb-[6rpx]"
|
||||
>{{ shopData.shop_address_detail
|
||||
}}{{ shopData.shop_address_detail }}</view
|
||||
>
|
||||
<view class="text-muted text-sm u-flex">
|
||||
<u-icon name="map" color="#909399"></u-icon>
|
||||
<view>{{ shopData.region_desc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-[22rpx] u-flex text-center">
|
||||
<view class="mr-[40rpx]" @click="call(shopData.mobile)">
|
||||
<view>
|
||||
<u-image
|
||||
width="40rpx"
|
||||
height="40rpx"
|
||||
src="@/bundle/static/images/icon_phone.png"
|
||||
></u-image>
|
||||
</view>
|
||||
<view>电话</view>
|
||||
</view>
|
||||
<view @click="openLocation()">
|
||||
<view>
|
||||
<u-image
|
||||
width="40rpx"
|
||||
height="40rpx"
|
||||
src="@/bundle/static/images/icon_address.png"
|
||||
></u-image>
|
||||
</view>
|
||||
<view>导航</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="rounded-[20rpx] bg-white p-[24rpx] m-[30rpx]">
|
||||
<tabs
|
||||
show-bar
|
||||
:isScroll="false"
|
||||
height="70"
|
||||
bar-width="60"
|
||||
font-size="32rpx"
|
||||
:bold="true"
|
||||
inactiveColor="#333"
|
||||
:activeColor="$theme.primaryColor"
|
||||
>
|
||||
<tab name="服务项目">
|
||||
<view class="mt-4" v-for="item in shopData.goods_lists" :key="item.id">
|
||||
<project :data="item" :shop_id="shopId" :is_shop="1"/>
|
||||
</view>
|
||||
</tab>
|
||||
<tab name="用户评价">
|
||||
<evaluate
|
||||
v-for="item in shopData.comment_lists"
|
||||
:index="item.id"
|
||||
:data="item"
|
||||
/>
|
||||
</tab>
|
||||
</tabs>
|
||||
</view>
|
||||
|
||||
<view class="footer u-flex fixed">
|
||||
<view class="flex-1">
|
||||
<u-button type="primary" @click="handleCollection(shopData.is_collect)">
|
||||
<u-icon
|
||||
:name="shopData.is_collect ? 'star-fill' : 'star'"
|
||||
color="#ffffff"
|
||||
size="30"
|
||||
class="mb-[1rpx]"
|
||||
/>
|
||||
<text class="text-base font-medium ml-1">加收藏</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<u-empty
|
||||
text="抱歉,该商家不存在~"
|
||||
:src="'/static/images/empty/collection.png'"
|
||||
:icon-size="300"
|
||||
color="#888888"
|
||||
>
|
||||
<template #bottom>
|
||||
<view class="empty-bottom">
|
||||
<button
|
||||
class="bg-primary text-lg text-white leading-[80rpx] h-[80rpx]"
|
||||
@click="router.reLaunch('/pages/shop/index')"
|
||||
>
|
||||
去看看其它
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</u-empty>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 返回顶部按钮 -->
|
||||
<u-back-top
|
||||
:scroll-top="scrollTop"
|
||||
:top="100"
|
||||
:customStyle="{
|
||||
backgroundColor: '#FFF',
|
||||
color: '#000',
|
||||
boxShadow: '0px 3px 6px rgba(0, 0, 0, 0.1)'
|
||||
}"
|
||||
>
|
||||
</u-back-top>
|
||||
|
||||
<!-- 简介 -->
|
||||
<introduce-popup v-model:show="showIntroducePopup" v-model:data="shopData" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
options: {
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, shallowRef, unref } from 'vue'
|
||||
import { onLoad, onPageScroll, onShow } from '@dcloudio/uni-app'
|
||||
import { apishopDetail } from '@/api/shop'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useRouter } from 'uniapp-router-next'
|
||||
import { locationState } from '@/hooks/useLocation'
|
||||
|
||||
import project from '@/components/project-card/index.vue'
|
||||
import evaluate from '@/components/evaluate-card/index.vue'
|
||||
import { putCollect } from '@/api/store'
|
||||
import IntroducePopup from './component/introduce.vue'
|
||||
import { getClient } from '@/utils/client'
|
||||
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const shopData = ref<any>({})
|
||||
|
||||
const isLogin = computed(() => userStore.token)
|
||||
const shopId = ref<number | string>('')
|
||||
const scrollTop = ref<number>(0)
|
||||
const percent = ref<number>(0)
|
||||
const isEmpty = ref<boolean>(true)
|
||||
const showIntroducePopup = ref(false)
|
||||
|
||||
/**
|
||||
* @description 获取商品详情
|
||||
*/
|
||||
const initializeCoachDetails = async (): Promise<void> => {
|
||||
try {
|
||||
shopData.value = await apishopDetail({
|
||||
id: shopId.value,
|
||||
longitude: locationState.longitude,
|
||||
latitude: locationState.latitude,
|
||||
terminal: getClient()
|
||||
})
|
||||
shopData.value.shop_image = shopData.value.shop_image.map((item) => {
|
||||
return item.uri
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
isEmpty.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 收藏
|
||||
const handleCollection = async (is_collect: number): Promise<void> => {
|
||||
if (!isLogin.value) {
|
||||
router.navigateTo('/pages/login/login')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await putCollect({
|
||||
id: unref(shopId) as number,
|
||||
type: 3
|
||||
})
|
||||
if (is_collect) {
|
||||
uni.$u.toast('取消收藏成功')
|
||||
} else {
|
||||
uni.$u.toast('收藏成功')
|
||||
}
|
||||
await initializeCoachDetails()
|
||||
} catch (error) {
|
||||
//TODO handle the exception
|
||||
console.log('收藏请求错误', error)
|
||||
}
|
||||
}
|
||||
//拨打电话
|
||||
const call = (phoneNumber: string) => {
|
||||
console.log('phoneNumber', phoneNumber)
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phoneNumber
|
||||
})
|
||||
}
|
||||
//查看位置
|
||||
const openLocation = () => {
|
||||
uni.openLocation({
|
||||
latitude: parseFloat(shopData.value.latitude as string),
|
||||
longitude: parseFloat(shopData.value.longitude as string),
|
||||
name: shopData.value.shop_address_detail,
|
||||
address: shopData.value.region_desc,
|
||||
success: function () {}
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
shopId.value = options?.id || 0
|
||||
initializeCoachDetails()
|
||||
})
|
||||
|
||||
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>
|
||||
.coach-detail {
|
||||
padding-bottom: 200rpx;
|
||||
|
||||
:deep(.u-swiper-indicator) {
|
||||
bottom: 100rpx !important;
|
||||
}
|
||||
|
||||
.coach-detail-info {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
margin: -70rpx 30rpx 0 30rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
.introduct {
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 14rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background-color: #f6f7f8;
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
text {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
&:not(:first-child) {
|
||||
::before {
|
||||
content: '·';
|
||||
position: absolute;
|
||||
left: -15rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-desc .line {
|
||||
margin: 0 10rpx;
|
||||
}
|
||||
|
||||
.service-desc:last-child .line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 收藏图标
|
||||
.collection {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
.footer {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 -4px 48px 0 #141a231f;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
|
||||
}
|
||||
|
||||
// 服务下架或不存在时
|
||||
.empty {
|
||||
padding-top: 200rpx;
|
||||
|
||||
.empty-bottom {
|
||||
width: 90vw;
|
||||
margin-top: 130rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
uniapp/src/bundle/pages/toAlipay/toAlipay.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<image v-if="imageShow" src="@/bundle/static/images/Alipay.png" class="img"></image>
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad,onShow,onUnload } from '@dcloudio/uni-app'
|
||||
import { apiJumpPayPrepay,getPayResult } from '@/api/app'
|
||||
|
||||
const order_id = ref()
|
||||
const from = ref()
|
||||
const pay_way = ref()
|
||||
const token = ref()
|
||||
let imageShow = ref(false)
|
||||
const chatInterval = ref()
|
||||
onLoad((param) => {
|
||||
order_id.value = param.order_id
|
||||
from.value = param.from
|
||||
pay_way.value = param.pay_way
|
||||
token.value = param.key
|
||||
})
|
||||
onShow(async () => {
|
||||
//#ifdef H5
|
||||
let ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.match(/MicroMessenger/i)) {
|
||||
imageShow.value = true
|
||||
chatInterval.value = setInterval(() => {
|
||||
const payResult = getPayResult({from:from.value,order_id:order_id.value},token.value)
|
||||
.then(function (response) {
|
||||
console.log(response)
|
||||
if(response.pay_status == 1) {
|
||||
clearInterval(chatInterval.value)
|
||||
uni.showModal({
|
||||
showCancel: false,
|
||||
content: '支付成功',
|
||||
success: () => {
|
||||
if(from.value == 'order') {
|
||||
const param = JSON.stringify({
|
||||
order_id: order_id.value,
|
||||
from: from.value
|
||||
})
|
||||
uni.reLaunch({
|
||||
url: `/bundle/pages/payment_result/payment_result?param=${param}`
|
||||
})
|
||||
}
|
||||
if(from.value == 'recharge') {
|
||||
uni.reLaunch({
|
||||
url: '/bundle/pages/user_wallet/user_wallet?isPay=true'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
return
|
||||
}
|
||||
//不是微信游览器
|
||||
const res = await apiJumpPayPrepay({
|
||||
from: from.value,
|
||||
pay_way: pay_way.value,
|
||||
order_id: order_id.value
|
||||
},token.value)
|
||||
console.log(res)
|
||||
const alipayPage = document.createElement('div')
|
||||
alipayPage.innerHTML = res.config
|
||||
document.body.appendChild(alipayPage)
|
||||
document.forms[0].submit()
|
||||
//#endif
|
||||
})
|
||||
onUnload((param) => {
|
||||
clearInterval(chatInterval.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
.container {
|
||||
padding: 0 40rpx;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
.img {
|
||||
height: 750rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
292
uniapp/src/bundle/pages/user_address/index.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="user-address flex flex-col min-h-0 h-full">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="地址管理"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: '#f6f7f8' }"
|
||||
title-color="#000000"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<view class="flex-1">
|
||||
<z-paging
|
||||
v-model="addressList"
|
||||
ref="paging"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
auto-show-back-to-top
|
||||
:empty-view-img="EmptyAddress"
|
||||
:hide-no-more-inside="true"
|
||||
hide-empty-view
|
||||
@query="queryList"
|
||||
>
|
||||
<view
|
||||
class="address-item u-flex justify-between mt-[20rpx]"
|
||||
v-for="(item, index) in addressList"
|
||||
:key="item.id"
|
||||
:data-id="item.id"
|
||||
@click.stop="onSelect"
|
||||
>
|
||||
<view class="flex-1" >
|
||||
<view class="sm text-muted text-base mb-[20rpx]">
|
||||
{{ item.province }}
|
||||
{{ item.city }}
|
||||
{{ item.district }}
|
||||
<text class="default" v-if="item.is_default">默认地址</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="text-base font-bold">{{ item.address }} {{ item?.house_number }}</text>
|
||||
</view>
|
||||
<view class="mt-[10rpx] sm text-muted text-[24rpx]">
|
||||
{{ item.contact }} {{ item.gender_desc }} {{ item.mobile }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex">
|
||||
<view class="mr-[40rpx]" @click.stop="goEditAddress(item.id)">
|
||||
<u-icon name="edit-pen" color="#909399" :size="40"></u-icon>
|
||||
</view>
|
||||
<view class="" @click.stop="openDelAddress(item.id)">
|
||||
<u-icon name="trash" color="#909399" :size="40"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 不可用-不在开通范围-->
|
||||
<template v-if="disableServerList.length">
|
||||
<view class="mx-[30rpx] mt-[30rpx]" >
|
||||
以下地址暂不在开通范围
|
||||
</view>
|
||||
<view class="address-item u-flex justify-between mt-[20rpx]" v-for="item in disableServerList" :key="item.id">
|
||||
<view class="flex-1 text-[#CCCCCC]">
|
||||
<view class="sm text-muted text-base mb-[20rpx]">
|
||||
{{ item.province }}
|
||||
{{ item.city }}
|
||||
{{ item.district }}
|
||||
</view>
|
||||
<view>
|
||||
<text class="text-base font-bold">{{ item.address }} {{ item?.house_number }}</text>
|
||||
</view>
|
||||
<view class="mt-[10rpx] sm text-muted text-[24rpx]">
|
||||
{{ item.contact }} {{ item.gender_desc }} {{ item.mobile }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex">
|
||||
<view class="mr-[40rpx]" @click.stop="goEditAddress(item.id)">
|
||||
<u-icon name="edit-pen" color="#CCCCCC" :size="40"></u-icon>
|
||||
</view>
|
||||
<view class="" @click.stop="openDelAddress(item.id)">
|
||||
<u-icon name="trash" color="#CCCCCC" :size="40"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 不可用 - 不在服务范围 -->
|
||||
<template v-if="disableDistanceList.length">
|
||||
<view class="mx-[30rpx] mt-[30rpx]" >
|
||||
以下地址暂不在服务范围
|
||||
</view>
|
||||
<view class="address-item u-flex justify-between mt-[20rpx]" v-for="item in disableDistanceList" :key="item.id">
|
||||
<view class="flex-1 text-[#CCCCCC]">
|
||||
<view class="sm text-muted text-base mb-[20rpx]">
|
||||
{{ item.province }}
|
||||
{{ item.city }}
|
||||
{{ item.district }}
|
||||
</view>
|
||||
<view>
|
||||
<text class="text-base font-bold">{{ item.address }} {{ item?.house_number }}</text>
|
||||
</view>
|
||||
<view class="mt-[10rpx] sm text-muted text-[24rpx]">
|
||||
{{ item.contact }} {{ item.gender_desc }} {{ item.mobile }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex">
|
||||
<view class="mr-[40rpx]" @click.stop="goEditAddress(item.id)">
|
||||
<u-icon name="edit-pen" color="#CCCCCC" :size="40"></u-icon>
|
||||
</view>
|
||||
<view class="" @click.stop="openDelAddress(item.id)">
|
||||
<u-icon name="trash" color="#CCCCCC" :size="40"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</z-paging>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center justify-between bg-white footer">
|
||||
<view>
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="router.navigateTo('/bundle/pages/user_address_edit/index')"
|
||||
>
|
||||
+ 新增地址
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 删除提示弹窗 -->
|
||||
<modal-popup
|
||||
v-model:show="deleteState.showPopup"
|
||||
title="温馨提示"
|
||||
content="确定要删除这个地址吗?"
|
||||
@confirm="deleteAddress"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef, ref, reactive } from 'vue'
|
||||
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { apiAddressLists, apiAddressEditDefault, apiAddressDel } from '@/api/user'
|
||||
import { useRouter } from 'uniapp-router-next'
|
||||
import EmptyAddress from '@/static/images/empty/address.png'
|
||||
import { location, useLocation } from '@/hooks/useLocation'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goodsId = ref(null)
|
||||
const coachId = ref(null)
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const type = ref(false)
|
||||
const addressList = ref<any>([]) // 地址列表-可用
|
||||
const disableDistanceList = ref<any>([]) // 地址列表-不在服务范围
|
||||
const disableServerList = ref<any>([]) // 地址列表-不在开通范围
|
||||
const isIOS = uni.getSystemInfoSync().system.includes('iOS')
|
||||
|
||||
// 删除地址状态
|
||||
const deleteState = reactive({
|
||||
showPopup: false,
|
||||
address_id: -1
|
||||
})
|
||||
|
||||
// 选择当前地址作为支付地址
|
||||
const onSelect = async (event: any) => {
|
||||
const id = event?.currentTarget?.dataset?.id
|
||||
console.log('id=>', id)
|
||||
try {
|
||||
if (type.value) {
|
||||
uni.$emit('address', id)
|
||||
router.navigateBack()
|
||||
} else {
|
||||
await apiAddressEditDefault({ id })
|
||||
router.redirectTo('/bundle/pages/user_address/index')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error, '设置默认地址捕捉错误')
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑地址
|
||||
const goEditAddress = (params = '') => {
|
||||
router.navigateTo({
|
||||
path: '/bundle/pages/user_address_edit/index',
|
||||
query: {
|
||||
id: params
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除地址
|
||||
const openDelAddress = (id: number) => {
|
||||
deleteState.showPopup = true
|
||||
deleteState.address_id = id
|
||||
}
|
||||
|
||||
const deleteAddress = async () => {
|
||||
await apiAddressDel({
|
||||
id: deleteState.address_id
|
||||
})
|
||||
paging.value.refresh()
|
||||
}
|
||||
|
||||
// 获取地址列表
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const res = await apiAddressLists({
|
||||
page_no,
|
||||
page_size,
|
||||
// longitude: location.longitude,
|
||||
// latitude: location.latitude,
|
||||
goods_id: goodsId.value,
|
||||
coach_id: coachId.value
|
||||
})
|
||||
console.log(res, addressList.value)
|
||||
addressList.value = res.usable
|
||||
disableDistanceList.value = res.distance_disable
|
||||
disableServerList.value = res.server_disable
|
||||
paging.value.setLocalPaging(res.usable)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
paging.value.setLocalPaging(false)
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (options.type) {
|
||||
type.value = options.type
|
||||
}
|
||||
if (options.goodsId) {
|
||||
goodsId.value = options.goodsId
|
||||
}
|
||||
if (options.coachId) {
|
||||
coachId.value = options.coachId
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
paging.value?.refresh()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$emit('changeAddress')
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.user-address {
|
||||
.address-item {
|
||||
border-radius: 24rpx;
|
||||
background-color: #ffffff;
|
||||
margin: 30rpx 30rpx;
|
||||
padding: 28rpx 36rpx 28rpx 20rpx;
|
||||
|
||||
.default {
|
||||
padding: 2px 10rpx;
|
||||
margin-left: 20rpx;
|
||||
border: 1px solid var(--color-primary);
|
||||
border-radius: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.setting {
|
||||
height: 90rpx;
|
||||
width: 100rpx;
|
||||
line-height: 90rpx;
|
||||
text-align: right;
|
||||
color: #f36161;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 118rpx;
|
||||
line-height: 118rpx;
|
||||
padding: 0 30rpx;
|
||||
box-sizing: content-box;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
||||
> view {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
361
uniapp/src/bundle/pages/user_address_edit/index.vue
Normal file
@@ -0,0 +1,361 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="user-add__edit flex flex-col min-h-0 h-full">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
:title="!addressId ? '新增地址' : '编辑地址'"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: '#f6f7f8' }"
|
||||
title-color="#000000"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<!-- Mian Start -->
|
||||
<view class="main flex-1">
|
||||
<view class="bg-white m-[30rpx]" style="border-radius: 24rpx">
|
||||
<u-form
|
||||
:model="formData"
|
||||
ref="formRef"
|
||||
:border-bottom="false"
|
||||
:error-type="['message', 'toast']"
|
||||
>
|
||||
<!-- 地址 -->
|
||||
<view class="card">
|
||||
<u-form-item prop="region">
|
||||
<view class="flex flex-1 items-center" @click="chooseLocation">
|
||||
<view class="label"> 地址</view>
|
||||
<view class="flex-1">
|
||||
<span v-if="formData.region">
|
||||
{{ formData.region }}
|
||||
</span>
|
||||
<span v-else class="text-muted">请选择地址</span>
|
||||
<!-- <u-input
|
||||
v-model="formData.region"
|
||||
placeholder="请选择省市区"
|
||||
:disabled="true"
|
||||
/> -->
|
||||
</view>
|
||||
<!--#ifdef H5 -->
|
||||
<!-- <view
|
||||
class="absolute w-full h-full opacity-0"
|
||||
@click="chooseLocation"
|
||||
></view> -->
|
||||
<!-- #endif -->
|
||||
<u-icon name="arrow-right" size="22" color="#888888"></u-icon>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</view>
|
||||
<!-- 门牌号 -->
|
||||
<view class="card">
|
||||
<u-form-item prop="house_number">
|
||||
<view class="flex w-full col-start">
|
||||
<view class="label"> 门牌号</view>
|
||||
<view class="flex-1 pt-[6rpx]">
|
||||
<u-input
|
||||
v-model="formData.house_number"
|
||||
placeholder="请输入门牌号"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</view>
|
||||
<!-- 联系人 -->
|
||||
<view class="card">
|
||||
<u-form-item prop="contact">
|
||||
<view class="flex">
|
||||
<view class="label"> 联系人</view>
|
||||
<view class="flex-1">
|
||||
<u-input
|
||||
v-model="formData.contact"
|
||||
placeholder="请输入联系人"
|
||||
/>
|
||||
<view>
|
||||
<u-radio-group
|
||||
v-model="formData.gender"
|
||||
size="24"
|
||||
labelSize="24"
|
||||
width="150"
|
||||
:active-color="$theme.primaryColor"
|
||||
>
|
||||
<u-radio :name="1" size="28" labelSize="24">
|
||||
<text class="mt-2">先生</text>
|
||||
</u-radio>
|
||||
<u-radio :name="2" size="28" labelSize="24">
|
||||
<text class="mt-2">女士</text>
|
||||
</u-radio>
|
||||
</u-radio-group>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</view>
|
||||
<!-- 手机号 -->
|
||||
<view class="card">
|
||||
<u-form-item prop="mobile">
|
||||
<view class="flex">
|
||||
<view class="label"> 手机号</view>
|
||||
<u-input
|
||||
v-model="formData.mobile"
|
||||
placeholder="请输入联系人手机号"
|
||||
/>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</view>
|
||||
</u-form>
|
||||
</view>
|
||||
|
||||
<!-- 是否默认地址 -->
|
||||
<view
|
||||
class="flex justify-between items-center bg-white mx-[30rpx] px-[20rpx] py-[30rpx]"
|
||||
style="border-radius: 24rpx"
|
||||
>
|
||||
<view>
|
||||
<view class="text-base font-bold text-main">设为默认地址</view>
|
||||
<view class="text-xs text-muted mt-2">提示:下单时会优先使用该地址</view>
|
||||
</view>
|
||||
<view class="pr-4">
|
||||
<u-switch
|
||||
v-model="formData.is_default"
|
||||
size="40"
|
||||
:active-color="$theme.primaryColor"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Mian End -->
|
||||
|
||||
<!-- Footer Start -->
|
||||
<view class="flex p-[20rpx] pt-[30rpx] bg-white safe-area-inset-bottom">
|
||||
<view class="flex-1">
|
||||
<u-button type="primary" @click="onSubmit"> 保存 </u-button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Footer End -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// +----------------------------------------------------------------------
|
||||
// | LikeShop100%开源免费商用电商系统
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | 商业版本务必购买商业授权,以免引起法律纠纷
|
||||
// | 禁止对系统程序代码以任何目的,任何形式的再发布
|
||||
// | Gitee下载:https://gitee.com/likeshop_gitee/likeshop
|
||||
// | 访问官网:https://www.likemarket.net
|
||||
// | 访问社区:https://home.likemarket.net
|
||||
// | 访问手册:http://doc.likemarket.net
|
||||
// | 微信公众号:好象科技
|
||||
// | 好象科技开发团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: LikeShopTeam
|
||||
// +----------------------------------------------------------------------
|
||||
import { onLoad, onReady, onUnload } from '@dcloudio/uni-app'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { toast } from '@/utils/util'
|
||||
import { getGeocoderCoordinate } from '@/api/app'
|
||||
import { apiAddressDetail, apiAddressEdit, apiAddressAdd, apiAddressDel } from '@/api/user'
|
||||
import router from '@/router'
|
||||
|
||||
type AddressType = {
|
||||
contact: string // 联系方式
|
||||
mobile: string | number // 手机号码
|
||||
province: string | number // 省
|
||||
province_id: string | number // 省
|
||||
city: string | number // 市
|
||||
city_id: string | number // 市
|
||||
district: string | number // 区
|
||||
district_id: string | number // 区
|
||||
address: string // 详细地址
|
||||
is_default: number | boolean // 是否默认
|
||||
gender: number // 性别
|
||||
region: string
|
||||
longitude: string // 经度
|
||||
latitude: string // 纬度
|
||||
house_number: string // 门牌号
|
||||
}
|
||||
|
||||
const formData = ref<AddressType>({
|
||||
contact: '',
|
||||
mobile: '',
|
||||
province: '',
|
||||
province_id: '',
|
||||
city: '',
|
||||
city_id: '',
|
||||
district: '',
|
||||
district_id: '',
|
||||
address: '',
|
||||
gender: 1,
|
||||
is_default: 0,
|
||||
region: '',
|
||||
longitude: '',
|
||||
latitude: '',
|
||||
house_number: '' // 门牌号
|
||||
})
|
||||
const formRef = ref()
|
||||
const addressId = ref<string | number>('')
|
||||
const rules = ref<object>({
|
||||
contact: [
|
||||
{ required: true, message: '请输入联系人', trigger: ['change', 'blur'] },
|
||||
{ min: 1, max: 20, message: '输入长度不得超过20位', trigger: ['blur', 'change'] }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请输入手机号码', trigger: ['change', 'blur'] },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
transform(value: any) {
|
||||
return String(value)
|
||||
},
|
||||
message: '请输入正确的手机号'
|
||||
}
|
||||
],
|
||||
region: [{ required: true, message: '请选择地址', trigger: ['change', 'blur'] }],
|
||||
house_number: [{ required: true, message: '请输入门牌号', trigger: ['change', 'blur'] }]
|
||||
})
|
||||
|
||||
// 获取地址详情
|
||||
const getAddressDetail = async (): Promise<void> => {
|
||||
formData.value = await apiAddressDetail({ id: addressId.value })
|
||||
formData.value.region = `${formData.value.city} ${formData.value.district} ${formData.value.address}`
|
||||
}
|
||||
|
||||
// 处理地址
|
||||
const getAddressInfo = async (address: string, title: string): Promise<void> => {
|
||||
// 获取 区或县 后面的详细地址
|
||||
const result = address.match(/(?:.*?(?:县|区))?(.*)/)
|
||||
const part = result?.[1]?.trim() || '' // 无行政区时返回原地址
|
||||
console.log('part =>', part)
|
||||
formData.value.address = part + ' ' + title
|
||||
console.log('formData.value.address =>', formData.value.address)
|
||||
}
|
||||
|
||||
// 校验
|
||||
const onSubmit = () => {
|
||||
formRef.value.validate((valid: boolean) => {
|
||||
if (!valid) return false
|
||||
if (!addressId.value) handleAddressAdd()
|
||||
else handleAddressEdit()
|
||||
})
|
||||
}
|
||||
// 添加地址
|
||||
const handleAddressAdd = async (): Promise<void> => {
|
||||
await apiAddressAdd({ ...formData.value })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 300)
|
||||
}
|
||||
// 编辑地址
|
||||
const handleAddressEdit = async (): Promise<void> => {
|
||||
await apiAddressEdit({ ...formData.value })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 300)
|
||||
}
|
||||
// 选择省市区
|
||||
const chooseLocation = () => {
|
||||
router.navigateTo({
|
||||
path: '/bundle/pages/city/index',
|
||||
query: {
|
||||
is_address: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 逆解析地址
|
||||
const getLocation = async (latitude: string | number, longitude: string | number) => {
|
||||
try {
|
||||
const res = await getGeocoderCoordinate({
|
||||
location: `${latitude},${longitude}`
|
||||
})
|
||||
const data = res.result
|
||||
console.log('逆解析地址 =>', res.result)
|
||||
handleAddressInfo(data)
|
||||
} catch (error) {
|
||||
console.log('地址逆解析', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理接口返回的地址数据
|
||||
const handleAddressInfo = (event: any) => {
|
||||
let city_id = event.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
|
||||
}
|
||||
formData.value.city_id = city_id + ''
|
||||
formData.value.province_id = formData.value.city_id.substr(0, 3) + '000'
|
||||
formData.value.district_id = event.ad_info.adcode
|
||||
formData.value.region = `${event.ad_info.city} ${event.ad_info.district} ${formData.value.address}`
|
||||
formData.value.longitude = event.location.lng
|
||||
formData.value.latitude = event.location.lat
|
||||
}
|
||||
|
||||
// 删除地址
|
||||
const handleAddressDel = async (): Promise<void> => {
|
||||
try {
|
||||
await apiAddressDel({ id: addressId.value })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 300)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options: { id?: number }) => {
|
||||
addressId.value = Number(options.id)
|
||||
if (options.id) {
|
||||
getAddressDetail()
|
||||
}
|
||||
})
|
||||
|
||||
uni.$on('city_select', (value: any) => {
|
||||
console.log('选择地址 ==>', value)
|
||||
getAddressInfo(value.address || '', value.title)
|
||||
getLocation(value.latitude, value.longitude)
|
||||
})
|
||||
|
||||
onReady(() => {
|
||||
formRef.value?.setRules(rules.value)
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
console.log('注销?')
|
||||
uni.$off(['city_select'])
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.user-add__edit {
|
||||
.card {
|
||||
padding: 0 24rpx;
|
||||
border-radius: 14rpx;
|
||||
background-color: #ffffff;
|
||||
|
||||
.label {
|
||||
width: 160rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
font-weight: 900;
|
||||
line-height: 70rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.is-default {
|
||||
image {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
461
uniapp/src/bundle/pages/user_profile/index.vue
Normal file
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="user-data p-[30rpx]">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="true"
|
||||
title="个人资料"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, 0)` }"
|
||||
:title-color="'#000'"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<!-- Main Start -->
|
||||
<!-- 头部修改头像 -->
|
||||
<!-- <view class="header flex flex-col justify-center items-center">
|
||||
<button
|
||||
class="relative flex flex-col items-center after:border-0"
|
||||
hover-class="none"
|
||||
open-type="chooseAvatar"
|
||||
style="background-color: transparent"
|
||||
@click="chooseAvatar"
|
||||
@chooseavatar="chooseAvatar"
|
||||
>
|
||||
<image
|
||||
class="absolute"
|
||||
style="width: 52rpx; height: 52rpx; right: 30rpx; bottom: 70rpx;"
|
||||
src="@/static/images/user_profile_photo.png"
|
||||
></image>
|
||||
<image :src="userInfo?.avatar" style="width: 192rpx; height: 192rpx"/>
|
||||
<view
|
||||
class="mt-[10rpx] text-center text-muted text-xs"
|
||||
@click.stop="(showUserName = true), (newUsername = userInfo?.account)"
|
||||
>
|
||||
账号: {{ userInfo?.account }}
|
||||
</view>
|
||||
</button>
|
||||
</view> -->
|
||||
|
||||
<!-- 头像 -->
|
||||
<view
|
||||
class="item rounded-t-[20rpx] mt-[20rpx] btn-border flex flex-1 justify-between items-center"
|
||||
>
|
||||
<button
|
||||
class="relative flex flex-1 justify-between items-center after:border-0 px-[0rpx]"
|
||||
hover-class="none"
|
||||
open-type="chooseAvatar"
|
||||
style="background-color: transparent"
|
||||
@click="chooseAvatar"
|
||||
@chooseavatar="chooseAvatar"
|
||||
>
|
||||
<view class="label text-left">头像</view>
|
||||
<u-image :src="userInfo?.avatar" width="80" height="80" border-radius="50%"></u-image>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="item flex flex-1 justify-between items-center"
|
||||
>
|
||||
<view class="label">昵称</view>
|
||||
<view class="content text-right pr-2" @click="toChangeAccount(userInfo?.nickname, FieldType.NICKNAME)">{{ userInfo?.nickname }}</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666" @click="toChangeAccount(userInfo?.nickname, FieldType.NICKNAME)"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 账号 -->
|
||||
<view
|
||||
class="item flex flex-1 justify-between items-center"
|
||||
>
|
||||
<view class="label">账号</view>
|
||||
<view class="content text-right pr-2" @click="toChangeAccount(userInfo?.account, FieldType.USERNAME)">{{ userInfo?.account }}</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666" @click="toChangeAccount(userInfo?.account, FieldType.USERNAME)"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 性别 -->
|
||||
<view class="item flex flex-1 justify-between items-center" @click="changeSex">
|
||||
<view class="label">性别</view>
|
||||
<view class="content text-right pr-2" v-if="userInfo?.sex === 0">未知</view>
|
||||
<view class="content text-right pr-2" v-if="userInfo?.sex === 1">先生</view>
|
||||
<view class="content text-right pr-2" v-if="userInfo?.sex === 2">女士</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<view class="item flex flex-1 justify-between items-center">
|
||||
<view class="label">手机号</view>
|
||||
<view class="content text-right pr-2" @click="toBindMobile">{{
|
||||
userInfo?.mobile == '' ? '未绑定手机号' : userInfo?.mobile
|
||||
}}
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="22" color="#666" @click="toBindMobile"></u-icon>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- <u-button
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
type="error"
|
||||
shape="circle"
|
||||
size="mini"
|
||||
:plain="true"
|
||||
class="btn"
|
||||
>
|
||||
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
|
||||
</u-button> -->
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<!-- <u-button
|
||||
@click="showMobilePop = true"
|
||||
type="error"
|
||||
size="mini"
|
||||
shape="circle"
|
||||
:plain="true"
|
||||
>
|
||||
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
|
||||
</u-button> -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 注册时间 -->
|
||||
<view class="item rounded-b-[20rpx] flex flex-1 justify-between items-center">
|
||||
<view class="label">注册时间</view>
|
||||
<view class="content text-right">{{ userInfo?.create_time }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 昵称修改组件 -->
|
||||
<u-popup
|
||||
v-model="showNickName"
|
||||
:closeable="true"
|
||||
mode="center"
|
||||
:maskCloseAble="false"
|
||||
border-radius="20"
|
||||
>
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<form @submit="changeNameConfirm">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改昵称</view>
|
||||
<u-form-item borderBottom>
|
||||
<input
|
||||
class="nr h-[60rpx] w-full"
|
||||
:value="userInfo.nickname"
|
||||
name="nickname"
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<button
|
||||
class="bg-primary text-white w-full h-[80rpx] !text-lg !leading-[80rpx] rounded-full"
|
||||
form-type="submit"
|
||||
hover-class="none"
|
||||
size="mini"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</view>
|
||||
</form>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 账号修改组件 -->
|
||||
<u-popup v-model="showUserName" :closeable="true" mode="center" border-radius="20">
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改账号</view>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="newUsername"
|
||||
placeholder="请输入账号"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<u-button @click="changeUserNameConfirm" type="primary" shape="circle">
|
||||
确定
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 性别修改组件 -->
|
||||
<u-picker
|
||||
mode="selector"
|
||||
v-model="showPicker"
|
||||
confirm-color="#4173FF"
|
||||
:default-selector="[0]"
|
||||
:range="sexList"
|
||||
@confirm="changeSexConfirm"
|
||||
/>
|
||||
|
||||
<!-- 账号修改组件 -->
|
||||
<u-popup v-model="showMobilePop" :closeable="true" mode="center" border-radius="20">
|
||||
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
|
||||
<view class="mb-[70rpx] text-xl text-center">修改手机号码</view>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="newMobile"
|
||||
placeholder="请输入新的手机号码"
|
||||
:border="false"
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="mobileCode"
|
||||
placeholder="请输入验证码"
|
||||
:border="false"
|
||||
/>
|
||||
<view
|
||||
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
|
||||
@click="sendSms"
|
||||
>
|
||||
<u-verification-code
|
||||
ref="uCodeRef"
|
||||
:seconds="60"
|
||||
@change="codeChange"
|
||||
change-text="x秒"
|
||||
/>
|
||||
{{ codeTips }}
|
||||
</view>
|
||||
</u-form-item>
|
||||
<view class="mt-[80rpx]">
|
||||
<u-button @click="changeCodeMobile" type="primary" shape="circle"> 确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, shallowRef} from 'vue'
|
||||
import {onShow, onUnload} from '@dcloudio/uni-app'
|
||||
import {apiUserInfoGet, apiUserInfoSet, userBindMobile, apiWechatMobileGet} from '@/api/user'
|
||||
import {smsSend} from '@/api/app'
|
||||
import {FieldType, SMSEnum} from '@/enums/appEnums'
|
||||
import {uploadFile} from '@/utils/util'
|
||||
|
||||
// 用户信息
|
||||
const userInfo = ref<any>({})
|
||||
// 用户信息的枚举
|
||||
const fieldType = ref(FieldType.NONE)
|
||||
//选择性别数据
|
||||
const sexList = ref<Array<string> | null>(['男', '女'])
|
||||
|
||||
//显示昵称弹窗
|
||||
const showNickName = ref<boolean | null>(false)
|
||||
//显示账户弹窗
|
||||
const showUserName = ref<boolean | null>(false)
|
||||
//显示性别选择弹窗
|
||||
const showPicker = ref<boolean | null>(false)
|
||||
// 显示手机号验证码调整弹窗 非小程序才需要
|
||||
const showMobilePop = ref<boolean | null>(false)
|
||||
|
||||
//新昵称
|
||||
const newNickname = ref<string>('')
|
||||
//新账号
|
||||
const newUsername = ref<string>('')
|
||||
//新的手机号码
|
||||
const newMobile = ref<string>('')
|
||||
|
||||
//修改手机验证码
|
||||
const mobileCode = ref<string>('')
|
||||
const codeTips = ref('')
|
||||
const uCodeRef = shallowRef()
|
||||
|
||||
// 跳转修改昵称、账号页面
|
||||
const toChangeAccount = (data:string, type:number) => {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/change_account/index?data=${data}&type=${type}`
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转更换手机号码页面
|
||||
const toBindMobile = () => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/bind_mobile/index?mobile=${userInfo.value.mobile}`
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const getUser = async (): Promise<void> => {
|
||||
userInfo.value = await apiUserInfoGet()
|
||||
}
|
||||
|
||||
// 获取验证码显示字段
|
||||
const codeChange = (text: string) => {
|
||||
codeTips.value = text
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
const sendSms = async () => {
|
||||
if (!newMobile.value) return uni.$u.toast('请输入新的手机号码')
|
||||
if (uCodeRef.value?.canGetCode) {
|
||||
await smsSend({
|
||||
scene: userInfo.value.mobile ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
|
||||
mobile: newMobile.value
|
||||
})
|
||||
uni.$u.toast('发送成功')
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码修改手机号-非微信小程序
|
||||
const changeCodeMobile = async () => {
|
||||
await userBindMobile({
|
||||
type: userInfo.value.mobile ? 'change' : 'bind',
|
||||
mobile: newMobile.value,
|
||||
code: mobileCode.value
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
showMobilePop.value = false
|
||||
getUser()
|
||||
}
|
||||
|
||||
// 修改用户信息
|
||||
const setUserInfoFun = async (value: string): Promise<void> => {
|
||||
await apiUserInfoSet({
|
||||
field: fieldType.value,
|
||||
value: value
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
getUser()
|
||||
}
|
||||
|
||||
// 上传头像
|
||||
const chooseAvatar = (e: any) => {
|
||||
fieldType.value = FieldType.AVATAR
|
||||
// #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
|
||||
if (e.detail.avatarUrl) {
|
||||
uploadAvatar(e.detail.avatarUrl)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 显示修改用户性别弹窗
|
||||
const changeSex = () => {
|
||||
showPicker.value = true
|
||||
fieldType.value = FieldType.SEX
|
||||
}
|
||||
|
||||
// 修改用户性别
|
||||
const changeSexConfirm = (value) => {
|
||||
setUserInfoFun(value[0] + 1)
|
||||
showPicker.value = false
|
||||
}
|
||||
|
||||
// 修改用户账号
|
||||
const changeUserNameConfirm = async () => {
|
||||
if (newUsername.value == '') return uni.$u.toast('账号不能为空')
|
||||
if (newUsername.value.length > 10) return uni.$u.toast('账号长度不得超过十位数')
|
||||
|
||||
fieldType.value = FieldType.USERNAME
|
||||
await setUserInfoFun(newUsername.value)
|
||||
showUserName.value = false
|
||||
}
|
||||
|
||||
// 修改用户昵称
|
||||
const changeNameConfirm = async (e: any) => {
|
||||
newNickname.value = e.detail.value.nickname
|
||||
if (newNickname.value == '') return uni.$u.toast('昵称不能为空')
|
||||
if (newNickname.value.length > 10) return uni.$u.toast('昵称长度不得超过十位数')
|
||||
fieldType.value = FieldType.NICKNAME
|
||||
await setUserInfoFun(newNickname.value)
|
||||
|
||||
showNickName.value = false
|
||||
}
|
||||
|
||||
// 微信小程序 绑定||修改用户手机号
|
||||
const getPhoneNumber = async (e): Promise<void> => {
|
||||
const {encryptedData, iv, code} = e.detail
|
||||
const data = {
|
||||
code,
|
||||
encrypted_data: encryptedData,
|
||||
iv
|
||||
}
|
||||
if (encryptedData) {
|
||||
await apiWechatMobileGet({
|
||||
...data
|
||||
})
|
||||
uni.$u.toast('操作成功')
|
||||
getUser()
|
||||
}
|
||||
}
|
||||
|
||||
const goPage = (url: string) => {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
|
||||
const uploadAvatar = (path: string) => {
|
||||
uni.showLoading({
|
||||
title: '正在上传中...',
|
||||
mask: true
|
||||
})
|
||||
uploadFile(path)
|
||||
.then((res) => {
|
||||
uni.hideLoading()
|
||||
setUserInfoFun(res.url)
|
||||
})
|
||||
.catch(() => {
|
||||
uni.hideLoading()
|
||||
uni.$u.toast('上传失败')
|
||||
})
|
||||
}
|
||||
|
||||
// 监听从裁剪页发布的事件,获得裁剪结果
|
||||
uni.$on('uAvatarCropper', (path) => {
|
||||
uploadAvatar(path)
|
||||
})
|
||||
|
||||
onShow(async () => {
|
||||
getUser()
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off('uAvatarCropper')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
|
||||
image {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 2rpx;
|
||||
padding: 40rpx 20rpx;
|
||||
background-color: #ffffff;
|
||||
|
||||
.label {
|
||||
width: 150rpx;
|
||||
@apply text-main text-base font-medium;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
width: 80%;
|
||||
padding-right: 12rpx;
|
||||
@apply text-content text-base font-medium text-right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
201
uniapp/src/bundle/pages/user_recharge/user_recharge.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<view class="user-recharge">
|
||||
<view class="wrapper">
|
||||
<view class="balance">充值金额</view>
|
||||
<view class="flex items-center balance-recharge-input">
|
||||
<text style="font-size: 46rpx">¥</text>
|
||||
<input class="input" placeholder="0.00" type="digit" v-model="rechargeData.money" />
|
||||
</view>
|
||||
<view class="balance-recharge-tips mt-[20rpx]">
|
||||
<view>当前可用余额为 ¥ {{ walletData.user_money }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="px-[30rpx]">
|
||||
<button class="btn text-lg text-white rounded-full" @click="handleRecharge()">
|
||||
立即充值
|
||||
</button>
|
||||
|
||||
<navigator
|
||||
class="record"
|
||||
hover-class="none"
|
||||
url="/bundle/pages/recharge_record/recharge_record"
|
||||
>
|
||||
充值记录
|
||||
</navigator>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 充值成功组件 -->
|
||||
<u-popup v-model="rechargeSuccess" :closeable="true" mode="center" border-radius="14">
|
||||
<view class="bg-white recharge-success-card" style="width: 70vw">
|
||||
<view class="recharge-success">
|
||||
<image src="@/static/images/recharge_uccess.png" />
|
||||
<view class="recharge-success-text"> 充值成功 </view>
|
||||
</view>
|
||||
|
||||
<view class="p-[40rpx]">
|
||||
<u-button
|
||||
@click="changeRechargeComplete"
|
||||
type="primary"
|
||||
:ripple="true"
|
||||
:hair-line="false"
|
||||
shape="circle"
|
||||
>
|
||||
完成
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow, onUnload, onLoad } from '@dcloudio/uni-app'
|
||||
import { apiRecharge, apiUserWallet } from '@/api/wallet'
|
||||
import { PaymentStatusEnum } from '@/utils/enum'
|
||||
|
||||
const rechargeAmountLists = ref<Array<any>>([])
|
||||
|
||||
const walletData = ref({
|
||||
user_money: '' // 可用余额
|
||||
})
|
||||
|
||||
const rechargeData = ref({
|
||||
money: '', // 充值金额
|
||||
template_id: '', // 充值模板id
|
||||
pay_way: 1 // 支付方式
|
||||
})
|
||||
|
||||
// 充值弹窗
|
||||
const rechargeSuccess = ref<boolean | null>(false)
|
||||
|
||||
// 获取钱包数据
|
||||
const getWalletData = () => {
|
||||
apiUserWallet('').then((res: any) => {
|
||||
walletData.value.user_money = res.user_money
|
||||
})
|
||||
}
|
||||
|
||||
// 获取充值模板
|
||||
// const getRechargeAmountLists = () => {
|
||||
// apiRechargeTemplateLists('').then((res: any) => {
|
||||
// rechargeAmountLists.value = res
|
||||
// })
|
||||
// }
|
||||
|
||||
// 充值
|
||||
const handleRecharge = (id = '') => {
|
||||
// await apiRecharge({ ...rechargeData.value }).then(() => {
|
||||
// rechargeSuccess.value = true
|
||||
// })
|
||||
delete rechargeData.value.template_id
|
||||
|
||||
if (id !== '') {
|
||||
rechargeData.value.template_id = id
|
||||
delete rechargeData.value.money
|
||||
}
|
||||
|
||||
apiRecharge({ ...rechargeData.value }).then((res: any) => {
|
||||
rechargeData.value.template_id = ''
|
||||
const params = {
|
||||
from: res.from,
|
||||
order_id: res.order_id
|
||||
}
|
||||
// 支付方式hooks
|
||||
try {
|
||||
// 支付方式hooks
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/order_pay/order_pay?order_id=${params.order_id}&from=${params.from}&order_amount=${rechargeData.value.money}`
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('下单', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// const changeRechargeValue = (index: any) => {
|
||||
// rechargeData.value.money = rechargeAmountLists.value[index].money
|
||||
// }
|
||||
|
||||
const changeRechargeComplete = () => {
|
||||
rechargeSuccess.value = false
|
||||
}
|
||||
|
||||
// 跳转页面
|
||||
const goPage = (url: any) => {
|
||||
uni.navigateTo({ url: url })
|
||||
}
|
||||
|
||||
onLoad(async (options) => {
|
||||
// 监听全局duringPayment事件
|
||||
uni.$on('duringPayment', (params) => {
|
||||
if (params.result === PaymentStatusEnum['SUCCESS']) {
|
||||
// uni.navigateBack()
|
||||
// setTimeout(() => toast('支付成功'), 0.5 * 1000)
|
||||
}
|
||||
})
|
||||
uni.$emit('send')
|
||||
|
||||
if (options.rechargeSuccess == 1) {
|
||||
rechargeSuccess.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
uni.$off(['duringPayment', 'send'])
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
getWalletData()
|
||||
// getRechargeAmountLists()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-recharge {
|
||||
.wrapper {
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 40rpx;
|
||||
width: 690rpx;
|
||||
height: 330rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #fff;
|
||||
.balance {
|
||||
font-weight: 500;
|
||||
font-size: 30rpx;
|
||||
line-height: 36rpx;
|
||||
color: #666;
|
||||
margin-bottom: 38rpx;
|
||||
}
|
||||
.balance-recharge-input {
|
||||
margin-top: 35rpx;
|
||||
margin-right: 66rpx;
|
||||
|
||||
.input {
|
||||
height: 94rpx;
|
||||
text-align: left;
|
||||
font-size: 66rpx;
|
||||
margin-left: 30rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
border-bottom: 1rpx solid #ebebeb;
|
||||
}
|
||||
.balance-recharge-tips {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
background: $u-type-primary;
|
||||
}
|
||||
.record {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
223
uniapp/src/bundle/pages/user_wallet/user_wallet.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<page-meta :page-style="$theme.pageStyle">
|
||||
<!-- #ifndef H5 -->
|
||||
<navigation-bar
|
||||
:front-color="$theme.navColor"
|
||||
:background-color="$theme.navBgColor"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</page-meta>
|
||||
<view class="user-wallet flex flex-col min-h-0 h-full">
|
||||
<view class="user-wallet-view flex flex-col min-h-0 h-full">
|
||||
<u-sticky h5-nav-height="0" bg-color="transparent">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
:is-fixed="false"
|
||||
title="我的钱包"
|
||||
:immersive="false"
|
||||
:border-bottom="false"
|
||||
:title-bold="false"
|
||||
:background="{ background: `rgba(256,256, 256, 0)` }"
|
||||
title-color="#000"
|
||||
back-icon-color="#000"
|
||||
>
|
||||
</u-navbar>
|
||||
</u-sticky>
|
||||
|
||||
<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.nickname }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="flex justify-between"
|
||||
style="margin-top: 32rpx"
|
||||
>
|
||||
<view>
|
||||
<price
|
||||
:content="walletData.total_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="router.navigateTo('/bundle/pages/account_detail/account_detail')"
|
||||
>
|
||||
<text>余额明细</text>
|
||||
<u-icon name="arrow-right" size="24"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex-1 m-[30rpx]">
|
||||
<view class="title-bar text-lg font-medium">
|
||||
<text class="ml-3">余额充值</text>
|
||||
</view>
|
||||
|
||||
<view class="grid grid-cols-3 gap-y-2 gap-x-2 mt-4">
|
||||
<view
|
||||
v-for="(item, index) in template"
|
||||
:key="item"
|
||||
style="transition: all 0.5s linear"
|
||||
class="py-[24rpx] text-center rounded-[20rpx]"
|
||||
:class="{
|
||||
'bg-primary text-white': currentIndex == index,
|
||||
'bg-primary-light-9 text-primary': currentIndex != index
|
||||
}"
|
||||
@tap="currentIndex = index"
|
||||
>
|
||||
<price
|
||||
:content="item"
|
||||
main-size="48rpx"
|
||||
minor-size="28rpx"
|
||||
font-weight="900"
|
||||
color="inherit"
|
||||
></price>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
style="transition: all 0.5s linear"
|
||||
class="flex items-center rounded-[20rpx] mt-4 p-[30rpx] bg-white text-primary"
|
||||
>
|
||||
<view class="text-[40rpx] font-bold">¥</view>
|
||||
<input
|
||||
type="digit"
|
||||
v-model="inputMoney"
|
||||
placeholder="请输入自定义金额"
|
||||
class="text-2xl font-medium pl-3"
|
||||
@focus="currentIndex = -1"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 text-muted text-xs font-medium leading-6">
|
||||
<view>充值说明:</view>
|
||||
<view>1.本次充值仅限于在平台上消费,无法跨地区使用。</view>
|
||||
<view>2.若遇到充值未到账,请联系客服</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="footer bg-white safe-area-inset-bottom p-[30rpx]"
|
||||
style="box-shadow: 0 -4px 48px 0 #141a231f;"
|
||||
>
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="handleRecharge"
|
||||
>
|
||||
<text class="text-base font-medium ml-1">充值</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue'
|
||||
import {onShow, onLoad} from '@dcloudio/uni-app'
|
||||
import {apiRecharge, apiUserWallet} from '@/api/wallet'
|
||||
import {useUserStore} from "@/stores/user";
|
||||
import {useRouter} from "uniapp-router-next";
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const walletData = ref<any>({
|
||||
user_money: '', // 可用余额
|
||||
user_earnings: '', // 可提现余额
|
||||
total_money: '', // 总资产
|
||||
})
|
||||
const template = ref([100, 200, 300, 400, 500, 600])
|
||||
const inputMoney = ref('')
|
||||
const currentIndex = ref(0)
|
||||
|
||||
const getWalletData = async () => {
|
||||
walletData.value = await apiUserWallet()
|
||||
}
|
||||
|
||||
const handleRecharge = async () => {
|
||||
const i = currentIndex.value
|
||||
const money = i == -1 ? inputMoney.value : template.value[i]
|
||||
const res = await apiRecharge({
|
||||
money: money,
|
||||
template_id: ''
|
||||
})
|
||||
router.navigateTo({
|
||||
path: '/bundle/pages/order_pay/order_pay',
|
||||
query: {
|
||||
from: res.from,
|
||||
order_id: res.order_id,
|
||||
order_amount: money
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getWalletData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-wallet {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -750px;
|
||||
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-7) 69.66%);
|
||||
}
|
||||
|
||||
.user-wallet-view {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 316rpx;
|
||||
margin: 30rpx;
|
||||
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);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
width: 4px;
|
||||
height: 70%;
|
||||
border-radius: 4px;
|
||||
@apply bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
342
uniapp/src/bundle/pages/user_withdraw/user_withdraw.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<template>
|
||||
<view class="user-withdraw">
|
||||
<view class="balance-withdrawal-card">
|
||||
<u-tabs
|
||||
:list="withdrawConfigData.type"
|
||||
:is-scroll="true"
|
||||
:current="operationCurrent"
|
||||
@change="operationChange"
|
||||
inactive-color="#666"
|
||||
active-color="#33D192"
|
||||
itemWidth="240"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 微信收款码 -->
|
||||
<template
|
||||
v-if="
|
||||
withdrawConfigData.type[operationCurrent]?.value ==
|
||||
BalanceWithdrawalEnum.WECHAT_COLLECTION_CODE
|
||||
"
|
||||
>
|
||||
<view class="payment-code-card">
|
||||
<u-form :model="withdrawApplyData" ref="uForm" label-width="150rpx">
|
||||
<u-form-item label="微信账号">
|
||||
<u-input v-model="withdrawApplyData.account" placeholder="请输入微信账号" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="真实姓名">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.real_name"
|
||||
placeholder="请输入真实姓名"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="备注">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.apply_remark"
|
||||
placeholder="请输入备注(选填)"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
|
||||
<view class="mt-[20rpx]">
|
||||
<uploader v-model="withdrawApplyData.money_qr_code"></uploader>
|
||||
<view class="ml-[10rpx]"> 微信收款码 </view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 支付宝收款码 -->
|
||||
<template
|
||||
v-if="
|
||||
withdrawConfigData.type[operationCurrent]?.value ==
|
||||
BalanceWithdrawalEnum.ALIPAY_COLLECTION_CODE
|
||||
"
|
||||
>
|
||||
<view class="payment-code-card">
|
||||
<u-form :model="withdrawApplyData" ref="uForm" label-width="150rpx">
|
||||
<u-form-item label="支付宝账号">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.account"
|
||||
placeholder="请输入支付宝账号"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="真实姓名">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.real_name"
|
||||
placeholder="请输入真实姓名"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="备注">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.apply_remark"
|
||||
placeholder="请输入备注(选填)"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
|
||||
<view class="mt-[20rpx]">
|
||||
<uploader v-model="withdrawApplyData.money_qr_code"></uploader>
|
||||
<view class="ml-[10rpx]"> 支付宝收款码 </view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 银行卡 -->
|
||||
<template
|
||||
v-if="
|
||||
withdrawConfigData.type[operationCurrent]?.value == BalanceWithdrawalEnum.BANK_CARD
|
||||
"
|
||||
>
|
||||
<view class="payment-code-card">
|
||||
<u-form :model="withdrawApplyData" ref="uForm" label-width="150rpx">
|
||||
<u-form-item label="银行卡账号">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.account"
|
||||
placeholder="请输入银行卡账号"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="持卡人姓名">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.real_name"
|
||||
placeholder="请输入持卡人姓名"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="提现银行">
|
||||
<u-input v-model="withdrawApplyData.bank" placeholder="请输入提现银行" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="银行支行">
|
||||
<u-input v-model="withdrawApplyData.subbank" placeholder="如:荔湾支行" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="备注">
|
||||
<u-input
|
||||
v-model="withdrawApplyData.apply_remark"
|
||||
placeholder="请输入备注(选填)"
|
||||
/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 钱包余额 / 微信钱包 -->
|
||||
<view class="wallet-balance-card">
|
||||
<view class="wallet-balance-input flex">
|
||||
<text style="font-size: 46rpx">¥</text>
|
||||
<input
|
||||
class="flex-1"
|
||||
placeholder="0.00"
|
||||
type="digit"
|
||||
v-model="withdrawApplyData.money"
|
||||
/>
|
||||
|
||||
<view class="withdrawal-text">
|
||||
<view
|
||||
class="all-withdrawal"
|
||||
@click="withdrawApplyData.money = withdrawConfigData.user_earnings"
|
||||
>
|
||||
全部提现
|
||||
</view>
|
||||
<view class="can-withdrawal">
|
||||
可提现余额 ¥ {{ withdrawConfigData.user_earnings }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="wallet-balance-tips"
|
||||
v-if="
|
||||
withdrawConfigData.type[operationCurrent]?.value != BalanceWithdrawalEnum.WALLET
|
||||
"
|
||||
>
|
||||
提示:提现需要扣除服务费{{ withdrawConfigData.percentage }}%
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-[30rpx]">
|
||||
<u-button
|
||||
@click="handleWithdraw()"
|
||||
:ripple="true"
|
||||
:hair-line="false"
|
||||
shape="circle"
|
||||
type="primary"
|
||||
hover-class="none"
|
||||
>
|
||||
确认提现
|
||||
</u-button>
|
||||
|
||||
<view
|
||||
class="withdrawal-record"
|
||||
@click="goPage('/bundle/pages/withdraw_record/withdraw_record')"
|
||||
>
|
||||
提现记录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { BalanceWithdrawalEnum } from '@/utils/enum'
|
||||
import { apiGetWithdrawConfig, apiWithdrawApply } from '@/api/wallet'
|
||||
import uploader from '@/components/uploader/index.vue'
|
||||
import formCard from './components/form-card.vue'
|
||||
|
||||
const operationCurrent = ref<any>(0)
|
||||
|
||||
// 提现配置数据
|
||||
const withdrawConfigData = ref({
|
||||
user_earnings: '', // 可提现金额
|
||||
min_withdraw: '', // 最低提现金额
|
||||
max_withdraw: '', // 最高提现金额
|
||||
percentage: '', // 手续费百分比
|
||||
type: '' // 提现类型
|
||||
})
|
||||
|
||||
// 提现申请数据
|
||||
const withdrawApplyData = ref({
|
||||
type: '', // 提现类型 1-余额 2-微信零钱 3-银行卡 4-微信收款码 5-支付宝收款码
|
||||
money: '', // 提现金额
|
||||
account: '', // 账号; 提现方式:银行卡、微信收款码、支付宝收款码 必填
|
||||
real_name: '', // 真实姓名; 提现方式:银行卡、微信收款码、支付宝收款码 必填
|
||||
money_qr_code: '', // 收款码 提现方式:微信收款码、支付宝收款码 必填
|
||||
bank: '', // 提现银行 提现方式:银行卡 必填
|
||||
subbank: '', // 提现支银行 提现方式:银行卡 必填
|
||||
apply_remark: '' // 备注
|
||||
})
|
||||
|
||||
const operationChange = (e: any) => {
|
||||
operationCurrent.value = e
|
||||
}
|
||||
|
||||
// 获取提现配置
|
||||
const getWithdrawConfig = async () => {
|
||||
const res = await apiGetWithdrawConfig('')
|
||||
withdrawConfigData.value = res
|
||||
}
|
||||
|
||||
// 提现
|
||||
const handleWithdraw = () => {
|
||||
if (withdrawApplyData.value.money == '') return uni.$u.toast('请输入提现金额')
|
||||
|
||||
const params = {
|
||||
type: withdrawConfigData.value.type[operationCurrent.value]?.value,
|
||||
money: withdrawApplyData.value.money,
|
||||
account: withdrawApplyData.value.account,
|
||||
real_name: withdrawApplyData.value.real_name,
|
||||
money_qr_code: withdrawApplyData.value.money_qr_code.toString(),
|
||||
bank: withdrawApplyData.value.bank,
|
||||
subbank: withdrawApplyData.value.subbank,
|
||||
apply_remark: withdrawApplyData.value.apply_remark
|
||||
}
|
||||
|
||||
apiWithdrawApply({ ...params }).then((res: any) => {
|
||||
uni.$u.toast('添加成功')
|
||||
withdrawApplyData.value.money = ''
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/withdrawal_details/withdrawal_details?id=${res.id}`
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转页面
|
||||
const goPage = (url: any) => {
|
||||
uni.navigateTo({ url: url })
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getWithdrawConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-withdraw {
|
||||
padding: 15rpx 30rpx;
|
||||
|
||||
// tab栏
|
||||
.balance-withdrawal-card {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
// 收款码/银行卡
|
||||
.payment-code-card {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 36rpx 30rpx 36rpx;
|
||||
|
||||
.u-list-item.data-v-f8c23944 {
|
||||
background-color: #fff !important;
|
||||
margin: 0;
|
||||
border: 1rpx dashed #ccc;
|
||||
margin-top: 20rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 钱包余额 / 微信钱包
|
||||
.wallet-balance-card {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
padding: 35rpx 0 66rpx 66rpx;
|
||||
|
||||
.wallet-balance-input {
|
||||
margin-top: 20rpx;
|
||||
margin-right: 66rpx;
|
||||
|
||||
input {
|
||||
height: 94rpx;
|
||||
text-align: left;
|
||||
font-size: 66rpx;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
|
||||
border-bottom: 1rpx solid #ebebeb;
|
||||
|
||||
.withdrawal-text {
|
||||
font-size: 24rpx;
|
||||
|
||||
.all-withdrawal {
|
||||
color: $u-type-primary;
|
||||
display: subgrid;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.can-withdrawal {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-balance-tips {
|
||||
margin-top: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.withdrawal-record {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
uniapp/src/bundle/pages/verify/verify.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
title="认证信息"
|
||||
:border-bottom="false"
|
||||
:fixed="false"
|
||||
title-color="#000"
|
||||
back-icon-color="#000"
|
||||
:background="{
|
||||
background: 'transparent'
|
||||
}"
|
||||
z-index="999"
|
||||
/>
|
||||
<view class="top absolute top-0 left-0 z[-1]">
|
||||
<image class="w-[100vw]" mode="aspectFit" :src="logo"></image>
|
||||
<view class="px-[20rpx]">
|
||||
<view class="rounded-lg p-[20rpx] flex items-center bg-white">
|
||||
<u-image src="" height="120" width="120" borderRadius="120"></u-image>
|
||||
<view class="ml-2">
|
||||
<view class="font-bold text-lg">杨业</view>
|
||||
<view class="mt-2">2213123***************</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-lg p-[20rpx] bg-white mt-4">
|
||||
<view>职业资格证</view>
|
||||
<view>
|
||||
<u-image widht="100%" mode="aspectFit"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rounded-lg p-[20rpx] bg-white mt-4">
|
||||
<view>健康证</view>
|
||||
<view>
|
||||
<u-image widht="100%" mode="aspectFit"></u-image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import logo from '@/bundle/static/images/anxingouTop.png'
|
||||
import Nav from '@/components/navbar/navbar.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top {
|
||||
background: linear-gradient(0deg, #f6f7f8 0%, #fdeede 100%);
|
||||
}
|
||||
</style>
|
||||
98
uniapp/src/bundle/pages/withdraw_record/components/card.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view class="withdrawal-record" v-for="(item, index) in withdrawApplyLists" :key="index">
|
||||
<view class="withdrawal-record-item" @click="toDetail(item.id)">
|
||||
<view class="flex">
|
||||
<view class="flex-1 withdrawal-record-item-text">提现至{{ item.type_desc }}</view>
|
||||
<view class="withdrawal-record-item-amount">+{{ item.money }}</view>
|
||||
</view>
|
||||
|
||||
<view class="flex justify-between mt-[10rpx]">
|
||||
<view class="withdrawal-record-item-time"> {{ item.create_time }} </view>
|
||||
|
||||
<template v-if="item.status == 1">
|
||||
<view class="text-xs" style="color: #0cc267">{{ item.status_desc }}</view>
|
||||
</template>
|
||||
|
||||
<template v-if="item.status == 2">
|
||||
<view class="text-xs" style="color: #ff2c3c">{{ item.status_desc }}</view>
|
||||
</template>
|
||||
|
||||
<template v-if="item.status == 3">
|
||||
<view class="text-xs" style="color: #666">{{ item.status_desc }}</view>
|
||||
</template>
|
||||
|
||||
<template v-if="item.status == 4">
|
||||
<view class="text-xs" style="color: #ff2c3c">{{ item.status_desc }}</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<template v-if="item.verify_remark != null">
|
||||
<template v-if="item.verify_remark != ''">
|
||||
<view class="review-tips">审核提示:{{ item.verify_remark }}</view>
|
||||
</template>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
withdrawApplyLists: any
|
||||
}>(),
|
||||
{
|
||||
withdrawApplyLists: []
|
||||
}
|
||||
)
|
||||
|
||||
const toDetail = (id: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/withdrawal_details/withdrawal_details?id=${id}`
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.withdrawal-record {
|
||||
padding-top: 10rpx;
|
||||
|
||||
.withdrawal-record-item {
|
||||
background-color: #fff;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #ebebeb;
|
||||
|
||||
.withdrawal-record-item-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.withdrawal-record-item-amount {
|
||||
font-size: 34rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.withdrawal-record-item-time {
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.review-tips {
|
||||
font-size: 24rpx;
|
||||
color: #ff2c3c;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.success {
|
||||
font-size: 24rpx;
|
||||
color: #0cc267;
|
||||
}
|
||||
|
||||
.fail {
|
||||
font-size: 24rpx;
|
||||
color: #ff2c3c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
35
uniapp/src/bundle/pages/withdraw_record/components/list.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<z-paging
|
||||
auto-show-back-to-top
|
||||
ref="paging"
|
||||
v-model="dataList"
|
||||
@query="queryList"
|
||||
:fixed="false"
|
||||
height="100%"
|
||||
>
|
||||
<Card :withdrawApplyLists="dataList" />
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import Card from './card.vue'
|
||||
import { apiWithdrawLists } from '@/api/wallet'
|
||||
|
||||
const paging = shallowRef<any>(null)
|
||||
const dataList = ref<any>([])
|
||||
|
||||
const queryList = async (page_no: number, page_size: number) => {
|
||||
try {
|
||||
const { lists } = await apiWithdrawLists({
|
||||
page_no,
|
||||
page_size
|
||||
})
|
||||
paging.value.complete(lists)
|
||||
} catch (e) {
|
||||
console.log('报错=>', e)
|
||||
//TODO handle the exception
|
||||
paging.value.complete(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
23
uniapp/src/bundle/pages/withdraw_record/withdraw_record.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="withdrawal-record">
|
||||
<view class="List pt-[20rpx]" v-if="isLogin">
|
||||
<List />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import List from './components/list.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 是否登录
|
||||
const userStore = useUserStore()
|
||||
const isLogin = computed(() => userStore.token)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.List {
|
||||
height: calc(100vh - env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<view class="withdrawal-details">
|
||||
<view class="bg-white withdrawal-details-item">
|
||||
<view class="whether-pass" v-if="formData.status == 1">
|
||||
<image src="@/bundle/static/images/icon_cashOut_wait.png" />
|
||||
<view class="mt-[12rpx]">{{ formData.status_desc }}</view>
|
||||
</view>
|
||||
|
||||
<view class="whether-pass" v-if="formData.status == 2">
|
||||
<image src="@/bundle/static/images/icon_cashOut_wait.png" />
|
||||
<view class="mt-[12rpx]">{{ formData.status_desc }}</view>
|
||||
</view>
|
||||
|
||||
<view class="whether-pass" v-if="formData.status == 3">
|
||||
<image src="@/bundle/static/images/icon_cashOut_success.png" />
|
||||
<view class="mt-[12rpx]">{{ formData.status_desc }}</view>
|
||||
</view>
|
||||
|
||||
<view class="whether-pass" v-if="formData.status == 4">
|
||||
<image src="@/bundle/static/images/icon_cashOut_fail.png" />
|
||||
<view class="mt-[12rpx]">{{ formData.status_desc }}</view>
|
||||
</view>
|
||||
|
||||
<view class="withdrawal-money">
|
||||
<view class="withdrawal-money-icon"> ¥ </view>
|
||||
<view class="withdrawal-money-text"> {{ formData.money }} </view>
|
||||
</view>
|
||||
|
||||
<view class="ml-[30rpx] mt-[20rpx] mr-[30rpx] pb-[20rpx]">
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>提现单号</view>
|
||||
<view>{{ formData.sn }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>申请时间</view>
|
||||
<view>{{ formData.create_time }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>提现至</view>
|
||||
<view>{{ formData.type_desc }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>服务费</view>
|
||||
<view>{{ formData.handling_fee }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>实际到账</view>
|
||||
<view>{{ formData.left_money }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 银行卡提现 -->
|
||||
<view
|
||||
class="ml-[30rpx] mt-[20rpx] mr-[30rpx] pt-[20rpx]"
|
||||
style="border-top: 1rpx solid #e5e5e5"
|
||||
v-if="formData.type == 3"
|
||||
>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>银行卡账号</view>
|
||||
<view>{{ formData.account }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>持卡人姓名</view>
|
||||
<view>{{ formData.real_name }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>提现银行</view>
|
||||
<view>{{ formData.bank }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>银行支行</view>
|
||||
<view>{{ formData.subbank }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>备注说明</view>
|
||||
<view>{{ formData.transfer_remark }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付宝收款码提现 -->
|
||||
<view
|
||||
class="ml-[30rpx] mt-[20rpx] mr-[30rpx] pt-[20rpx]"
|
||||
style="border-top: 1rpx solid #e5e5e5"
|
||||
v-if="formData.type == 5"
|
||||
>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>支付宝账号</view>
|
||||
<view>{{ formData.account }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>真实姓名</view>
|
||||
<view>{{ formData.real_name }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>支付宝收款码</view>
|
||||
<u-image
|
||||
height="160"
|
||||
width="160"
|
||||
:src="formData.money_qr_code"
|
||||
@click="showImage([formData.money_qr_code])"
|
||||
>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>备注说明</view>
|
||||
<view>{{ formData.transfer_remark }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信收款码提现 -->
|
||||
<view
|
||||
class="ml-[30rpx] mt-[20rpx] mr-[30rpx] pt-[20rpx]"
|
||||
style="border-top: 1rpx solid #e5e5e5"
|
||||
v-if="formData.type == 4"
|
||||
>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>微信账号</view>
|
||||
<view>{{ formData.account }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>真实姓名</view>
|
||||
<view>{{ formData.real_name }}</view>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>微信收款码</view>
|
||||
<u-image
|
||||
height="160"
|
||||
width="160"
|
||||
:src="formData.money_qr_code"
|
||||
@click="showImage([formData.money_qr_code])"
|
||||
>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>备注说明</view>
|
||||
<view>{{ formData.transfer_remark }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转账凭证 -->
|
||||
<view
|
||||
class="mx-[30rpx] my-[20rpx] pt-[20rpx]"
|
||||
style="border-top: 1rpx solid #e5e5e5"
|
||||
v-if="formData.status == 3 || formData.status == 4"
|
||||
>
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>转账凭证</view>
|
||||
<u-image
|
||||
height="160"
|
||||
width="160"
|
||||
:src="formData.transfer_voucher"
|
||||
v-if="formData.transfer_voucher"
|
||||
@click="showImage([formData.transfer_voucher])"
|
||||
>
|
||||
</u-image>
|
||||
</view>
|
||||
|
||||
<view class="flex justify-between withdrawal-content">
|
||||
<view>转账说明</view>
|
||||
<view>{{ formData.transfer_remark || '-' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="check-withdrawal-record">
|
||||
<button class="Btn" @click="toRecord">查看历史提现记录</button>
|
||||
|
||||
<view class="mt-[20rpx]">
|
||||
<button class="Btn del_Btn" @click="toHome">返回首页</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="review-success-tips">* 审核成功后约72小时内到账,请留意账户明细</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { apiWithdrawDetail } from '@/api/wallet'
|
||||
|
||||
const withdrawId = ref<number | string>('')
|
||||
|
||||
const formData = ref({
|
||||
type: '', // 提现类型:1-钱包余额;2-微信零钱;3-银行卡;4-微信收款码;5-支付宝收款码
|
||||
type_desc: '', // 提现类型描述
|
||||
status: '', // 提现状态:1-待提现2-提现中3-提现成功4-提现失败
|
||||
status_desc: '', // 提现状态描述
|
||||
money: '', // 提现金额
|
||||
money_qr_code: '', // 收款码
|
||||
create_time: '', // 提现申请时间
|
||||
sn: '', // 提现编号
|
||||
handling_fee: '', // 服务费
|
||||
account: '', // 提现账号
|
||||
real_name: '', // 提现人姓名
|
||||
bank: '', // 提现银行
|
||||
subbank: '', // 提现支行
|
||||
transfer_voucher: '', // 转账凭证
|
||||
transfer_remark: '' // 转账备注
|
||||
})
|
||||
|
||||
// 获取提现申请详情
|
||||
const getWithdrawDetail = () => {
|
||||
apiWithdrawDetail({ id: withdrawId.value }).then((res: any) => {
|
||||
formData.value = res
|
||||
})
|
||||
}
|
||||
|
||||
const toRecord = () => {
|
||||
uni.redirectTo({
|
||||
url: '/bundle/pages/withdraw_record/withdraw_record'
|
||||
})
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
|
||||
const showImage = (list: any) => {
|
||||
uni.previewImage({
|
||||
urls: list,
|
||||
current: 1
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
withdrawId.value = options?.id || ''
|
||||
getWithdrawDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.withdrawal-details {
|
||||
padding: 20rpx;
|
||||
|
||||
.withdrawal-details-item {
|
||||
.whether-pass {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
padding-top: 40rpx;
|
||||
|
||||
image {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.withdrawal-money {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #ff2c3c;
|
||||
margin-top: 20rpx;
|
||||
padding-bottom: 52rpx;
|
||||
|
||||
.withdrawal-money-icon {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.withdrawal-money-text {
|
||||
font-size: 46rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.withdrawal-content {
|
||||
padding-bottom: 34rpx;
|
||||
}
|
||||
|
||||
.check-withdrawal-record {
|
||||
padding: 40rpx 0rpx 50rpx 0;
|
||||
margin: 0 30rpx;
|
||||
border-top: 1px solid #ebebeb;
|
||||
|
||||
.Btn {
|
||||
@apply bg-white text-base text-black leading-[72rpx] h-[72rpx] rounded-full;
|
||||
border: 1px solid $u-type-primary;
|
||||
background-color: $u-type-primary;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
.del_Btn {
|
||||
border: 1px solid $u-type-primary;
|
||||
background-color: #c9c9c900;
|
||||
color: $u-type-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.review-success-tips {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
BIN
uniapp/src/bundle/static/icon/distance.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
uniapp/src/bundle/static/icon/favor.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
uniapp/src/bundle/static/icon/user.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
uniapp/src/bundle/static/images/Alipay.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
uniapp/src/bundle/static/images/anxingouLogo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
uniapp/src/bundle/static/images/anxingouTop.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
uniapp/src/bundle/static/images/icon_address.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
uniapp/src/bundle/static/images/icon_cashOut_fail.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
uniapp/src/bundle/static/images/icon_cashOut_success.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
uniapp/src/bundle/static/images/icon_cashOut_wait.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
uniapp/src/bundle/static/images/icon_del.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
uniapp/src/bundle/static/images/icon_phone.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
uniapp/src/bundle/static/images/icon_wechat.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
uniapp/src/bundle/static/images/map/good.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
uniapp/src/bundle/static/images/map/origin.png
Normal file
|
After Width: | Height: | Size: 293 B |
6
uniapp/src/bundle/static/images/mb-like.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="28" height="28" style="border-color: rgba(0,0,0,0);border-width: bpx;border-style: undefined" filter="none">
|
||||
|
||||
<g>
|
||||
<path d="M25.467 11.733c3.239 0 4.442 2.018 3.739 4.629l-1.438 5.403-1.44 5.402-0.183 0.693h-23.611v-15.235h8.621l2.496-7.532c0.060-0.285 0.195-0.631 0.452-0.981 0.477-0.649 1.219-1.045 2.187-1.045 1.039 0 1.78 0.517 2.165 1.323 0.212 0.446 0.279 0.869 0.279 1.195v6.149h6.733zM24.709 25.994v0zM25.963 21.285l1.44-5.405c0.409-1.521-0.043-2.281-1.936-2.281h-8.599v-8.016c-0.008-0.143-0.043-0.276-0.101-0.397l0.003 0.006c-0.089-0.185-0.197-0.26-0.48-0.26-0.357 0-0.548 0.102-0.683 0.284-0.057 0.075-0.102 0.163-0.129 0.259l-0.001 0.006-0.035 0.141-2.939 8.869h-8.102v11.502h20.309l1.253-4.708zM8.8 13.733v13.867h1.867v-13.867z" fill="rgba(243.015,96.9,96.9,1)"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 877 B |
BIN
uniapp/src/bundle/static/images/search_bg.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
uniapp/src/bundle/static/images/search_hot.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
uniapp/src/bundle/static/images/service_bg.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
90
uniapp/src/components/agreement/agreement.vue
Normal 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="/bundle/pages/agreement/agreement?type=service"
|
||||
>
|
||||
《服务协议》
|
||||
</router-navigate>
|
||||
</view>
|
||||
|
||||
和
|
||||
<view class="text-primary" @click.stop>
|
||||
<router-navigate
|
||||
class="text-primary"
|
||||
to="/bundle/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>
|
||||
107
uniapp/src/components/avatar-upload/avatar-upload.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<button
|
||||
class="avatar-upload p-0 m-0 rounded inline-flex flex-col items-center"
|
||||
hover-class="none"
|
||||
open-type="chooseAvatar"
|
||||
@click="chooseAvatar"
|
||||
@chooseavatar="chooseAvatar"
|
||||
>
|
||||
<image
|
||||
:style="styles"
|
||||
class="w-full h-full"
|
||||
mode="heightFix"
|
||||
:src="modelValue"
|
||||
v-if="modelValue"
|
||||
/>
|
||||
<slot v-else>
|
||||
<view
|
||||
: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"/>
|
||||
添加图片
|
||||
</view>
|
||||
</slot>
|
||||
<slot name="footer"></slot>
|
||||
</button>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {addUnit} from '@/utils/util'
|
||||
import {isBoolean} from 'lodash'
|
||||
import {computed, CSSProperties, onUnmounted} from 'vue'
|
||||
import {useRouter} from 'uniapp-router-next'
|
||||
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 140
|
||||
},
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: string): void
|
||||
(event: 'upload', value: string): void
|
||||
}>()
|
||||
|
||||
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 = async (e: any) => {
|
||||
// #ifndef MP-WEIXIN
|
||||
router.navigateTo({
|
||||
path: '/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) => {
|
||||
emit('update:modelValue', file)
|
||||
emit('upload', file)
|
||||
}
|
||||
// 监听从裁剪页发布的事件,获得裁剪结果
|
||||
uni.$on('uAvatarCropper', (path) => {
|
||||
// console.log(path)
|
||||
uploadImageIng(path)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
uni.$off('uAvatarCropper')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar-upload {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
uniapp/src/components/evaluate-card/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<view class="mt-[20rpx] pb-[20rpx]" :class="{ item: border }">
|
||||
<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
|
||||
border: true
|
||||
}>()
|
||||
|
||||
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>
|
||||
93
uniapp/src/components/goods-card/index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<template v-if="type == 'list'">
|
||||
<block v-for="goodsItem in goodsList" :key="goodsItem.id">
|
||||
<view
|
||||
class="goods-item goods-list w-[340rpx] h-[480rpx] mt-[20rpx] flex bg-white"
|
||||
@click="goPage(goodsItem.id)"
|
||||
>
|
||||
<u-image :src="goodsItem.image" width="340" height="340"></u-image>
|
||||
<view class="goods-name truncate pl-[20rpx] pt-[20rpx]">
|
||||
{{ goodsItem.name }}
|
||||
</view>
|
||||
<view class="pl-[20rpx] mt-[10rpx]">
|
||||
<price
|
||||
:price="goodsItem?.price"
|
||||
:desc="goodsItem.unit_desc || goodsItem?.unit_name"
|
||||
></price>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</template>
|
||||
|
||||
<template v-if="type == 'row'">
|
||||
<block v-for="goodsItem in goodsList" :key="goodsItem.id">
|
||||
<view
|
||||
class="goods-item w-[220rpx] h-[310rpx] mr-[20rpx] flex bg-white"
|
||||
@click="goPage(goodsItem.id)"
|
||||
>
|
||||
<u-image :src="goodsItem.image" width="220" height="200"></u-image>
|
||||
<view class="goods-name truncate pt-[20rpx]">
|
||||
{{ goodsItem.name }}
|
||||
</view>
|
||||
<view class="mt-[10rpx]">
|
||||
<price
|
||||
:price="goodsItem?.price"
|
||||
:desc="goodsItem.unit_desc || goodsItem?.unit_name"
|
||||
></price>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/** Props Start **/
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
type: string // 列表类型
|
||||
goodsList?: any // 商品数据
|
||||
}>(),
|
||||
{
|
||||
type: 'list',
|
||||
goodsList: []
|
||||
}
|
||||
)
|
||||
/** Props End **/
|
||||
|
||||
/** Methods Start **/
|
||||
/**
|
||||
* @param { string } url
|
||||
* @return { void }
|
||||
* @description 跳转到商品
|
||||
*/
|
||||
const goPage = (id: number | string) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods/index?id=${id}`
|
||||
})
|
||||
}
|
||||
/** Methods End **/
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-item {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
border-radius: 8rpx;
|
||||
|
||||
.goods-name {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 28rpx;
|
||||
color: #222222;
|
||||
}
|
||||
}
|
||||
.goods-list {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.goods-list:nth-child(2n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
97
uniapp/src/components/l-swiper/l-swiper.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<u-swiper
|
||||
v-if="lists.length"
|
||||
: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>
|
||||
82
uniapp/src/components/la-swiper/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<u-swiper
|
||||
v-if="lists.length"
|
||||
:list="lists"
|
||||
:mode="mode"
|
||||
:height="height"
|
||||
:effect3d="effect3d"
|
||||
:indicator-pos="indicatorPos"
|
||||
:autoplay="autoplay"
|
||||
:interval="interval"
|
||||
:circular="circular"
|
||||
:border-radius="borderRadius"
|
||||
:current="current"
|
||||
:name="name"
|
||||
@click="handleClick"
|
||||
>
|
||||
</u-swiper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
import { watchEffect, computed } from 'vue'
|
||||
|
||||
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
|
||||
circular?: boolean // 是否衔接播放
|
||||
current?: number // 默认显示第几项
|
||||
name?: string // 显示的属性
|
||||
borderRadius?: number | string //圆角
|
||||
}>(),
|
||||
{
|
||||
content: {
|
||||
data: []
|
||||
},
|
||||
mode: 'round',
|
||||
indicatorPos: 'bottomCenter',
|
||||
height: '340',
|
||||
effect3d: false,
|
||||
autoplay: true,
|
||||
interval: '2500',
|
||||
circular: true,
|
||||
current: 0,
|
||||
name: 'image',
|
||||
borderRadius: 0
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
} catch (error) {
|
||||
//TODO handle the exception
|
||||
console.log('轮播图数据错误', error)
|
||||
}
|
||||
})
|
||||
|
||||
const lists = computed(() => props.content.data || [])
|
||||
|
||||
const handleClick = (index: number) => {
|
||||
const link = props.content.data[index]?.link
|
||||
if (!link) return
|
||||
navigateTo(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
73
uniapp/src/components/master-worker-card/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<view
|
||||
class="mx-[30rpx] mt-[30rpx] p-[12rpx] rounded-[20rpx] bg-white flex "
|
||||
@click="emits('confirm', data.id)"
|
||||
>
|
||||
<u-image width="188" height="188" borderRadius="12px" :src="data.work_photo"></u-image>
|
||||
<view class="ml-3 flex-1 flex flex-col">
|
||||
<view class="flex justify-between relative pt-1">
|
||||
<text class="font-bold text-lg">{{ data.name }}</text>
|
||||
<view
|
||||
v-if="type == 2"
|
||||
class="absolute px-[20rpx] py-[10rpx] text-xs"
|
||||
:class="{
|
||||
'text-primary bg-primary-light-9': data.work_status == 1,
|
||||
'text-[#f6f7f8] text-content': data.work_status != 1
|
||||
}"
|
||||
style="top: -12rpx; right: -12rpx; border-radius: 0 24rpx 0 24rpx"
|
||||
>
|
||||
<text v-if="data.work_status == 1"> 最早可约{{ data.first_appoint }} </text>
|
||||
<text v-else>休息中</text>
|
||||
</view>
|
||||
<text v-if="type == 1 && data.work_status == 1" class="text-[20rpx] text-[#C38925]">
|
||||
最早可约{{ data.first_appoint }}
|
||||
</text>
|
||||
<text v-if="type == 1 && data.work_status != 1" class="text-[20rpx] text-muted">
|
||||
已休息
|
||||
</text>
|
||||
</view>
|
||||
<view class="text-xs flex n mt-2">
|
||||
<view>
|
||||
<text class="text-info">已服务</text>
|
||||
<text>{{ data.order_num }}</text>
|
||||
<text class="text-info">人</text>
|
||||
</view>
|
||||
<view class="ml-2">
|
||||
<text class="text-info">好评率</text>
|
||||
<text>{{ data.good_comment }}</text>
|
||||
</view>
|
||||
<view class="ml-auto">
|
||||
<text class="text-info">{{ data.distance_desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-4 flex justify-between">
|
||||
<view class="flex items-center">
|
||||
<view class="text-info flex items-center">
|
||||
<u-image :src="icon_star" width="32" height="32"></u-image>
|
||||
<text class="ml-1">{{ data.collect_num }}</text>
|
||||
</view>
|
||||
<view class="text-info flex items-center ml-4">
|
||||
<u-image :src="icon_flower" width="32" height="32"></u-image>
|
||||
<text class="ml-1">{{ data.comment_num }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-button class="mx-[0rpx]" type="primary" size="mini" :disabled="data.work_status != 1">
|
||||
立即下单
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import icon_star from '@/static/images/icon/icon_star.png'
|
||||
import icon_flower from '@/static/images/icon/icon_flower.png'
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'confirm', id: number): void
|
||||
}>()
|
||||
const props = defineProps<{
|
||||
data: any
|
||||
type: '1' | '2'
|
||||
}>()
|
||||
</script>
|
||||
130
uniapp/src/components/modal-popup/modal-popup.vue
Normal 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>
|
||||
203
uniapp/src/components/navbar/navbar.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<view class="navbar" @tap.stop="navbar.showFloat = false">
|
||||
<u-navbar
|
||||
:background="background"
|
||||
:title="title"
|
||||
:title-color="titleColor"
|
||||
:border-bottom="borderBottom"
|
||||
:immersive="immersive"
|
||||
:title-bold="true"
|
||||
:is-back="false"
|
||||
>
|
||||
<view class="navbar-left flex py-[12rpx] px-[25rpx] rounded-[30rpx] ml-[20rpx]">
|
||||
<u-icon :name="backIcon" :size="36" @click="goBack"></u-icon>
|
||||
<view class="line"></view>
|
||||
<view class="navbar-lists" @tap.stop="navbar.showFloat = !navbar.showFloat">
|
||||
<u-icon :name="IconList" :size="32"></u-icon>
|
||||
<view class="navbar-float" v-show="navbar.showFloat">
|
||||
<view
|
||||
v-for="(item, index) in navbar.list"
|
||||
:key="index"
|
||||
:to="item.url"
|
||||
:open-type="item.type"
|
||||
class="float-item"
|
||||
hover-class="none"
|
||||
@click="gotoPage(item)"
|
||||
>
|
||||
<view class="flex items-center justify-center">
|
||||
<u-icon :name="item.icon" :size="44"></u-icon>
|
||||
<text class="ml-[20rpx]">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-navbar>
|
||||
<view class="mask" v-show="navbar.showFloat" @touchstart="navbar.showFloat = false"> </view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
import IconBack from '@/static/images/icon/icon_back.png'
|
||||
import IconList from '@/static/images/icon/icon_list.png'
|
||||
import IconHome from '@/static/images/icon/icon_home.png'
|
||||
import IconSearch from '@/static/images/icon/icon_search.png'
|
||||
import IconUser from '@/static/images/icon/icon_nav_user.png'
|
||||
import IconCart from '@/static/images/icon/icon_cart.png'
|
||||
// import { useRouter } from 'uniapp-router-next'
|
||||
import { navigateTo } from '@/utils/util'
|
||||
|
||||
// const router = useRouter()
|
||||
const props = defineProps({
|
||||
// 导航标题
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
// 导航标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
// 导航的背景颜色
|
||||
background: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
background: '#ffffff'
|
||||
})
|
||||
},
|
||||
// 是否显示底部边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
immersive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const navbar = reactive({
|
||||
isIndex: false,
|
||||
showFloat: false,
|
||||
list: [
|
||||
{
|
||||
url: '/pages/index/index',
|
||||
name: '首页',
|
||||
icon: IconHome,
|
||||
type: 'switchTab'
|
||||
},
|
||||
{
|
||||
url: '/pages/search/search',
|
||||
name: '搜索',
|
||||
icon: IconSearch,
|
||||
type: 'navigate'
|
||||
},
|
||||
{
|
||||
url: '/pages/shop_cart/shop_cart',
|
||||
name: '购物车',
|
||||
icon: IconCart,
|
||||
type: 'switchTab'
|
||||
},
|
||||
{
|
||||
url: '/pages/user/user',
|
||||
name: '个人中心',
|
||||
icon: IconUser,
|
||||
type: 'switchTab'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const backIcon = computed(() => (navbar.isIndex ? IconHome : IconBack))
|
||||
|
||||
const goBack = () => {
|
||||
if (!navbar.isIndex) {
|
||||
uni.navigateBack()
|
||||
return
|
||||
}
|
||||
// router.reLaunch('/pages/index/index')
|
||||
}
|
||||
|
||||
const gotoPage = (item: any) => {
|
||||
if (item.type == 'switchTab') {
|
||||
uni.switchTab({
|
||||
url: item.url
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: item.url
|
||||
})
|
||||
//navigateTo(item.url, 'navigateTo')
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length == 1) {
|
||||
navbar.isIndex = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
.navbar-left {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 1rpx solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
.line {
|
||||
width: 1px;
|
||||
height: 36rpx;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
margin: 0 25rpx;
|
||||
}
|
||||
|
||||
.navbar-lists {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.navbar-float {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
width: 258rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 14rpx;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.06);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 14rpx solid transparent;
|
||||
border-bottom-color: #fff;
|
||||
transform: translate(-50%, -100%);
|
||||
}
|
||||
|
||||
.float-item {
|
||||
padding: 20rpx 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
uniapp/src/components/news-card/news-card.vue
Normal 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-lg font-medium">{{ 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>
|
||||
154
uniapp/src/components/order-footer/append.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="showPopup"
|
||||
mode="bottom"
|
||||
:customStyle="{
|
||||
'background': `none`
|
||||
}"
|
||||
:closeable="true"
|
||||
:mask-close-able="false"
|
||||
@close="showPopup = false"
|
||||
>
|
||||
<view
|
||||
style="border-radius: 20rpx 20rpx 0 0;"
|
||||
class="append-popup bg-white p-[30rpx] text-center"
|
||||
>
|
||||
<view class="py-2 font-medium text-2xl text-black">
|
||||
{{ title }}
|
||||
</view>
|
||||
|
||||
<view class="mt-3">
|
||||
<view
|
||||
class="flex text-base bg-[#f6f7f8]"
|
||||
style="border-radius: 20rpx; padding: 20rpx 24rpx;"
|
||||
>
|
||||
<view class="flex-none">
|
||||
<u-image
|
||||
:src="goods.goods_image"
|
||||
width="136"
|
||||
height="136"
|
||||
border-radius="20"
|
||||
/>
|
||||
</view>
|
||||
<view class="ml-2 flex-1 text-left">
|
||||
<view class="line-clamp-1 text-base font-bold text-main">
|
||||
{{ goods.goods_name }}
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-muted">
|
||||
服务时间:{{ duration }}分钟
|
||||
</view>
|
||||
<view class="u-flex justify-between mt-1 ">
|
||||
<price
|
||||
:content="goodsPrice"
|
||||
main-size="32rpx"
|
||||
minor-size="20rpx"
|
||||
font-weight="900"
|
||||
color="#E86016"
|
||||
></price>
|
||||
<u-number-box
|
||||
bg-color=""
|
||||
v-model="goodsNum"
|
||||
@change="getOrderInfo"
|
||||
></u-number-box>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 text-left">
|
||||
<view class="text-xs font-medium text-muted">
|
||||
*支付前需与服务人员协商一致,如有异议,请联系平台客服。
|
||||
</view>
|
||||
<view class="mt-1 text-xs font-medium text-muted">
|
||||
系统只保障在线支付的项目,切勿线下支付。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="margin-top: 90rpx" class="footer flex-1 safe-area-inset-bottom">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="confirm"
|
||||
>
|
||||
<text class="font-medium mr-2">立即支付</text>
|
||||
<price
|
||||
:content="orderAmount"
|
||||
main-size="40rpx"
|
||||
minor-size="24rpx"
|
||||
font-weight="900"
|
||||
color="#ffffff"
|
||||
></price>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref, watch} from 'vue'
|
||||
import { useRouter } from 'uniapp-router-next'
|
||||
import {apiOrderAppend} from "@/api/order";
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
title: string
|
||||
goods: any
|
||||
order_id: number
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:show', show: boolean): void
|
||||
}>()
|
||||
|
||||
const showPopup = computed({
|
||||
get() {
|
||||
return props.show
|
||||
},
|
||||
set(val) {
|
||||
if (!val) {
|
||||
goodsNum.value = 1
|
||||
}
|
||||
emits('update:show', val)
|
||||
}
|
||||
})
|
||||
const router = useRouter()
|
||||
const goodsNum = ref<number>(1)
|
||||
const goodsPrice = ref<string>('')
|
||||
const duration = ref<number>(0)
|
||||
const orderAmount = ref<number>(0)
|
||||
|
||||
const getOrderInfo = async () => {
|
||||
const data = await apiOrderAppend({
|
||||
order_id: props.order_id,
|
||||
goods: {
|
||||
0: { id: props.goods.goods_id, goods_num: goodsNum.value }
|
||||
},
|
||||
action: 'settlement'
|
||||
})
|
||||
orderAmount.value = data.order_amount
|
||||
goodsPrice.value = data.goods_price
|
||||
duration.value = data.duration
|
||||
}
|
||||
|
||||
const confirm = async () => {
|
||||
const { id, type } = await apiOrderAppend({
|
||||
order_id: props.order_id,
|
||||
goods: {
|
||||
0: { id: props.goods.goods_id, goods_num: goodsNum.value }
|
||||
},
|
||||
action: 'submit'
|
||||
})
|
||||
router.navigateTo({
|
||||
url: '/bundle/pages/order_pay/order_pay',
|
||||
query: {
|
||||
order_id: id,
|
||||
from: type,
|
||||
order_amount: orderAmount.value
|
||||
}
|
||||
})
|
||||
showPopup.value = false
|
||||
}
|
||||
|
||||
watch(() => props.show, () => {
|
||||
setTimeout(getOrderInfo, 100)
|
||||
})
|
||||
</script>
|
||||
116
uniapp/src/components/order-footer/gap.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="showPopup"
|
||||
mode="bottom"
|
||||
:customStyle="{
|
||||
'background': `none`
|
||||
}"
|
||||
:closeable="true"
|
||||
:mask-close-able="false"
|
||||
@close="showPopup = false"
|
||||
>
|
||||
<view
|
||||
style="border-radius: 20rpx 20rpx 0 0;"
|
||||
class="append-popup bg-white p-[30rpx] text-center"
|
||||
>
|
||||
<view class="py-2 font-medium text-2xl text-black">
|
||||
{{ title }}
|
||||
</view>
|
||||
|
||||
<view class="mt-3">
|
||||
|
||||
<view
|
||||
class="flex text-base bg-[#f6f7f8]"
|
||||
style="border-radius: 20rpx; padding: 20rpx 24rpx;"
|
||||
>
|
||||
<view class="flex-none text-base font-medium">
|
||||
补差价金额
|
||||
</view>
|
||||
<view class="ml-2 flex-1 text-right">
|
||||
<input type="number" v-model="orderAmount" placeholder="¥ 0.00">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="flex text-base bg-[#f6f7f8] mt-3"
|
||||
style="border-radius: 20rpx; padding: 20rpx 24rpx;"
|
||||
>
|
||||
<view class="flex-none text-base font-medium">
|
||||
备注
|
||||
</view>
|
||||
<view class="ml-2 flex-1 text-right">
|
||||
<input type="text" v-model="orderDesc" placeholder="请输入补差价原因">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-4 text-left">
|
||||
<view class="text-xs font-medium text-muted">
|
||||
*支付前需与服务人员协商一致,如有异议,请联系平台客服。
|
||||
</view>
|
||||
<view class="mt-1 text-xs font-medium text-muted">
|
||||
系统只保障在线支付的项目,切勿线下支付。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="margin-top: 90rpx" class="footer flex-1 safe-area-inset-bottom">
|
||||
<u-button
|
||||
type="primary"
|
||||
@click="confirm"
|
||||
>
|
||||
立即支付
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from 'vue'
|
||||
import {apiOrderGap} from "@/api/order";
|
||||
import {useRouter} from "uniapp-router-next";
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
title: string
|
||||
order_id: number
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'update:show', show: boolean): void
|
||||
}>()
|
||||
|
||||
const showPopup = computed({
|
||||
get() {
|
||||
return props.show
|
||||
},
|
||||
set(val) {
|
||||
if (!val) {
|
||||
orderAmount.value = null
|
||||
orderDesc.value = ''
|
||||
}
|
||||
emits('update:show', val)
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const orderAmount = ref<number | null>(null)
|
||||
const orderDesc = ref<string>('')
|
||||
|
||||
const confirm = async () => {
|
||||
const { id , type } = await apiOrderGap({
|
||||
order_id: props.order_id,
|
||||
order_amount: orderAmount.value,
|
||||
remark: orderDesc.value
|
||||
})
|
||||
router.navigateTo({
|
||||
url: '/bundle/pages/order_pay/order_pay',
|
||||
query: {
|
||||
order_id: id,
|
||||
from: type,
|
||||
order_amount: orderAmount.value
|
||||
}
|
||||
})
|
||||
showPopup.value = false
|
||||
}
|
||||
</script>
|
||||
269
uniapp/src/components/order-footer/index.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<view class="flex w-full justify-end">
|
||||
<!-- 取消订单 -->
|
||||
<view
|
||||
v-if="cancel_btn"
|
||||
class="ml-2"
|
||||
@click.stop="handleCommand('cancel')"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="default"
|
||||
>
|
||||
取消订单
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 去支付 -->
|
||||
<view
|
||||
v-if="pay_btn"
|
||||
class="ml-2"
|
||||
@click.stop="handlePayment"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="primary"
|
||||
>
|
||||
立即支付
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 我要加时 -->
|
||||
<view
|
||||
v-if="append_btn"
|
||||
class="ml-2"
|
||||
@click.stop="handleCommand('append')"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="default"
|
||||
>
|
||||
我要加时
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 补差价 -->
|
||||
<view
|
||||
v-if="gap_btn"
|
||||
class="ml-2"
|
||||
@click.stop="handleCommand('gap')"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="primary"
|
||||
>
|
||||
补差价
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 去评价 -->
|
||||
<view
|
||||
v-if="comment_btn"
|
||||
class="ml-2"
|
||||
@click.stop="router.navigateTo('/bundle/pages/evaluate_submit/index?order_goods_id=' + props.order_goods_id)"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="primary"
|
||||
:plain="true"
|
||||
>
|
||||
去评价
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 查看评价 -->
|
||||
<view
|
||||
v-if="look_comment_btn"
|
||||
class="ml-2"
|
||||
@click.stop="router.navigateTo('/bundle/pages/evaluate_detail/index?id='+props.order_goods_id)"
|
||||
>
|
||||
<u-button
|
||||
:size="size"
|
||||
type="default"
|
||||
>
|
||||
查看评价
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 核销订单 -->
|
||||
<!-- <template v-if="verification">-->
|
||||
<!-- <view class="flex justify-around">-->
|
||||
<!-- <button-->
|
||||
<!-- class="Btn flex-1 mr-2 bg-white text-sm text-black leading-[70rpx] h-[70rpx] rounded-full"-->
|
||||
<!-- @click.stop="showInputCode = true"-->
|
||||
<!-- >-->
|
||||
<!-- 手动核销码-->
|
||||
<!-- </button>-->
|
||||
|
||||
<!-- <button-->
|
||||
<!-- class="flex-1 bg-primary text-sm text-white leading-[70rpx] h-[70rpx] rounded-full"-->
|
||||
<!-- @click.stop="handleScanQRCode"-->
|
||||
<!-- >-->
|
||||
<!-- 扫码核销-->
|
||||
<!-- </button>-->
|
||||
<!-- </view>-->
|
||||
|
||||
<!-- <!– 手动输入核销码MODAL –>-->
|
||||
<!-- <u-modal-->
|
||||
<!-- ref="uModalInput"-->
|
||||
<!-- v-model="showInputCode"-->
|
||||
<!-- show-cancel-button-->
|
||||
<!-- confirmColor="#F36161"-->
|
||||
<!-- confirm-text="确定"-->
|
||||
<!-- @confirm="inputOrderCode"-->
|
||||
<!-- @cancel="handleCancel"-->
|
||||
<!-- title="手动核销"-->
|
||||
<!-- >-->
|
||||
<!-- <view class="slot-content justify-center" style="padding: 60rpx 40rpx">-->
|
||||
<!-- <u-input-->
|
||||
<!-- type="number"-->
|
||||
<!-- v-model="code"-->
|
||||
<!-- :border="true"-->
|
||||
<!-- placeholder="请输入核销码"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- />-->
|
||||
<!-- </view>-->
|
||||
<!-- </u-modal>-->
|
||||
<!-- </template>-->
|
||||
</view>
|
||||
<!-- <u-popup v-model="showConfirmPop" mode="center" height="500" border-radius="14">-->
|
||||
<!-- <view class="text-center w-[560rpx] pt-[40rpx]">-->
|
||||
<!-- <view class="w-[160rpx] h-[160rpx] px-[200rpx]">-->
|
||||
<!-- <u-image width="160" height="160" :src="goods_image"/>-->
|
||||
<!-- </view>-->
|
||||
<!-- <view class="mt-[20rpx] truncate">{{ goods_name }}</view>-->
|
||||
<!-- <view class="mt-[10rpx] mb-[40rpx] text-[#f36161]">完成服务请点击确认</view>-->
|
||||
<!-- <button-->
|
||||
<!-- class="flex-1 bg-primary text-sm text-white leading-[70rpx] h-[70rpx] rounded-full mx-[40rpx]"-->
|
||||
<!-- @click.stop="confirmVerification"-->
|
||||
<!-- >-->
|
||||
<!-- 确认核销-->
|
||||
<!-- </button>-->
|
||||
<!-- </view>-->
|
||||
<!-- </u-popup>-->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useRouter} from "uniapp-router-next";
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'command', value: any): void
|
||||
}>()
|
||||
|
||||
const props = defineProps<{
|
||||
order_id?: string | number // 订单ID
|
||||
cancel_btn: number // 取消订单
|
||||
comment_btn: number // 评价
|
||||
gap_btn: number // 补差价
|
||||
append_btn: number // 加时
|
||||
pay_btn?: number // 支付
|
||||
type?: number | null // 支付方式
|
||||
size: 'default' | 'medium' | 'mini'
|
||||
look_comment_btn?: number // 查看评价
|
||||
order_goods_id?: string | number // 订单商品ID
|
||||
}>()
|
||||
const router = useRouter()
|
||||
|
||||
// const code = ref<number | string>('') // 输入核销码
|
||||
// const showInputCode = ref<boolean>(false) // 显示(输入核销码):是 | 否
|
||||
|
||||
// 取消订单
|
||||
const handleCommand = async (command: string): Promise<void> => {
|
||||
try {
|
||||
emits('command', {
|
||||
order_id: props.order_id,
|
||||
command: command
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('错误信息:', error, props.order_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除订单
|
||||
// const handleOrderDel = async (id: number | string): Promise<void> => {
|
||||
// const modelRes = await uni.showModal({
|
||||
// title: '温馨提示',
|
||||
// content: '确认删除该订单吗?'
|
||||
// })
|
||||
// if (modelRes.cancel) return
|
||||
//
|
||||
// try {
|
||||
// await apiOrderDel({id: props.order_id})
|
||||
// emits('refresh')
|
||||
// // uni.navigateBack()
|
||||
// } catch (error) {
|
||||
// console.log('错误信息:', error, props.order_id)
|
||||
// }
|
||||
//
|
||||
// await emits('refresh')
|
||||
// }
|
||||
|
||||
// 支付
|
||||
const handlePayment = () => {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/pages/order_pay/order_pay?order_id=${props.order_id}&from=order`
|
||||
})
|
||||
}
|
||||
|
||||
// 确认服务
|
||||
// const handleConfirmService = async (id: number | string): Promise<void> => {
|
||||
// const modelRes = await uni.showModal({
|
||||
// title: '温馨提示',
|
||||
// content: '确认服务该订单吗?'
|
||||
// })
|
||||
// if (modelRes.cancel) return
|
||||
// await apiStaffOrderConfirmService({id: id})
|
||||
// uni.$u.toast('操作成功')
|
||||
// emits('refresh')
|
||||
// }
|
||||
|
||||
// 扫码核销
|
||||
// const handleScanQRCode = async () => {
|
||||
// // #ifdef H5
|
||||
// if (!isWeixinClient()) return toast('h5暂不支持扫码')
|
||||
// showInputCode.value = true
|
||||
// // #endif
|
||||
//
|
||||
// // #ifndef H5
|
||||
// try {
|
||||
// const data: any = await uni.scanCode({})
|
||||
// code.value = data.result
|
||||
// showConfirmPop.value = true
|
||||
// } catch (error) {
|
||||
// uni.$u.toast(error)
|
||||
// }
|
||||
// // #endif
|
||||
// }
|
||||
|
||||
// 确认核销
|
||||
// const showConfirmPop = ref(false)
|
||||
// const confirmVerification = async () => {
|
||||
// try {
|
||||
// await apiStaffOrderVerification({verification_code: code.value})
|
||||
// code.value = ''
|
||||
// uni.$u.toast('订单核销成功')
|
||||
// showConfirmPop.value = false
|
||||
// emits('refresh')
|
||||
// } catch (error) {
|
||||
// uni.$u.toast(error)
|
||||
// }
|
||||
// }
|
||||
// 手动输入核销码
|
||||
// const inputOrderCode = async (): Promise<void> => {
|
||||
// if (code.value === '') return toast('请输入核销码')
|
||||
// await apiStaffOrderVerification({verification_code: code.value})
|
||||
// uni.$u.toast('订单核销成功')
|
||||
// code.value = ''
|
||||
// emits('refresh')
|
||||
// }
|
||||
|
||||
// 取消核销码弹框
|
||||
// const handleCancel = () => {
|
||||
// code.value = ''
|
||||
// emits('refresh')
|
||||
// }
|
||||
</script>
|
||||
69
uniapp/src/components/page-status/page-status.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<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">
|
||||
export default {
|
||||
options: {
|
||||
virtualHost: true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<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>
|
||||
55
uniapp/src/components/payment/alipay.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<!-- 结果查询 -->
|
||||
<modal-popup
|
||||
v-model:show="showCheckPay"
|
||||
title="支付宝支付"
|
||||
:closeable="true"
|
||||
@close="emits('check')"
|
||||
>
|
||||
<template #content>
|
||||
<view
|
||||
class="p-20"
|
||||
style="width: 100%;padding: 15rpx 15rpx;margin: 50rpx 0;"
|
||||
>
|
||||
请复制链接,粘贴至浏览器并支付
|
||||
</view>
|
||||
</template>
|
||||
<template #footer>
|
||||
<view class="flex-1">
|
||||
<u-button
|
||||
type="primary"
|
||||
:custom-style="{
|
||||
'flex': 1
|
||||
}"
|
||||
@click="copy(alipayLink)"
|
||||
>
|
||||
复制链接
|
||||
</u-button>
|
||||
</view>
|
||||
</template>
|
||||
</modal-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed} from "vue";
|
||||
import {useCopy} from "@/hooks/useCopy";
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:show', value: boolean): void
|
||||
(e: 'check'): void
|
||||
}>()
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
alipayLink: string
|
||||
}>()
|
||||
const {copy} = useCopy()
|
||||
|
||||
const showCheckPay = computed({
|
||||
get() {
|
||||
return props.show
|
||||
},
|
||||
set(value) {
|
||||
emits('update:show', value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||