yshop意向点餐系统v1.2.2

This commit is contained in:
hupeng committed 2023-12-17 10:19:51 +08:00
1 parent d03982bf66
commit fc575265c3
4685 files changed
+518513 -182051

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
/unpackage
/dist
# misc
.DS_Store
.cache
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.tmp*
.svn
.tags
*.sublime-*
sftp-config.json
logs
*.log
.idea*
.yo-rc.json
*.swo
*.swp
/dist
/deps
yarn.lock
dev-stats.json
.vscode
.history
@@ -0,0 +1,27 @@
{
// launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
"version" : "0.0",
"configurations" : [
{
"app-plus" : {
"launchtype" : "local"
},
"default" : {
"launchtype" : "local"
},
"mp-weixin" : {
"launchtype" : "local"
},
"type" : "uniCloud"
},
{
"playground" : "standard",
"type" : "uni-app:app-ios"
},
{
"playground" : "custom",
"type" : "uni-app:app-android"
}
]
}
+72
View File
@@ -0,0 +1,72 @@
<script setup>
import { onHide,onLaunch,onShow } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store/store'
const main = useMainStore()
import { isWeixin,parseQuery } from '@/utils/util'
import cookie from '@/utils/cookie'
import {
userAuthSession,
wechatAuth
} from '@/api/auth'
import { APP_ID } from '@/config'
onLaunch(() => {
console.log('App Launch')
})
onShow(() => {
console.log('App Show')
// 检查用户登录情况
// #ifdef H5
if(isWeixin()){
// H5编译的代码
// 判断是否是微信浏览器
}
// #endif
// #ifdef MP-WEIXIN
wechatMiniLogin();
// #endif
})
onHide(() => {
console.log('App Hide')
})
const wechatMiniLogin = () => {
//this.$u.toast('登录中');
uni.login({
provider: 'weixin'
}).then(async (res) => {
let data = await userAuthSession({
code: res.code
});
if (data) {
main.SET_OPENID(data.openId)
if (data.hasOwnProperty('userInfo') && data.accessToken && data.accessToken != '') {
main.SET_MEMBER(data.userInfo);
main.SET_TOKEN(data.accessToken);
}
}
});
}
</script>
<style lang="scss">
@import '~@/static/style/app.scss';
//@import 'static/iconfont/iconfont.scss';
//@import url('./static/style/style.less');
@import 'static/style/yshop.css';
// /*每个页面公共css */
// page {
// background-color: #f5f5f5;
// }
</style>
+22
View File
@@ -0,0 +1,22 @@
import api from './api'
// 删除用户地址
export function addressDelete(data) {
return api.post(`/address/del/${data.id}`, undefined, { login: true })
}
// 设置默认地址
export function shopGetDistanceFromLocation(data) {
return api.post(`/address/getDistanceFromLocation`, data, { login: true })
}
// 添加或修改地址
export function getAddressAddAndEdit(data) {
return api.post(`/address/addAndEdit`, data, { login: true })
}
// 用户地址列表 l
export function addressAll(data) {
return api.get(`/address/list`, data, { login: true })
}
+166
View File
@@ -0,0 +1,166 @@
// #ifdef H5
// h5端
import Fly from 'flyio/dist/npm/fly'
// #endif
// #ifdef APP-PLUS
// app端
import Fly from 'flyio/dist/npm/wx'
// #endif
// #ifdef MP-WEIXIN
import Fly from 'flyio/dist/npm/wx'
// #endif
import { handleLoginFailure } from '@/utils'
import { isWeixin } from '@/utils/util'
import { VUE_APP_API_URL } from '@/config'
import cookie from '@/utils/cookie'
import { replace } from '@/utils/router'
const fly = new Fly()
fly.config.baseURL = VUE_APP_API_URL
fly.interceptors.response.use(
response => {
// console.log(response)
// 定时刷新access-token
return response
},
error => {
if (error.toString() == 'Error: Network Error') {
handleLoginFailure()
return Promise.reject({ msg: '未登录', toLogin: true })
}
if (error.status == 401) {
handleLoginFailure()
return Promise.reject({ msg: '未登录', toLogin: true })
}
if (error.response.data.status == 5109) {
uni.showToast({
title: error.response.data.msg,
icon: 'none',
duration: 2000,
})
}
return Promise.reject(error)
}
)
const defaultOpt = { login: true }
function baseRequest(options) {
const token = cookie.get('accessToken')
console.log('--> % token % token:\n', token)
options.headers = {
...options.headers,
}
// if (options.login === true) {
options.headers = {
...options.headers,
Authorization: 'Bearer ' + token,
}
// }
// 结构请求需要的参数
const { url, params, data, login, ...option } = options
// 发起请求
return fly
.request(url, params || data, {
...option,
})
.then(res => {
const data = res.data || {}
//console.log('res.status:',res)
// console.log('res.code:',res.code)
// #ifdef H5
if (res.data.code == 1004004002) {
if(isWeixin()){
const url = cookie.get('index_url')
console.log('redirect_uri:',url)
//const url = `${location.origin}/h5/#/pages/index/index`
location.href = url
return
}
}
// #endif
if (res.status !== 200) {
return Promise.reject({ msg: '请求失败', res, data })
}
if (data.code == 401) {
uni.hideLoading()
handleLoginFailure()
uni.showToast({
title: data.msg,
icon: 'none',
duration: 2000,
})
return Promise.reject({ msg: data.msg, res, data })
}
if (data.code != 0) {
uni.showToast({
title: data.msg,
icon: 'none',
duration: 2000,
})
return Promise.reject({ data, res })
}
return Promise.resolve(data.data, res)
// if ([401, 403].indexOf(data.status) !== -1) {
// handleLoginFailure()
// return Promise.reject({ msg: res.data.msg, res, data, toLogin: true })
// } else if (data.status === 200) {
// return Promise.resolve(data, res)
// } else if (data.status == 5101) {
// return Promise.reject({ msg: res.data.msg, res, data })
// } else {
// return Promise.reject({ msg: res.data.msg, res, data })
// }
})
}
/**
* http 请求基础类
* 参考文档 https://www.kancloud.cn/yunye/axios/234845
*
*/
const request = ['post', 'put', 'patch'].reduce((request, method) => {
/**
*
* @param url string 接口地址
* @param data object get参数
* @param options object axios 配置项
* @returns {AxiosPromise}
*/
request[method] = (url, data = {}, options = {}) => {
console.log(url, data)
return baseRequest(Object.assign({ url, data, method }, defaultOpt, options))
}
return request
}, {})
;['get', 'delete', 'head'].forEach(method => {
/**
*
* @param url string 接口地址
* @param params object get参数
* @param options object axios 配置项
* @returns {AxiosPromise}
*/
request[method] = (url, params = {}, options = {}) => {
return baseRequest(Object.assign({ url, params, method }, defaultOpt, options))
}
})
export default request
+37
View File
@@ -0,0 +1,37 @@
import api from './api'
/**
* 使用手机 + 验证码登录
*/
export function userLogin(data) {
return api.post('/member/auth/sms-login', data, { login: false })
}
/**
* 使用手机 + 验证码登录 member/auth/send-sms-code
*/
export function smsSend(data) {
return api.post('/member/auth/send-sms-code', data, { login: false })
}
/**
* 小程序 member/auth/auth-miniapp-login
*/
export function userLoginForWechatMini(data) {
return api.post('/member/auth/auth-miniapp-login', data, { login: false })
}
/**
* userAuthSession
*/
export function userAuthSession(data) {
return api.post('/member/auth/auth-session', data, { login: false })
}
/**
* wechatAuth
*/
export function wechatAuth(data) {
return api.get('/member/auth/auth-wechat-login', data, { login: false })
}
+31
View File
@@ -0,0 +1,31 @@
import api from './api'
/**
* couponReceive
*/
export function couponReceive(data) {
return api.post('/coupon/receive', data, { login: false })
}
/**
* couponMine
*/
export function couponMine(data) {
return api.get(`/coupon/my`, data, { login: false })
}
/**
* couponIndex let couponCount = (params = {}) => vm.$u.get('/coupon/count', params);
*/
export function couponIndexApi(data) {
return api.get(`/coupon/not`, data, { login: false })
}
/**
* couponCount
*/
export function couponCount(data) {
return api.get(`/coupon/count`, data, { login: false })
}
+15
View File
@@ -0,0 +1,15 @@
import api from './api'
/**
* 获得banner列表
*/
export function shopNearby(data) {
return api.get('/store/nearby', data, { login: false })
}
/**
* 获取首页信息
*/
export function menuGoods(data) {
return api.get('/product/products', data, { login: false })
}
+13
View File
@@ -0,0 +1,13 @@
import api from './api'
/**
* shopGetList
*/
export function shopGetList(data) {
return api.get('/store/list', data, { login: false })
}
export function menuAds(data) {
return api.get('/ad/list', data, { login: false })
}
+63
View File
@@ -0,0 +1,63 @@
import api from './api'
/**
* 订单列表
*/
export function orderTakeFoods(data) {
return api.get('/order/list', data, { login: false })
}
/**
* 订单创建
*/
export function orderSubmit(data) {
return api.post(`/order/create`, data, { login: false })
}
/**
* 订单列表
*/
export function orderGetOrders(data) {
return api.get(`/order/list`, data, { login: false })
}
/**
* 计算详情
*/
export function orderDetail(data) {
return api.get(`/order/detail/${data}`, data, { login: false })
}
/**
* 订单收货
*/
export function orderReceive(data) {
return api.post(`/order/take`, data, { login: false })
}
/**
* 订单退款
*/
export function orderRefund(data) {
return api.post(`/order/refund`, data, { login: false })
}
/**
* 订单支付
*/
export function payUnify(data) {
return api.post(`/order/pay`, data, { login: false })
}
/**
* getWechatConfig
*/
export function getWechatConfig() {
return api.get(`/member/wx-mp/create-jsapi-signature`, { url: location.href }, { login: false })
}
+55
View File
@@ -0,0 +1,55 @@
import api from './api'
/**
* 基本信息
*/
export function userGetUserInfo(data) {
return api.get('/member/user/get-info', data, { login: true })
}
/**
* 获取菜单
*/
export function mineService(data) {
return api.get('/service/list', data, { login: true })
}
/**
* 获取内容
*/
export function mineServiceContent(data) {
return api.get('/service/content', data, { login: true })
}
/**
* save
*/
export function userEdit(data) {
return api.post('/member/user/update-nickname', data, { login: true })
}
/**
* balanceGetBillList
*/
export function balanceGetBillList(data) {
return api.get('/member/user/getBill', data, { login: true })
}
/**
* 充值列表
*/
export function balanceGetMoneyList(data) {
return api.get('/recharge/getMoneyList', data, { login: true })
}
/**
* 充值
*/
export function balanceRecharge(data) {
return api.post('/member/user/recharge', data, { login: true })
}
@@ -0,0 +1,15 @@
<template>
<view
class="blank"
:style="{ height: size + 'px' }"
></view>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps(['size'])
const size = ref(props.size)
</script>
<style lang="less">
</style>
@@ -0,0 +1,25 @@
<template>
<view
:class="['card', props.class]"
:style="{ width: width ? width + 'rpx' : '100%' }"
>
<slot></slot>
</view>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps(['class', 'width'])
const className = ref(props.class)
const width = ref(props.width)
</script>
<style lang="less">
.card {
width: 100%;
background-color: #fff;
border-radius: 7.5rpx;
box-sizing: border-box;
overflow: hidden;
}
</style>
@@ -0,0 +1,397 @@
<template>
<view>
<text
class="uni-input"
@tap="open"
>{{ value }}</text>
<uv-popup
ref="popup"
mode="bottom"
>
<view class="cityselect">
<view class="cityselect-header">
<view class="cityselect-title">
<text>请选择地址</text>
</view>
<view class="cityselect-nav">
<view
class="item"
v-if="provinceActive"
@tap="changeNav(0)"
>
<text>{{ provinceActive.name }}</text>
</view>
<view
class="item"
v-if="cityActive"
@tap="changeNav(1)"
>
<text>{{ cityActive.name }}</text>
</view>
<view
class="item"
v-if="districtActive"
@tap="changeNav(2)"
>
<text>{{ districtActive.name }}</text>
</view>
<view
class="item active"
v-else
>
<text>请选择</text>
</view>
</view>
</view>
<view class="cityselect-content">
<swiper
class="swiper"
disable-touch="true"
touchable="false"
:current="current"
>
<swiper-item>
<scroll-view
scroll-y
class="cityScroll"
>
<view>
<view
class="cityselect-item"
v-for="(item, index) in province"
:key="index"
@tap="selectProvince(item, index)"
>
<view
class="cityselect-item-box"
:class="{ active: isProvinceActive(item) }"
>
<text>{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view
scroll-y
class="cityScroll"
>
<view>
<view
class="cityselect-item"
v-for="(item, index) in city"
:key="index"
@tap="selectCity(item, index)"
>
<view
class="cityselect-item-box"
:class="{ active: isCityActive(item) }"
>
<text>{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view
scroll-y
class="cityScroll"
>
<view>
<view
class="cityselect-item"
v-for="(item, index) in district"
:key="index"
@tap="selectDistrict(item, index)"
>
<view
class="cityselect-item-box"
:class="{ active: isDistrictActive(item) }"
>
<text>{{ item.name }}</text>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</view>
</uv-popup>
</view>
</template>
<script setup>
import { ref, watch,onMounted } from "vue"
const props = defineProps(['items', 'defaultValue'])
const emit = defineEmits(['callback'])
const items = ref(props.items || [])
// const defaultValue = ref(props.defaultValue)
// console.log("--> % defaultValue:\n", defaultValue)
const value = ref(props.value || '请选择')
const province = ref(props.items)
const provinceActive = ref(null)
const city = ref([])
const cityActive = ref(null)
const district = ref([])
const districtActive = ref(null)
const current = ref(0)
const popup = ref(null)
watch(() => props.items, (next) => {
province.value = next
})
watch(() => props.defaultValue, (next) => {
console.log("--> % defaultValue % next:\n", next)
value.value = `${next.province.name} ${next.city.name} ${next.district.name}`
setDefaultValue(items.value, next)
})
onMounted(() => {
// setDefaultValue(items, props.defaultValue)
})
const isProvinceActive = (item) => {
return provinceActive.value && item.value == provinceActive.value.value
}
const isCityActive = (item) => {
return cityActive.value && item.value == cityActive.value.value
}
const isDistrictActive = (item) => {
return districtActive.value && item.value == districtActive.value.value
}
const setDefaultValue = (items, value) => {
if (!items || !value) {
return
}
province.value = items
items.map(prov => {
console.log("--> % setDefaultValue % prov:\n", prov)
if (prov.name == value.province.name) {
city.value = prov.id
provinceActive.value = {
value: prov.id,
name: value.province.name,
}
prov.children.map(city => {
if (city.name == value.city.name) {
district.value = city.children
cityActive.value = {
value: city.id,
name: value.city.name,
}
city.children.map(district => {
if (district.name == value.district.name) {
districtActive.value = {
value: city.id,
name: value.district.name,
}
}
})
}
})
}
})
console.log(provinceActive.value, cityActive.value, districtActive.value)
}
const open = () => {
province.value = props.items
provinceActive.value = null
cityActive.value = null
districtActive.value = null
city.value = []
district.value = []
current.value = 0
popup.value.open()
setDefaultValue(items.value, props.defaultValue.value)
}
const changeNav = (index) => {
if (index == 0) {
provinceActive.value = null
}
if (index == 1) {
cityActive.value = null
districtActive.value = null
}
if (index == 2) {
districtActive.value = null
}
current.value = index
}
const selectProvince = (selectItem, index) => {
provinceActive.value = selectItem
city.value = selectItem.children
current.value = 1
}
const selectCity = (selectItem, index) => {
cityActive.value = selectItem
district.value = selectItem.children
current.value = 2
}
const selectDistrict = (selectItem, index) => {
console.log("--> % selectDistrict % selectItem:\n", selectItem)
districtActive.value = selectItem
value.value = `${provinceActive.value?.name} ${cityActive.value?.name} ${districtActive.value?.name}`
console.log({
province: {
id: provinceActive.value?.id,
name: provinceActive.value?.name,
},
city: {
id: cityActive.value?.id,
name: cityActive.value?.name,
},
district: {
id: districtActive.value?.id,
name: districtActive.value?.name,
},
})
emit('callback', {
province: {
id: provinceActive.value?.id,
name: provinceActive.value?.name,
},
city: {
id: cityActive.value?.id,
name: cityActive.value?.name,
},
district: {
id: districtActive.value?.id,
name: districtActive.value?.name,
},
})
popup.value.close()
}
</script>
<style lang="less">
.cityselect {
width: 100%;
height: 75%;
background-color: #fff;
z-index: 1502;
position: relative;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.cityScroll {
height: 100%;
}
.swiper {
height: 800rpx;
}
}
.cityselect-header {
width: 100%;
z-index: 1;
}
.cityselect-title {
width: 100%;
font-size: 30rpx;
text-align: center;
height: 95rpx;
line-height: 95rpx;
position: relative;
&:cityselect-title:after {
height: 1px;
position: absolute;
z-index: 0;
bottom: 0;
left: 0;
content: '';
width: 100%;
background-image: linear-gradient(0deg, #ececec 50%, transparent 0);
}
}
.cityselect-nav {
width: 100%;
padding-left: 20rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: flex-start;
.item {
font-size: 26rpx;
color: #222;
display: block;
height: 80rpx;
line-height: 92rpx;
padding: 0 16rpx;
position: relative;
margin-right: 30rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 40%;
&.active {
color: #f23030 !important;
border-bottom: 1rpx solid #f23030;
}
}
}
.cityselect-content {
height: 100%;
width: 100%;
}
.cityselect-item {
.cityselect-item-box {
display: block;
padding: 0 40rpx;
position: relative;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
text-overflow: ellipsis;
line-height: 64rpx;
max-height: 65rpx;
font-size: 26rpx;
color: #333;
&.active {
color: #f23030 !important;
}
&:after {
content: '';
height: 1rpx;
position: absolute;
z-index: 0;
bottom: 0;
left: 0;
width: 100%;
background-image: linear-gradient(0deg, #ececec 50%, transparent 0);
}
}
}
</style>
@@ -0,0 +1,22 @@
<template>
<view :class="['container', min ? 'container-min' : '']">
<slot></slot>
</view>
</template>
<script setup>
import { ref, } from 'vue';
const props = defineProps(['min'])
const min = ref(props?.min != undefined)
</script>
<style lang="less">
.container {
padding: 0 34rpx;
&-min {
padding: 0 10rpx;
}
}
</style>
@@ -0,0 +1,16 @@
<template>
<view class="layout">
<slot />
</view>
</template>
<script setup>
import { ref, defineProps, reactive } from 'vue';
const props = defineProps(['size'])
</script>
<style lang="less">
.layout {
min-height: 100%;
}
</style>
@@ -0,0 +1,131 @@
<template>
<view
class="tui-cell-class tui-list-cell"
:class="{ 'tui-cell-arrow': arrow, 'tui-cell-last': last, 'tui-line-left': lineLeft, 'tui-line-right': lineRight, 'tui-radius': radius }"
:hover-class="hover ? 'tui-cell-hover' : ''"
:style="{ background: bgcolor, fontSize: size + 'rpx', color: color, padding: padding}"
:hover-stay-time="150"
@tap="handleClick"
>
<slot></slot>
<image src="/static/images/navigator-1.png" class="arrow" v-if="arrow"></image>
<!-- <view class="iconfont iconarrow-right arrow" v-if="arrow"></view> -->
</view>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps ({
//是否有箭头
arrow: {
type: Boolean,
default: false
},
//是否有点击效果
hover: {
type: Boolean,
default: true
},
//left 30rpx
lineLeft:{
type: Boolean,
default: false
},
//right 30rpx
lineRight:{
type: Boolean,
default: false
},
padding:{
type:String,
default:"26rpx 30rpx"
},
last: {
type: Boolean,
default: false //最后一条数据隐藏线条
},
radius:{
type:Boolean,
default:false
},
bgcolor: {
type: String,
default: "#fff" //背景颜色
},
size: {
type: Number,
default: 28 //字体大小
},
color: {
type: String,
default: "#5A5B5C" //字体颜色
},
index: {
type: Number,
default: 0
}
})
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click', {
index: props.index
});
}
</script>
<style lang="scss" scoped>
.tui-list-cell {
position: relative;
width: 100%;
box-sizing: border-box;
overflow: hidden;
display: flex;
align-items: center;
}
.tui-radius {
border-radius: 12rpx;
overflow: hidden;
}
.tui-cell-hover {
background: #f7f7f9 !important;
}
.tui-list-cell::after {
content: '';
position: absolute;
border-bottom: 2rpx solid #eee;
-webkit-transform: scaleY(0.8);
transform: scaleY(0.8);
bottom: 0;
right: 0;
left: 0;
}
.tui-line-left::after {
left: 30rpx !important;
}
.tui-line-right::after {
right: 30rpx !important;
}
.tui-cell-last::after {
border-bottom: 0 !important;
}
// .arrow {
// font-size: 44rpx;
// line-height: 100%;
// color: $text-color-grey;
// position: relative;
// margin-right: -12rpx;
// }
.arrow {
width: 50rpx;
height: 50rpx;
position: relative;
margin-right: -10rpx;
flex-shrink: 0;
}
</style>
@@ -0,0 +1,20 @@
<template>
<view>
</view>
</template>
<script>
export default {
name:"logo",
data() {
return {
};
}
}
</script>
<style lang="less">
</style>
@@ -0,0 +1,347 @@
<template>
<view @touchmove.stop.prevent>
<view class="modal-box" :style="{width:props.width,padding:props.padding,borderRadius:props.radius}" :class="[(fadein || props.show)?'modal-normal':'modal-scale',props.show?'modal-show':'']">
<view v-if="props.custom">
<slot></slot>
</view>
<view v-else>
<view class="modal-title" v-if="props.title">{{props.title}}</view>
<view class="modal-content" :class="[props.title?'':'mtop']" :style="{color:props.color,fontSize:props.size+'rpx'}">
<slot></slot>
</view>
<view class="modalBtn-box" :class="[props.button.length!=2?'flex-column':'']">
<block v-for="(item,index) in props.button" :key="index">
<button class="modal-btn"
:class="[
''+(item.type || 'primary')+(item.plain?'-outline':''),
props.button.length!=2?'btn-width':'',
props.button.length>2?'mbtm':'',
props.shape=='circle'?'circle-btn':'',
'btn-' + (item.size || 'default'),
]"
:hover-class="''+(item.plain?'outline':(item.type || 'primary'))+'-hover'" :data-index="index" @tap="handleClick">{{item.text || "确定"}}</button>
</block>
</view>
</view>
</view>
<view class="modal-mask" :class="[props.show?'mask-show':'']" @tap="handleClickCancel"></view>
</view>
</template>
<script setup>
import { ref } from 'vue'
//const props = defineProps(['show','custom','width','padding','radius','title','content','color','size','shape','button','maskClosable','fadein'])
const props = defineProps ({
show: {type: Boolean,default: false},
custom: {type: Boolean,default: false},
width: {type: String,default: "84%"},
padding: {type: String,default: "40rpx"},
radius: {type: String,default: "24rpx"},
title: {type: String,default: ""},
content: {type: String,default: ""},
color: {type: String,default: "#999"},
size: {type: Number,default: 28},
shape: {type: String,default: 'square'}, //形状 circle, square
button: {type: Array,
default: function() {
return [{
text: "取消",
type: "red",
plain: true //是否空心
}, {
text: "确定",
type: "red",
plain: false
}]
}},
maskClosable: {type: Boolean,default: true},
custom: {type: Boolean,default: false},
fadein: {type: Boolean,default: false} //淡入效果,自定义弹框插入input输入框时传true
})
// const show = ref(props.show)
// const maskClosable = ref(props.maskClosable)
const emit = defineEmits(['click','cancel','test'])
const handleClick = (e) => {
if (!props.show) return;
const dataset = e.currentTarget.dataset;
emit('click', {
index: Number(dataset.index)
});
}
const handleClickCancel = () => {
if (!props.maskClosable) return;
emit('cancel');
}
</script>
<style lang="scss">
.modal-box {
position: fixed;
left: 50%;
top: 50%;
margin: auto;
background: #fff;
z-index: 201;
transition: all 0.3s ease-in-out;
opacity: 0;
box-sizing: border-box;
visibility: hidden;
}
.modal-scale {
transform: translate(-50%, -50%) scale(0);
}
.modal-normal {
transform: translate(-50%, -50%) scale(1);
}
.modal-show {
opacity: 1;
visibility: visible;
}
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 98;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.mask-show {
visibility: visible;
opacity: 1;
}
.modal-title {
text-align: center;
font-size: 34rpx;
color: #333;
padding-top: 20rpx;
font-weight: bold;
}
.modal-content {
color: #999;
font-size: 28rpx;
padding-top: 20rpx;
padding-bottom: 60rpx;
}
.mtop {
margin-top: 30rpx;
}
.mbtm {
margin-bottom: 30rpx;
}
.modalBtn-box {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between
}
.flex-column {
flex-direction: column;
}
.modal-btn {
width: 46%;
height: 68rpx;
line-height: 68rpx;
position: relative;
border-radius: 60rpx;
font-size: 28rpx;
overflow: visible;
margin-left: 0;
margin-right: 0;
&.btn-default {
font-size: 28rpx;
}
&.btn-lg {
font-size: 32rpx;
}
&.btn-sm {
font-size: 24rpx;
}
}
.modal-btn::after {
content: "";
position: absolute;
width: 200%;
height: 200%;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
left: 0;
top: 0;
border-radius: 60rpx;
}
.btn-width {
width: 80% !important;
}
.primary {
background: #97AF13;
color: #fff;
}
.primary-hover {
background: #97AF13;
color: #e5e5e5;
}
.primary-outline {
color: #97AF13;
background: none;
}
.primary-outline::after {
border: 1px solid #97AF13;
}
.danger {
background: #ed3f14;
color: #fff;
}
.danger-hover {
background: #d53912;
color: #e5e5e5;
}
.danger-outline {
color: #ed3f14;
background: none;
}
.danger-outline::after {
border: 1px solid #ed3f14;
}
.red {
background: #e41f19;
color: #fff;
}
.red-hover {
background: #c51a15;
color: #e5e5e5;
}
.red-outline {
color: #e41f19;
background: none;
}
.red-outline::after {
border: 1px solid #e41f19;
}
.warning {
background: #ff7900;
color: #fff;
}
.warning-hover {
background: #e56d00;
color: #e5e5e5;
}
.warning-outline {
color: #ff7900;
background: none;
}
.warning-outline::after {
border: 1px solid #ff7900;
}
.green {
background: #19be6b;
color: #fff;
}
.green-hover {
background: #16ab60;
color: #e5e5e5;
}
.green-outline {
color: #19be6b;
background: none;
}
.green-outline::after {
border: 1px solid #19be6b;
}
.white {
background: #fff;
color: #333;
}
.white-hover {
background: #f7f7f9;
color: #666;
}
.white-outline {
color: #333;
background: none;
}
.white-outline::after {
border: 1px solid #333;
}
.gray {
background: #ededed;
color: #999;
}
.gray-hover {
background: #d5d5d5;
color: #898989;
}
.gray-outline {
color: #999;
background: none;
}
.gray-outline::after {
border: 1px solid #999;
}
.outline-hover {
opacity: 0.6;
}
.circle-btn {
border-radius: 40rpx !important;
}
.circle-btn::after {
border-radius: 80rpx !important;
}
</style>
@@ -0,0 +1,220 @@
<template>
<view
:class="classObject"
:style="[getStyle]"
>
<slot></slot>
</view>
</template>
<script setup>
import { ref, watch, onMounted } from "vue"
const props = defineProps([
'direction',
'align',
'wrap',
'justify',
'size',
'height',
'padding',
'flex',
'border'
])
console.log("--> % props:\n", props)
const classObject = ref({})
const handleClassObject = (props) => {
console.log("--> % handleClassObject % props:\n", props.value)
let className = 'yshop-space'
let direction = props.direction
let align = props.align
let wrap = props.wrap
let justify = props.justify
let flex = props.flex
let border = props.border
if (border) {
className += ' yshop-space-border'
}
if (direction) {
className += ` yshop-space-${direction}`
}
if (wrap) {
className += ` yshop-space-${wrap}`
}
if (align) {
className += ` yshop-space-align-${align}`
}
if (justify) {
className += ` yshop-space-justify-${justify}`
}
console.log("--> % handleClassObject % className:\n", className)
classObject.value = className
}
const getStyle = ({
size = 6,
wrap,
height,
padding,
flex
}) => {
let innerStyle = {}
// let size = size.value
// let wrap = wrap.value
// let height = height.value
// let padding = padding.value
if (height) {
innerStyle.height = `${height}rpx`
}
if (typeof size === 'number') {
innerStyle.gap = size + 'px'
}
if (wrap) {
innerStyle.flexWrap = 'wrap'
}
if (typeof size === 'string') {
switch (size) {
case 'small':
innerStyle.gap = '8rpx'
break
case 'middle':
innerStyle.gap = '16rpx'
break
case 'large':
innerStyle.gap = '24rpx'
break
}
}
if (typeof padding === 'string') {
innerStyle.padding = `${padding}rpx`
}
if (Object.prototype.toString.call(padding) === '[object Array]') {
if (typeof padding === 'object') {
if (padding.length == 1) {
innerStyle.padding = `${padding[0]}rpx`
}
if (padding.length == 2) {
innerStyle.padding = `${padding[0]}rpx ${padding[1]}rpx`
}
}
}
if (Object.prototype.toString.call(size) === '[object Array]') {
if (typeof size === 'object') {
if (size.length == 1) {
innerStyle.gap = `${size[0]}rpx`
}
if (size.length == 2) {
innerStyle.gap = `${size[0]}rpx ${size[1]}rpx`
}
}
}
if (flex) {
innerStyle.flex = flex
}
return innerStyle
}
onMounted(() => {
handleClassObject(props)
getStyle(props)
})
</script>
<style lang="less">
space {
width: 100%;
}
.yshop-space {
display: flex;
width: 100%;
flex: 1;
&-vertical {
flex-direction: column;
}
&-align-center {
align-items: center;
}
&-align-start {
align-items: flex-start;
}
&-align-end {
align-items: flex-end;
}
&-justify-center {
justify-content: center;
}
&-justify-between {
justify-content: space-between;
}
&-justify-around {
justify-content: space-around;
}
&-align-end {
align-items: flex-end;
}
&-align-baseline {
align-items: baseline;
}
&-item:empty {
display: none;
}
&-border {
border-bottom: 1rpx solid #f9f9f9;
}
}
// #ifdef APP-PLUS
.yshop-space {
&>view {
margin-right: 8rpx;
}
&>text {
margin-right: 8rpx;
}
&>image {
margin-right: 8rpx;
}
.u-tag-wrapper {
margin-right: 8rpx;
}
.u-tag {
margin-right: 8rpx;
}
}
// #endif
</style>
@@ -0,0 +1,125 @@
<template>
<uv-upload
:fileList="list"
name="1"
multiple
:maxCount="10"
@afterRead="afterRead"
@delete="handleDeletePic"
></uv-upload>
</template>
<script setup>
import { VUE_APP_UPLOAD_URL } from '@/config';
import { ref } from 'vue';
const props = defineProps(['modelValue'])
console.log("--> % modelValue:\n", props.modelValue)
const emit = defineEmits(['update:modelValue'])
const list = ref(props.modelValue)
const handleDeletePic = (event) => {
list.value.splice(event.index, 1)
emit('update:modelValue', list.value)
}
const afterRead = async (event) => {
// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
let lists = [].concat(event.file)
let fileListLen = list.value.length
lists.map((item) => {
list.value.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await uploadFilePromise(lists[i].url)
console.log("gxs --> % afterRead % result:\n", result)
let item = list.value[fileListLen]
list.value.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: result
}))
fileListLen++
}
emit('update:modelValue', list.value)
}
const uploadFilePromise = (url) => {
return new Promise((resolve, reject) => {
let a = uni.uploadFile({
url: VUE_APP_UPLOAD_URL, // 仅为示例,非真实的接口地址
filePath: url,
name: 'file',
formData: {
user: 'test'
},
success: (res) => {
console.log("gxs --> % returnnewPromise % res:\n", res)
setTimeout(() => {
resolve(res.data.data)
}, 10)
}
});
})
}
</script>
<style lang="less">
.activity {
&-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
&-info {
display: flex;
align-items: flex-end;
}
&-title {
line-height: 45rpx;
font-size: 32rpx;
color: #333333;
}
&-subtitle {
margin-left: 10rpx;
line-height: 33rpx;
font-size: 24rpx;
color: #EE6D46;
}
&-more {
display: flex;
align-items: center;
&-info {
line-height: 33rpx;
font-size: 24rpx;
color: #999999;
}
.image {
margin-left: 10rpx;
display: block;
width: 20rpx;
height: 20rpx;
}
}
&-body {}
}
}
</style>
@@ -0,0 +1,452 @@
<template>
<uv-button
round
size="mini"
block
type="primary"
@click="startCountdown"
>
{{ buttonText }}
</uv-button>
</template>
<script setup>
import {
ref,
computed,
onUnmounted
} from 'vue'
import {
sendSmsCode
} from '@/api/auth'
const props = defineProps(['mobile', 'scene'])
const countingDown = ref(false); // 是否正在倒计时
const countdownSeconds = ref(60); // 倒计时的总秒数
const timer = ref(null); // 倒计时的总秒数
const buttonText = computed(() => {
return countingDown.value ? `${countdownSeconds.value} 秒` : '发送验证码';
});
const startCountdown = () => {
if (!countingDown.value) {
countingDown.value = true;
handleSendSmsCode()
}
};
const handleSendSmsCode = () => {
uni.showLoading({
title: '发送验证码中'
});
console.log("gxs --> % handleSendSmsCode % props.mobile:\n", props.mobile)
sendSmsCode({
"mobile": props.mobile,
"scene": props.scene
}).then(res => {
uni.hideLoading();
uni.showToast({
icon: 'none',
title: '验证码已发送',
duration: 2000
});
timer.value = setInterval(() => {
countdownSeconds.value--;
console.log("gxs --> % timer % countdownSeconds.value:\n", countdownSeconds.value)
if (countdownSeconds.value <= 0) {
clearInterval(timer.value);
countdownSeconds.value = 60; // 倒计时结束后重置为初始值
countingDown.value = false;
}
}, 1000);
}).catch(error => {
countingDown.value = false;
})
}
onUnmounted(() => {
clearInterval(timer.value);
})
</script>
<style lang="less">
.goods {
position: relative;
padding: 30rpx 0;
&-card {
display: flex;
flex-direction: column;
.goods {
&-content {
padding: 0 20rpx;
display: flex;
flex-direction: column;
}
&-info {
margin-top: 15rpx;
}
&-thumb {
margin-bottom: 15rpx;
width: 100%;
height: 203rpx;
&-img {
width: 100%;
height: 100%;
display: block;
}
}
}
}
&-header {}
&-thumb {
background: #FAFAFA;
}
&-content {}
&-storeName {
line-height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
&-price {
&-row {
display: flex;
align-items: center;
.goods-price {}
}
&-primary {
line-height: 42rpx;
font-size: 30rpx;
font-weight: 500;
color: #EE6D46;
}
&-default {
line-height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
&-original {
margin-left: 9rpx;
line-height: 28rpx;
font-size: 20rpx;
color: #999999;
text-decoration: line-through;
}
}
&-desc {
line-height: 33rpx;
font-size: 24rpx;
color: #999999;
}
&-info {
display: flex;
align-items: flex-end;
justify-content: space-between;
&-left {}
&-action {
&-btn {}
&-desc {
line-height: 28rpx;
font-size: 20rpx;
color: #999999;
}
}
}
&-image {
&-img {}
}
&-list {
display: flex;
flex-direction: row;
padding: 14rpx;
box-sizing: border-box;
width: 100%;
.goods {
&-thumb {
margin-bottom: 0;
width: 220rpx;
height: 220rpx;
&-img {
width: 100%;
height: 100%;
display: block;
}
}
&-content {
padding-right: 40rpx;
margin-left: 30rpx;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
&-model {
display: flex;
margin-bottom: 28rpx;
&-border {
display: flex;
align-items: center;
height: 40rpx;
border: 1px solid #CCCCCC;
opacity: 1;
border-radius: 0rpx;
padding: 0 10rpx;
}
&-info {}
&-label {
line-height: 38rpx;
font-size: 24rpx;
color: #999999;
margin-right: 10rpx;
}
&-value {
line-height: 38rpx;
font-size: 24rpx;
color: #333333;
margin-right: 10rpx;
}
&-action {
width: 11rpx;
height: 7rpx;
}
}
}
&-min {
.goods {
&-thumb {
margin-bottom: 0;
width: 150rpx;
height: 150rpx;
&-img {
width: 100%;
height: 100%;
display: block;
}
}
}
}
// .goods {
// padding: 16rpx 14rpx;
// &-header {
// display: flex;
// align-items: flex-start;
// }
// &-thumb {
// width: 220rpx;
// height: 220rpx;
// &-img {
// width: 100%;
// height: 100%;
// display: block;
// }
// }
// &-content {
// margin-top: 24rpx;
// margin-left: 40rpx;
// flex: 1
// }
// &-storeName {
// line-height: 40rpx;
// font-size: 28rpx;
// font-weight: 500;
// color: #333333;
// margin-bottom: 35rpx;
// }
// &-info {
// display: flex;
// align-items: center;
// justify-content: space-between;
// &-left {
// display: flex;
// align-items: flex-end;
// }
// &-action {
// &-btn {}
// &-desc {
// color: #999999;
// font-size: 24rpx;
// line-height: 40rpx;
// }
// }
// }
// &-price {
// &-default {
// line-height: 28rpx;
// font-size: 20rpx;
// color: #999999;
// }
// &-primary {
// line-height: 42rpx;
// font-size: 30rpx;
// font-weight: 500;
// color: #EE6D46;
// margin-left: 5rpx;
// }
// }
// &-desc {
// color: #999999;
// font-size: 24rpx;
// line-height: 40rpx;
// }
&-model {
display: inline-flex;
align-items: center;
width: auto;
height: 40rpx;
border: 1px solid #CCCCCC;
opacity: 1;
border-radius: 0rpx;
padding: 0 10rpx;
margin-bottom: 28rpx;
&-label {
line-height: 38rpx;
font-size: 24rpx;
color: #999999;
}
&-value {
line-height: 38rpx;
font-size: 24rpx;
color: #333333;
margin-right: 10rpx;
}
&-action {
width: 11rpx;
height: 7rpx;
}
}
// &-model-info {
// display: inline-flex;
// align-items: center;
// width: auto;
// height: 40rpx;
// opacity: 1;
// border-radius: 0rpx;
// margin-bottom: 28rpx;
// &-label {
// line-height: 38rpx;
// font-size: 24rpx;
// color: #999999;
// }
// &-value {
// line-height: 38rpx;
// font-size: 24rpx;
// color: #333333;
// margin-right: 10rpx;
// }
// &-action {
// width: 11rpx;
// height: 7rpx;
// }
// }
// }
}
.buy-progress {
display: flex;
align-items: center;
justify-content: space-between;
&-info {
flex: 1;
&-desc {
color: #999999;
font-size: 24rpx;
line-height: 32rpx;
}
}
&-action {
margin-left: 17rpx;
}
}
.buy-num {
&-info-desc {
color: #999999;
font-size: 24rpx;
line-height: 32rpx;
}
}
</style>
+48
View File
@@ -0,0 +1,48 @@
export const VUE_APP_API_URL = 'http://localhost:48081/app-api'
//export const VUE_APP_API_URL = 'https://apidc.yixiang.co/app-api'
export const VUE_APP_RESOURCES_URL = 'https://h5.yixiang.co/static'
export const VUE_APP_UPLOAD_URL = VUE_APP_API_URL + '/infra/file/upload'
export const APP_ID = 'wxc061dee8806ff712'
const orderListStatus = {}
// -1:申请退款
// -2:退货成功
// 0:待发货;
// 1:待收货;
// 2:已收货;
// 3:待评价;
// -1:已退款
export const orderStatus = {
0: '未支付',
1: '待发货',
2: '待收货',
3: '待评价',
4: '已完成',
5: '退款中',
6: '已退款',
7: '退款',
}
export const orderReStatus = {
0: '等待买家付款',
// 1: '等待卖家发货',
1: '卖家已发货',
2: '等待买家待评价',
3: '订单已完成',
4: '订单退款中',
5: '订单已退款',
6: '退款已完成',
}
// export const orderReStatus = {
// 0: '等待买家付款',
// 1: '等待卖家发货',
// 2: '卖家已发货',
// 3: '等待买家待评价',
// 4: '订单已完成',
// 5: '订单退款中',
// 6: '订单已退款',
// 7: '退款已完成',
// }
+2
View File
@@ -0,0 +1,2 @@
export * from './usePage'
export * from './useGlobalProperties'
@@ -0,0 +1,8 @@
// mouse.js
import { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
import { onReady, onReachBottom } from '@dcloudio/uni-app'
export const useGlobalProperties = () => {
const instance = getCurrentInstance()
return instance.appContext.app.config.globalProperties
}
+88
View File
@@ -0,0 +1,88 @@
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
import { onReady, onReachBottom } from '@dcloudio/uni-app'
export const usePage = getPage => {
// 页码,默认为1
const page = ref(1)
// 页大小,默认为10
const limit = ref(10)
// 关键字
const keyword = ref('')
// 类别
const type = ref('')
// 分类ID
const sid = ref('')
// 是否新品,不为空的字符串即可
const news = ref('')
// 是否积分兑换商品
const isIntegral = ref('')
// 到底了
const loadend = ref(false)
// 加载中
const loading = ref(false)
const dataList = ref([])
const handleGetDataList = async () => {
console.log('--> % handleGetDataList % loading:\n', loading.value)
console.log('--> % handleGetDataList % loadend:\n', loadend.value)
if (loading.value || loadend.value) return
loading.value = true
const products = await getPage({
page: page.value,
limit: limit.value,
keyword: keyword.value,
type: type.value,
sid: sid.value,
news: news.value,
isIntegral: isIntegral.value,
})
console.log('--> % handleGetDataList % products:\n', products)
if (products) {
if (products.length <= 0) {
loadend.value = true
}
dataList.value = dataList.value.concat(products)
}
loading.value = false
}
const handleRefresh = () => {
loadend.value = false
loading.value = false
dataList.value = []
handleGetDataList()
}
onReady(() => {
console.log('onReady')
// handleGetDataList()
})
onReachBottom(() => {
if (loading.value) return
page.value += 1
})
// 通过返回值暴露所管理的状态
return {
type,
dataList,
page,
limit,
keyword,
loading,
loadend,
refresh: handleRefresh,
}
}
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["./*"],
"@/api": ["./api"],
"@/utils": ["./utils"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["/**/*"]
}
+22
View File
@@ -0,0 +1,22 @@
/*
* @Author: Gaoxs
* @Date: 2023-04-07 15:12:06
* @LastEditors: Gaoxs
* @Description:
*/
import util from '@/utils'
import App from './App'
import { createPinia } from 'pinia'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(util)
app.use(createPinia())
return {
app,
}
}
+437
View File
@@ -0,0 +1,437 @@
{
"name" : "yshop-miniapp",
"appid" : "__UNI__ADC0FB0",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : 1,
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {
"Payment" : {},
"OAuth" : {}
},
/* 应用发布信息 */
"distribute" : {
//必选,JSON对象,云端打包配置
"android" : {
//可选,JSON对象,Android平台云端打包配置
"packagename" : "", //必填,字符串类型,Android包名
"keystore" : "", //必填,字符串类型,Android签名证书文件路径
"password" : "", //必填,字符串类型,Android签名证书文件的密码
"aliasname" : "", //必填,字符串类型,Android签名证书别名
"schemes" : "", //可选,字符串类型,参考:https://uniapp.dcloud.io/tutorial/app-android-schemes
"abiFilters" : [
//可选,字符串数组类型,参考:https://uniapp.dcloud.io/tutorial/app-android-abifilters
"armeabi-v7a",
"arm64-v8a",
"x86",
"x86_64"
],
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"custompermissions" : false, //可选,Boolean类型,是否自定义Android权限配置
"permissionExternalStorage" : {
//可选,JSON对象,Android平台应用启动时申请读写手机存储权限策略
"request" : "always", //必填,字符串类型,申请读写手机存储权限策略,可取值none、once、always
"prompt" : "" //可选,字符串类型,当request设置为always值用户拒绝时弹出提示框上的内容
},
"permissionPhoneState" : {
//可选,JSON对象,Android平台应用启动时申请读取设备信息权限配置
"request" : "always", //必填,字符串类型,申请读取设备信息权限策略,可取值none、once、always
"prompt" : "" //可选,字符串类型,当request设置为always值用户拒绝时弹出提示框上的内容
},
"minSdkVersion" : 21, //可选,数字类型,Android平台最低支持版本,参考:https://uniapp.dcloud.io/tutorial/app-android-minsdkversion
"targetSdkVersion" : 30, //可选,数字类型,Android平台目标版本,参考:https://uniapp.dcloud.io/tutorial/app-android-targetsdkversion
"packagingOptions" : [
//可选,字符串数组类型,Android平台云端打包时build.gradle的packagingOptions配置项
"doNotStrip '*/armeabi-v7a/*.so'",
"merge '**/LICENSE.txt'"
],
"jsEngine" : "v8", //可选,字符串类型,uni-app使用的JS引擎,可取值v8、jsc
"debuggable" : false, //可选,Boolean类型,是否开启Android调试开关
"locale" : "default", //可选,应用的语言
"forceDarkAllowed" : false, //可选,Boolean类型,是否强制允许暗黑模式
"resizeableActivity" : false, //可选,Boolean类型,是否支持分屏调整窗口大小
"hasTaskAffinity" : false, //可选,Boolean类型,是否设置android:taskAffinity
"buildFeatures" : {
//(HBuilderX3.5.0+版本支持)可选,JSON对象,Android平台云端打包时build.gradle的buildFeatures配置项
"dataBinding" : false, //可选,Boolean类型,是否设置dataBinding
"viewBinding" : false //可选,Boolean类型,是否设置viewBinding
}
},
"ios" : {
//可选,JSON对象,iOS平台云端打包配置
"appid" : "", //必填,字符串类型,iOS平台Bundle ID
"mobileprovision" : "", //必填,字符串类型,iOS打包使用的profile文件路径
"p12" : "", //必填,字符串类型,iOS打包使用的证书文件路径
"password" : "", //必填,字符串类型,iOS打包使用的证书密码
"devices" : "iphone", //必填,字符串类型,iOS支持的设备类型,可取值iphone、ipad、universal
"urlschemewhitelist" : "baidumap", //可选,字符串类型,应用访问白名单列表,参考:https://uniapp.dcloud.io/tutorial/app-ios-schemewhitelist
"urltypes" : "", //可选,字符串类型,参考:https://uniapp.dcloud.io/tutorial/app-ios-schemes
"UIBackgroundModes" : "audio", //可选,字符串类型,应用后台运行模式,参考:https://uniapp.dcloud.io/tutorial/app-ios-uibackgroundmodes
"frameworks" : [
//可选,字符串数组类型,依赖的系统库,已废弃,推荐使用uni原生插件扩展使用系统依赖库
"CoreLocation.framework"
],
"deploymentTarget" : "10.0", //可选,字符串类型,iOS支持的最低版本
"privacyDescription" : {
//可选,JSON对象,iOS隐私信息访问的许可描述
"NSPhotoLibraryUsageDescription" : "", //可选,字符串类型,系统相册读取权限描述
"NSPhotoLibraryAddUsageDescription" : "", //可选,字符串类型,系统相册写入权限描述
"NSCameraUsageDescription" : "", //可选,字符串类型,摄像头使用权限描述
"NSMicrophoneUsageDescription" : "", //可选,字符串类型,麦克风使用权限描述
"NSLocationWhenInUseUsageDescription" : "", //可选,字符串类型,运行期访问位置权限描述
"NSLocationAlwaysUsageDescription" : "", //可选,字符串类型,后台运行访问位置权限描述
"NSLocationAlwaysAndWhenInUseUsageDescription" : "", //可选,字符串类型,运行期后后台访问位置权限描述
"NSCalendarsUsageDescription" : "", //可选,字符串类型,使用日历权限描述
"NSContactsUsageDescription" : "", //可选,字符串类型,使用通讯录权限描述
"NSBluetoothPeripheralUsageDescription" : "", //可选,字符串类型,使用蓝牙权限描述
"NSBluetoothAlwaysUsageDescription" : "", //可选,字符串类型,后台使用蓝牙权限描述
"NSSpeechRecognitionUsageDescription" : "", //可选,字符串类型,系统语音识别权限描述
"NSRemindersUsageDescription" : "", //可选,字符串类型,系统提醒事项权限描述
"NSMotionUsageDescription" : "", //可选,字符串类型,使用运动与健康权限描述
"NSHealthUpdateUsageDescription" : "", //可选,字符串类型,使用健康更新权限描述
"NSHealthShareUsageDescription" : "", //可选,字符串类型,使用健康分享权限描述
"NSAppleMusicUsageDescription" : "", //可选,字符串类型,使用媒体资料库权限描述
"NFCReaderUsageDescription" : "", //可选,字符串类型,使用NFC权限描述
"NSHealthClinicalHealthRecordsShareUsageDescription" : "", //可选,字符串类型,访问临床记录权限描述
"NSHomeKitUsageDescription" : "", //可选,字符串类型,访问HomeKit权限描述
"NSSiriUsageDescription" : "", //可选,字符串类型,访问Siri权限描述
"NSFaceIDUsageDescription" : "", //可选,字符串类型,使用FaceID权限描述
"NSLocalNetworkUsageDescription" : "", //可选,字符串类型,访问本地网络权限描述
"NSUserTrackingUsageDescription" : "" //可选,字符串类型,跟踪用户活动权限描述
},
"idfa" : true, //可选,Boolean类型,是否使用广告标识
"capabilities" : {},
//可选,JSON对象,应用的能力配置(Capabilities)
"CFBundleName" : "HBuilder", //可选,字符串类型,CFBundleName名称
"validArchitectures" : [
//可选,字符串数组类型,编译时支持的CPU指令,可取值arm64、arm64e、armv7、armv7s、x86_64
"arm64"
],
"pushRegisterMode" : "manual", //可选,使用“Push(消息推送)”模块时申请系统推送权限模式,manual表示调用push相关API时申请,其它值表示应用启动时自动申请
"privacyRegisterMode" : "manual", //可选,仅iOS有效,设置为manual表示用户同意隐私政策后才获取idfv,设置为其它值表示应用启动时自动获取
"dSYMs" : false
},
"sdkConfigs" : {
//可选,JSON对象,三方SDK相关配置
"geolocation" : {
//可选,JSON对象,Geolocation(定位)模块三方SDK配置
"system" : {
//可选,JSON对象,使用系统定位
"__platform__" : [ "ios", "android" ] //可选,字符串数组类型,支持的平台
},
"amap" : {
//可选,JSON对象,使用高德定位SDK配置
"__platform__" : [ "ios", "android" ], //可选,字符串数组类型,支持的平台
"appkey_ios" : "", //必填,字符串类型,iOS平台高德定位appkey
"appkey_android" : "" //必填,字符串类型,Android平台高德定位appkey
},
"baidu" : {
//可选,JSON对象,使用百度定位SDK配置
"__platform__" : [ "ios", "android" ], //可选,字符串数组类型,支持的平台
"appkey_ios" : "", //必填,字符串类型,iOS平台百度定位appkey
"appkey_android" : "" //必填,字符串类型,Android平台百度定位appkey
}
},
"maps" : {
//可选,JSON对象,Maps(地图)模块三方SDK配置
"amap" : {
//可选,JSON对象,使用高德地图SDK配置
"appkey_ios" : "", //必填,字符串类型,iOS平台高德地图appkey
"appkey_android" : "" //必填,字符串类型,Android平台高德地图appkey
},
"baidu" : {
//可选,JSON对象,使用百度地图SDK配置
"appkey_ios" : "", //必填,字符串类型,iOS平台百度地图appkey
"appkey_android" : "" //必填,字符串类型,Android平台百度地图appkey
},
"google" : {
//可选,JSON对象,使用Google地图SDK配置
"APIKey_ios" : "", //必填,字符串类型,iOS平台Google地图APIKey
"APIKey_android" : "" //必填,字符串类型,Android平台Google地图APIKey
}
},
"oauth" : {
//可选,JSON对象,使用苹果登录(Sign in with Apple)SDK配置,无配置参数,仅iOS平台支持
"weixin" : {
"appid" : "wx7c84ede33062d1e4",
"UniversalLinks" : "https://yixiang.co/app/"
}
},
"payment" : {
"weixin" : {
"__platform__" : [ "ios", "android" ],
"appid" : "wx7c84ede33062d1e4",
"UniversalLinks" : "https://yixiang.co/app/"
}
},
//可选,JSON对象,使用google支付SDK配置,无配置参数,仅Android平台支持
"push" : {
//可选,JSON对象,Push(消息推送)模块三方SDK配置
"unipush" : {
//可选,JSON对象,使用UniPush SDK配置,无需手动配置参数,云端打包自动获取配置参数
"icons" : {
//可选,JSON对象,推送图标配置
"push" : {
//可选,JSON对象,Push图标配置
"ldpi" : "", //可选,字符串类型,普通屏设备推送图标路径,分辨率要求48x48
"mdpi" : "", //可选,字符串类型,大屏设备设备推送图标路径,分辨率要求48x48
"hdpi" : "", //可选,字符串类型,高分屏设备推送图标路径,分辨率要求72x72
"xdpi" : "", //可选,字符串类型,720P高分屏设备推送图标路径,分辨率要求96x96
"xxdpi" : "", //可选,字符串类型,1080P高密度屏幕推送图标路径,分辨率要求144x144
"xxxdpi" : "" //可选,字符串类型,4K屏设备推送图标路径,分辨率要求192x192
},
"smal" : {
//可选,JSON对象,Push小图标配置
"ldpi" : "", //可选,字符串类型,普通屏设备推送小图标路径,分辨率要求18x18
"mdpi" : "", //可选,字符串类型,大屏设备设备推送小图标路径,分辨率要求24x24
"hdpi" : "", //可选,字符串类型,高分屏设备推送小图标路径,分辨率要求36x36
"xdpi" : "", //可选,字符串类型,720P高分屏设备推送小图标路径,分辨率要求48x48
"xxdpi" : "", //可选,字符串类型,1080P高密度屏幕推送小图标路径,分辨率要求72x72
"xxxdpi" : "" //可选,字符串类型,4K屏设备推送小图标路径,分辨率要求96x96
}
}
},
"igexin" : {
//可选,JSON对象,使用个推推送SDK配置,**已废弃,推荐使用UniPush,UniPush是个推推送VIP版,功能更强大**
"appid" : "", //必填,字符串类型,个推开放平台申请的appid
"appkey" : "", //必填,字符串类型,个推开放平台申请的appkey
"appsecret" : "", //必填,字符串类型,个推开放平台申请的appsecret
"icons" : {
//可选,JSON对象,推送图标配置
"push" : {
//可选,JSON对象,Push图标配置
"ldpi" : "", //可选,字符串类型,普通屏设备推送图标路径,分辨率要求48x48
"mdpi" : "", //可选,字符串类型,大屏设备设备推送图标路径,分辨率要求48x48
"hdpi" : "", //可选,字符串类型,高分屏设备推送图标路径,分辨率要求72x72
"xdpi" : "", //可选,字符串类型,720P高分屏设备推送图标路径,分辨率要求96x96
"xxdpi" : "", //可选,字符串类型,1080P高密度屏幕推送图标路径,分辨率要求144x144
"xxxdpi" : "" //可选,字符串类型,4K屏设备推送图标路径,分辨率要求192x192
},
"smal" : {
//可选,JSON对象,Push小图标配置
"ldpi" : "", //可选,字符串类型,普通屏设备推送小图标路径,分辨率要求18x18
"mdpi" : "", //可选,字符串类型,大屏设备设备推送小图标路径,分辨率要求24x24
"hdpi" : "", //可选,字符串类型,高分屏设备推送小图标路径,分辨率要求36x36
"xdpi" : "", //可选,字符串类型,720P高分屏设备推送小图标路径,分辨率要求48x48
"xxdpi" : "", //可选,字符串类型,1080P高密度屏幕推送小图标路径,分辨率要求72x72
"xxxdpi" : "" //可选,字符串类型,4K屏设备推送小图标路径,分辨率要求96x96
}
}
}
},
"share" : {
//可选,JSON对象,Share(分享)模块三方SDK配置
"weixin" : {
//可选,JSON对象,使用微信分享SDK配置
"appid" : "", //必填,字符串类型,微信开放平台申请的appid
"UniversalLinks" : "" //可选,字符串类型,微信开放平台配置的iOS平台通用链接
},
"qq" : {
//可选,JSON对象,使用QQ分享SDK配置
"appid" : "", //必填,字符串类型,QQ开放平台申请的appid
"UniversalLinks" : "" //可选,字符串类型,QQ开放平台配置的iOS平台通用链接
},
"sina" : {
//可选,JSON对象,使用新浪微博分享SDK配置
"appkey" : "", //必填,字符串类型,新浪微博开放平台申请的appid
"redirect_uri" : "", //必填,字符串类型,新浪微博开放平台配置的redirect_uri
"UniversalLinks" : "" //可选,字符串类型,新浪微博开放平台配置的iOS平台通用链接
}
},
"speech" : {
//可选,JSON对象,Speech(语音识别)模块三方SDK配置
"baidu" : {
//可选,JSON对象,使用百度语音识别SDK配置
"appid" : "", //必填,字符串类型,百度开放平台申请的appid
"apikey" : "", //必填,字符串类型,百度开放平台申请的apikey
"secretkey" : "" //必填,字符串类型,百度开放平台申请的secretkey
}
},
"statics" : {
//可选,JSON对象,Statistic(统计)模块三方SDK配置
"umeng" : {
//可选,JSON对象,使用友盟统计SDK配置
"appkey_ios" : "", //必填,字符串类型,友盟统计开放平台申请的iOS平台appkey
"channelid_ios" : "", //可选,字符串类型,友盟统计iOS平台的渠道标识
"appkey_android" : "", //必填,字符串类型,友盟统计开放平台申请的Android平台appkey
"channelid_android" : "" //可选,字符串类型,友盟统计Android平台的渠道标识
},
"google" : {
//可选,JSON对象,使用Google Analytics for Firebase配置
"config_ios" : "", //必填,字符串类型,Google Firebase统计开发者后台获取的iOS平台配置文件路径
"config_android" : "" //必填,字符串类型,Google Firebase统计开发者后台获取的Android平台配置文件路径
}
},
"ad" : {}
},
//可选,JSON对象,使用互动游戏(变现猫)SDK,无需手动配置,在uni-AD后台申请开通后自动获取配置参数
"icons" : {
//可选,JSON对象,应用图标相关配置
"ios" : {
//可选,JSON对象,iOS平台图标配置
"appstore" : "", //必填,字符串类型,分辨率1024x1024, 提交app sotre使用的图标路径
"iphone" : {
//可选,JSON对象,iPhone设备图标配置
"app@2x" : "", //可选,字符串类型,分辨率120x120,程序图标路径
"app@3x" : "", //可选,字符串类型,分辨率180x180,程序图标路径
"spotlight@2x" : "", //可选,字符串类型,分辨率80x80,Spotlight搜索图标路径
"spotlight@3x" : "", //可选,字符串类型,分辨率120x120,Spotlight搜索图标路径
"settings@2x" : "", //可选,字符串类型,分辨率58x58,Settings设置图标路径
"settings@3x" : "", //可选,字符串类型,分辨率87x87,Settings设置图标路径
"notification@2x" : "", //可选,字符串类型,分辨率40x40,通知栏图标路径
"notification@3x" : "" //可选,字符串类型,分辨率60x60,通知栏图标路径
},
"ipad" : {
//可选,JSON对象,iPad设备图标配置
"app" : "", //可选,字符串类型,分辨率76x76,程序图标图标路径
"app@2x" : "", //可选,字符串类型,分辨率152x152,程序图标图标路径
"proapp@2x" : "", //可选,字符串类型,分辨率167x167,程序图标图标路径
"spotlight" : "", //可选,字符串类型,分辨率40x40,Spotlight搜索图标路径
"spotlight@2x" : "", //可选,字符串类型,分辨率80x80,Spotlight搜索图标路径
"settings" : "", //可选,字符串类型,分辨率29x29,Settings设置图标路径
"settings@2x" : "", //可选,字符串类型,分辨率58x58,Settings设置图标路径
"notification" : "", //可选,字符串类型,分辨率20x20,通知栏图标路径
"notification@2x" : "" //可选,字符串类型,分辨率740x40,通知栏图标路径
}
},
"android" : {
//可选,JSON对象,Android平台图标配置
"ldpi" : "", //可选,字符串类型,普通屏设备程序图标,分辨率要求48x48,已废弃
"mdpi" : "", //可选,字符串类型,大屏设备程序图标,分辨率要求48x48,已废弃
"hdpi" : "", //可选,字符串类型,高分屏设备程序图标,分辨率要求72x72
"xhdpi" : "", //可选,字符串类型,720P高分屏设备程序图标,分辨率要求96x96
"xxhdpi" : "", //可选,字符串类型,1080P高分屏设备程序图标,分辨率要求144x144
"xxxhdpi" : "" //可选,字符串类型,2K屏设备程序图标,分辨率要求192x192
}
},
"splashscreen" : {
//可选,JSON对象,启动界面配置
"iosStyle" : "common", //可选,字符串类型,iOS平台启动界面样式,可取值common、default、storyboard
"ios" : {
//可选,JSON对象,iOS平台启动界面配置
"storyboard" : "", //可选,字符串类型,自定义storyboard启动界面文件路径,iosStyle值为storyboard时生效
"iphone" : {
//可选,JSON对象,iPhone设备启动图配置,iosStyle值为default时生效
"default" : "", //可选,字符串类型,分辨率320x480,iPhone3(G/GS)启动图片路径,已废弃
"retina35" : "", //可选,字符串类型,分辨率640x960,3.5英寸设备(iPhone4/4S)启动图片路径,已废弃
"retina40" : "", //可选,字符串类型,分辨率640x1136,4.0英寸设备(iPhone5/5S)启动图片路径
"retina40l" : "", //可选,字符串类型,分辨率1136x640,4.0英寸设备(iPhone5/5S)横屏启动图片路径
"retina47" : "", //可选,字符串类型,分辨率750x1334,4.7英寸设备(iPhone6/7/8)启动图片路径
"retina47l" : "", //可选,字符串类型,分辨率1334x750,4.7英寸设备(iPhone6/7/8)横屏启动图片路径
"retina55" : "", //可选,字符串类型,分辨率1242x2208,5.5英寸设备(iPhone6/7/8Plus)启动图片路径
"retina55l" : "", //可选,字符串类型,分辨率2208x1242,5.5英寸设备(iPhone6/7/8Plus)横屏启动图片路径
"iphonex" : "", //可选,字符串类型,分辨率1125x2436,5.8英寸设备(iPhoneX/XS)启动图片路径
"iphonexl" : "", //可选,字符串类型,分辨率2436x1125,5.8英寸设备(iPhoneX/XS)横屏启动图片路径
"portrait-896h@2x" : "", //可选,字符串类型,分辨率828x1792,6.1英寸设备(iPhoneXR)启动图片路径
"landscape-896h@2x" : "", //可选,字符串类型,分辨率1792x828,6.1英寸设备(iPhoneXR)iPhoneXR横屏启动图片路径
"portrait-896h@3x" : "", //可选,字符串类型,分辨率1242x2688,6.5英寸设备(iPhoneXS Max)启动图片路径
"landscape-896h@3x" : "" //可选,字符串类型,分辨率2688x1242,6.5英寸设备(iPhoneXS Max)横屏启动图片路径
},
"ipad" : {
//可选,JSON对象,iPad设备启动图配置,iosStyle值为default时生效
"portrait" : "", //可选,字符串类型,分辨率768x1004,iPad竖屏启动图片路径,已废弃
"portrait-retina" : "", //可选,字符串类型,分辨率1536x2008,iPad高分屏竖屏启动图片路径,已废弃
"landscape" : "", //可选,字符串类型,分辨率1024x748,iPad横屏启动图片路径,已废弃
"landscape-retina" : "", //可选,字符串类型,分辨率2048x1496,iPad高分屏横屏启动图片路径,已废弃
"portrait7" : "", //可选,字符串类型,分辨率768x1024,9.7/7.9英寸iPad/mini竖屏启动图片路径
"landscape7" : "", //可选,字符串类型,分辨率1024x768,9.7/7.9英寸iPad/mini横屏启动图片路径
"portrait-retina7" : "", //可选,字符串类型,分辨率1536x2048,9.7/7.9英寸iPad/mini高分屏竖屏图片路径
"landscape-retina7" : "", //可选,字符串类型,分辨率2048x1536,9.7/7.9英寸iPad/mini高分屏横屏启动图片路径
"portrait-1112h@2x" : "", //可选,字符串类型,分辨率1668x2224,10.5英寸iPad Pro竖屏启动图片路径
"landscape-1112h@2x" : "", //可选,字符串类型,分辨率2224x1668,10.5英寸iPad Pro横屏启动图片路径
"portrait-1194h@2x" : "", //可选,字符串类型,分辨率1668x2388,11英寸iPad Pro竖屏启动图片路径
"landscape-1194h@2x" : "", //可选,字符串类型,分辨率2388x1668,11英寸iPad Pro横屏启动图片路径
"portrait-1366h@2x" : "", //可选,字符串类型,分辨率2048x2732,12.9英寸iPad Pro竖屏启动图片路径
"landscape-1366h@2x" : "" //可选,字符串类型,分辨率2732x2048,12.9英寸iPad Pro横屏启动图片路径
}
},
"androidStyle" : "common", //可选,字符串类型,Android平台启动界面样式,可取值common、default
"android" : {
//可选,JSON对象,Android平台启动图片配置, androidStyle值为default时生效
"ldpi" : "", //可选,字符串类型,分辨率320x442,低密度屏幕启动图片路径,已废弃
"mdpi" : "", //可选,字符串类型,分辨率240x282,中密度屏幕启动图片路径,已废弃
"hdpi" : "", //可选,字符串类型,分辨率480x762,高密度屏幕启动图片路径
"xhdpi" : "", //可选,字符串类型,分辨率720x1242,720P高密度屏幕启动图片路径
"xxhdpi" : "" //可选,字符串类型,分辨率1080x1882,1080P高密度屏幕启动图片路径
}
},
"orientation" : [
//可选,字符串数组类型,应用支持的横竖屏,**已废弃,使用screenOrientation配置**
"portrait-primary",
"portrait-secondary",
"landscape-primary",
"landscape-secondary"
]
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "wx001e2dc50bf532df",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "定位最近的门店"
}
},
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"fallbackLocale" : "zh-Hans",
"h5" : {
"router" : {
"base" : "/h5/"
}
}
}
/* 模块配置 *//* 应用发布信息 *//* android打包配置 */
+423
View File
@@ -0,0 +1,423 @@
{
"name": "yshop-miniapp",
"version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@vant/area-data": {
"version": "1.5.0",
"resolved": "https://registry.npmmirror.com/@vant/area-data/-/area-data-1.5.0.tgz",
"integrity": "sha512-SWmDhYmWiOgtAgtJqcW7N4XyGgrg/7l6t1+XSgt8BkPp2oOKO1ZUn8+46brLpT/gzRe/v8BtyTLmdBwMamrmQw=="
},
"@vant/popperjs": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz",
"integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw=="
},
"@vant/use": {
"version": "1.5.2",
"resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.5.2.tgz",
"integrity": "sha512-CBK61iT568dCHUwFFsErGbW6/5tmrPnZJKGtcSy7Tjcrmws8Ku+YZo7IUFD9Xkj9MfSJ4pfhQ7pU2KouP5Cojg=="
},
"@vue/devtools-api": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"@vue/shared": {
"version": "3.3.4",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
},
"add": {
"version": "2.0.6",
"resolved": "https://registry.npmmirror.com/add/-/add-2.0.6.tgz",
"integrity": "sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q=="
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmmirror.com/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmmirror.com/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
},
"aws4": {
"version": "1.12.0",
"resolved": "https://registry.npmmirror.com/aws4/-/aws4-1.12.0.tgz",
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"requires": {
"tweetnacl": "^0.14.3"
}
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"requires": {
"delayed-stream": "~1.0.0"
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"requires": {
"assert-plus": "^1.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"flyio": {
"version": "0.6.14",
"resolved": "https://registry.npmmirror.com/flyio/-/flyio-0.6.14.tgz",
"integrity": "sha512-RE2OXE1ZZmcXOKb0jCtGyquHDxpAqHg17CZ8lmQKRfl3x1kP+NBpaQDx4WgN7DNpLJjFnspTzTEQpwRGg6/xaA==",
"requires": {
"request": "^2.85.0"
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmmirror.com/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"requires": {
"assert-plus": "^1.0.0"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
},
"har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmmirror.com/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"requires": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
}
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
},
"json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.4.0",
"verror": "1.10.0"
}
},
"jweixin-module": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
"integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmmirror.com/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
},
"pinia": {
"version": "2.1.6",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz",
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
"requires": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
}
},
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA=="
},
"qs": {
"version": "6.5.3",
"resolved": "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz",
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
},
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmmirror.com/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sshpk": {
"version": "1.17.0",
"resolved": "https://registry.npmmirror.com/sshpk/-/sshpk-1.17.0.tgz",
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmmirror.com/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"requires": {
"punycode": "^2.1.0"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"vant": {
"version": "4.6.2",
"resolved": "https://registry.npmmirror.com/vant/-/vant-4.6.2.tgz",
"integrity": "sha512-6EHCCAGM5a9VVzpBg/wZNPDFmJ8T1a4k29DPNcEMW3X670awW3rnD7+/x3dw+bE17JhhSg49V/+fQwBP2iQkAg==",
"requires": {
"@vant/popperjs": "^1.3.0",
"@vant/use": "^1.5.1",
"@vue/shared": "^3.0.0"
}
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
},
"dependencies": {
"extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="
}
}
},
"vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA=="
},
"weixin-js-sdk": {
"version": "1.6.3",
"resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.3.tgz",
"integrity": "sha512-5jYNtVcxWmgHNqCLSPoX6vXab9G4RPDZx0klX8IsVwGjDTwx/UDmgfF7fkRMGguRpuFv0O7tk1jb+Oay2QiV1w=="
},
"yarn": {
"version": "1.22.19",
"resolved": "https://registry.npmmirror.com/yarn/-/yarn-1.22.19.tgz",
"integrity": "sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ=="
}
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "yshop-miniapp",
"version": "1.2.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@vant/area-data": "^1.5.0",
"add": "^2.0.6",
"flyio": "^0.6.14",
"jweixin-module": "^1.6.0",
"pinia": "^2.1.6",
"vant": "^4.6.2",
"weixin-js-sdk": "^1.6.3",
"yarn": "^1.22.19"
}
}
+207
View File
@@ -0,0 +1,207 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path" : "pages/menu/menu",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
}
,{
"path" : "pages/order/order",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
}
,{
"path" : "pages/mine/mine",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
}
,{
"path" : "pages/cart/cart",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
],
"subPackages": [{
"root": "pages/components",
"pages": [ {
"path": "pages/mine/content",
"style": {
"navigationBarTitleText": "内容",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/pay/pay",
"style": {
"navigationBarTitleText": "支付",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/remark/remark",
"style": {
"navigationBarTitleText": "备注",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/packages/index",
"style": {
"navigationBarTitleText": "可用优惠券",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"enablePullDownRefresh": true
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
},
{
"path": "pages/login/logout",
"style": {
"navigationBarTitleText": "退出登录"
}
},
{
"path": "pages/address/address",
"style": {
"navigationBarTitleText": "我的地址",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/address/add",
"style": {
"navigationBarTitleText": "添加地址",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/orders/orders",
"style": {
"navigationBarTitleText": "历史订单",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
}, {
"path": "pages/orders/detail",
"style": {
"navigationBarTitleText": "订单详情",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/coupons/coupons",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的卡券",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/mine/userinfo",
"style": {
"navigationBarTitleText": "用户信息",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
},
{
"path": "pages/shop/shop",
"style": {
"navigationBarTitleText": "店铺",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}, {
"path": "pages/balance/bill",
"style": {
"navigationBarTitleText": "账单",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff"
}
}
,{
"path" : "pages/orders/refund",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
]
}],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": " ",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"navigationStyle": "custom"
},
"tabBar": {
"color": "#919293",
"selectedColor": "#dd524d",
"backgroundColor": "#fff",
"borderStyle": "black",
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/images/tabBar/index.png",
"selectedIconPath": "static/images/tabBar/index_selected.png"
},
{
"pagePath": "pages/menu/menu",
"text": "点餐",
"iconPath": "static/images/tabBar/drink.png",
"selectedIconPath": "static/images/tabBar/drink-selected.png"
}, {
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "static/images/tabBar/cart.png",
"selectedIconPath": "static/images/tabBar/cart-selected.png"
},{
"pagePath": "pages/order/order",
"text": "订单",
"iconPath": "static/images/tabBar/order.png",
"selectedIconPath": "static/images/tabBar/order-selected.png"
}, {
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/images/tabBar/mine.png",
"selectedIconPath": "static/images/tabBar/mine_selected.png"
}
]
},
"uniIdRouter": {}
}
+245
View File
@@ -0,0 +1,245 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="cart-popup">
<view class="top flex justify-between">
<text>已点{{ getCartGoodsNumber }}份</text>
<text @tap="handleCartClear">清空</text>
</view>
<scroll-view class="cart-list" scroll-y>
<view class="wrapper">
<uv-empty mode="car" v-if="cart.length == 0"></uv-empty>
<view class="item" v-for="(item, index) in cart" :key="index">
<view class="left">
<view class="name">{{ item.name }}</view>
<view class="props">{{ item.valueStr }}</view>
</view>
<view class="center">
<text>¥{{ item.price }}</text>
</view>
<view class="right">
<button type="default" plain size="mini" class="btn" hover-class="none"
@tap="handleCartItemReduce(index)">
<view class="iconfont iconsami-select"></view>
</button>
<view class="number">{{ item.number }}</view>
<button type="primary" class="btn" size="min" hover-class="none"
@tap="handleCartItemAdd(index)">
<view class="iconfont iconadd-select"></view>
</button>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="fixed-bottom flex justify-between align-center bg-white ">
<view class="mx-2 ont-weight-light">应付:<text class="text-danger">¥{{ getCartGoodsPrice }}</text></view>
<view><uv-button type="warning" color="#dd524d" :customStyle="customStyle" size="large" text="去结算" @click="toPay"></uv-button></view>
</view>
<uv-toast ref="uToast"></uv-toast>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow} from '@dcloudio/uni-app'
const main = useMainStore()
const { orderType,address, store,location,isLogin } = storeToRefs(main)
const title = ref('购物车')
const cart = ref([])
const uToast = ref()
// onLoad(() => {
// cart.value = uni.getStorageSync('cart')
// })
onShow(() => {
//cart.value = []
cart.value = uni.getStorageSync('cart')
})
const getCartGoodsNumber = computed(() => { //计算购物车总数
if(cart.value.length == 0) {
return 0
}
return cart.value.reduce((acc, cur) => acc + cur.number, 0)
})
const getCartGoodsPrice = computed(() =>{ //计算购物车总价
if(cart.value.length == 0) {
return 0
}
let price = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);
return parseFloat(price).toFixed(2);
})
const customStyle = computed(() =>{
return {
paddingLeft:'60rpx',
paddingRight:'60rpx'
}
})
const handleCartItemAdd = (index) => {
cart.value[index].number += 1
uni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))
}
const handleCartItemReduce = (index) => {
if (cart.value[index].number === 1) {
cart.value.splice(index, 1)
} else {
cart.value[index].number -= 1
}
if (!cart.value.length) {
cartPopupVisible.value = false
}
uni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))
}
const handleCartClear = () => { //清空购物车
uni.showModal({
title: '提示',
content: '确定清空购物车么',
success: ({
confirm
}) => {
if (confirm) {
cart.value = []
uni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))
}
}
})
}
const toPay = () => {
if(cart.value.length == 0){
uToast.value.show({message:'请先去点餐哦',type: 'error'});
return;
}
if (!isLogin.value) {
uni.navigateTo({
url: '/pages/components/pages/login/login'
})
return
} else {
if (store.value.status == 0) {
uToast.value.show({message:'不在店铺营业时间内',type: 'error'});
return;
}
// 判断当前是否在配送范围内
if (orderType.value == 'takeout' && store.value.distance < store.value.far) {
uToast.value.show({message:'选中的地址不在配送范围',type: 'error'});
return;
}
uni.showLoading({
title: '加载中'
})
uni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))
uni.navigateTo({
url: '/pages/components/pages/pay/pay'
})
}
uni.hideLoading()
}
</script>
<style lang="scss" scoped>
.cart-popup {
.top {
background-color: $bg-color-primary;
//color: $color-primary;
color: #5A5B5C;
padding: 10rpx 30rpx;
font-size: 24rpx;
text-align: right;
}
.cart-list {
background-color: #ffffff;
width: 100%;
overflow: hidden;
min-height: 1vh;
max-height: 60vh;
.wrapper {
height: 100%;
display: flex;
flex-direction: column;
padding: 0 30rpx;
margin-bottom: 156rpx;
.item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
position: relative;
&::after {
content: ' ';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: $border-color;
height: 2rpx;
transform: scaleY(0.6);
}
.left {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
margin-right: 30rpx;
.name {
font-size: $font-size-sm;
color: $text-color-base;
}
.props {
color: $text-color-assist;
font-size: 24rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.center {
margin-right: 120rpx;
font-size: $font-size-base;
}
.right {
display: flex;
align-items: center;
justify-content: space-between;
.btn {
width: 46rpx;
height: 46rpx;
border-radius: 100%;
padding: 0;
text-align: center;
line-height: 46rpx;
}
.number {
font-size: $font-size-base;
width: 46rpx;
height: 46rpx;
text-align: center;
line-height: 46rpx;
}
}
}
}
}
}
</style>
@@ -0,0 +1,182 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container">
<view class="form-box">
<view class="form">
<list-cell :hover="false">
<view class="form-input">
<view class="label">收货人</view>
<input class="input" placeholder="请输入收货人" v-model="form.realName" placeholder-class="text-color-assist" />
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input">
<view class="label">联系方式</view>
<input class="input" placeholder="请输入收货人联系方式" v-model="form.phone" placeholder-class="text-color-assist" />
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input">
<view class="label">收货地址</view>
<view class="input" @click="chooseLocation">{{form.address ? form.address : '请选择收货地址'}}</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input">
<view class="label">详细地址</view>
<input class="input" placeholder="请输入收货人详细地址" v-model="form.detail" placeholder-class="text-color-assist" />
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input">
<view class="label">默认地址</view>
<view class="radio-group">
<view class="radio" :class="{'checked': !form.isDefault}" style="margin-right: 10rpx;" @tap="form.isDefault=0">否</view>
<view class="radio" :class="{'checked': form.isDefault}" @tap="form.isDefault=1">是</view>
</view>
</view>
</list-cell>
</view>
<view class="btn-section">
<button type="primary" size="default" @tap="save">保存</button>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow } from '@dcloudio/uni-app'
import { formatDateTime,prePage } from '@/utils/util'
import {
getAddressAddAndEdit
} from '@/api/address'
const main = useMainStore()
const { isLogin,addresses } = storeToRefs(main)
const title = ref('编辑地址')
const form = ref({
realName: '',
isDefault: 0,
phone: '',
address: '',
detail: '',
latitude: '',
longitude: ''
})
onLoad((option) => {
//为了方便演示,这里用本地缓存
console.log('option:',option)
if (option.id) {
form.value = addresses.value.find(item => item.id == option.id)
}
})
const save = async() => {
let data = {}
if (form.value.hasOwnProperty('id')) {
data = await getAddressAddAndEdit(form.value);
} else {
data = await getAddressAddAndEdit(form.value);
}
if (data) {
setTimeout(function(){
uni.navigateBack()
}, 2000);
}
}
const chooseLocation = async() => {
uni.chooseLocation({
success: function (res) {
form.value.address = res.address + ' ' + res.name;
form.value.latitude = res.latitude;
form.value.longitude = res.longitude;
}
});
// let [error, res] = await uni.chooseLocation();
// if (res) {
// form.value.address = res.address + ' ' + res.name;
// form.value.latitude = res.latitude;
// form.value.longitude = res.longitude;
// }
}
</script>
<style lang="scss" scoped>
.form-box {
width: 100%;
height: 100%;
padding: 30rpx;
display: flex;
flex-direction: column;
color: $text-color-base;
.form-input {
display: flex;
align-items: center;
width: 100%;
}
.label {
width: 200rpx;
font-size: $font-size-lg;
color: $text-color-base;
font-weight: 500;
}
.input {
flex: 1;
display: flex;
align-items: center;
}
.radio-group {
display: flex;
justify-content: flex-start;
.radio {
padding: 10rpx 30rpx;
border-radius: 6rpx;
border: 2rpx solid $text-color-assist;
color: $text-color-assist;
font-size: $font-size-base;
&.checked {
background-color: $color-primary;
color: $text-color-white;
border: 2rpx solid $color-primary;
}
}
}
.btn-section {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
button {
font-size: $font-size-base;
height: 90rpx;
border-radius: 50rem !important;
width: 85%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>
@@ -0,0 +1,223 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container">
<view class="main">
<uv-empty v-if="addresses.length == 0" mode="address"></uv-empty>
<template v-else>
<uv-swipe-action>
<uv-swipe-action-item class="address-wrapper" :options="swipeOption"
@click="handleSwipeClick(address.id)" v-for="(address, index) in addresses" :key="index">
<view class="address" @tap="chooseAddress(address)">
<view class="left flex-fill overflow-hidden mr-20">
<view class="font-size-lg font-weight-bold text-truncate"
style="margin-bottom: 10rpx;white-space:normal;">
{{ address.address + ' ' + address.detail }}
</view>
<view class="font-size-sm text-color-assist">
{{ address.realName }} {{ address.isDefault ? '默认' : '' }} {{ address.phone }}
</view>
</view>
<image src="/static/images/edit.png" class="edit-icon" @tap.stop="edit(address.id)"></image>
</view>
</uv-swipe-action-item>
</uv-swipe-action>
</template>
</view>
<view class="btn-box">
<button type="primary" size="default" @tap="add">新增地址</button>
</view>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow } from '@dcloudio/uni-app'
import { formatDateTime,prePage } from '@/utils/util'
import {
addressAll,
addressDelete,
shopGetDistanceFromLocation
} from '@/api/address'
const main = useMainStore()
const { isLogin,addresses,store } = storeToRefs(main)
const title = ref('我的地址')
const scene = ref('menu')
const is_choose = ref(false)
const swipeOption = ref(
[{
text: '删除',
style: {
backgroundColor: '#D12E32'
}
}])
onLoad((option) => {
console.log('option:',option)
is_choose.value = option.is_choose || false
scene.value = option.scene || 'menu'
})
onShow(() => {
init();
})
const init = async() => {
let data = await addressAll();
if (data) {
main.SET_ADDRESSES(data)
}
}
const add = () => {
uni.navigateTo({
url: '/pages/components/pages/address/add'
})
}
const edit = (id) => {
uni.navigateTo({
url: '/pages/components/pages/address/add?id=' + id
})
}
const handleSwipeClick = async(id) => {
let [error, res] = await uni.showModal({
title: '提示',
content: '确定要删除?'
});
if (res && res.confirm) {
let data = await addressDelete({
id: id
});
if (data) {
const index = addresses.value.findIndex(item => item.id == id)
const newaddresses = JSON.parse(JSON.stringify(addresses.value))
newaddresses.splice(index, 1)
main.SET_ADDRESSES(newaddresses)
uni.showToast({
title: '删除成功!',
icon: 'success'
})
}
}
}
const chooseAddress = async(address) => {
if (!is_choose.value) {
return;
}
console.log('scene.value:',scene.value)
if (scene.value == 'menu' || scene.value == 'pay') {
let data = await shopGetDistanceFromLocation({
lat: address.latitude,
lng: address.longitude,
lat2: main.store.lat,
lng2: main.store.lng
});
if (!data) {
return;
}
if (parseFloat(data) > store.value.distance) {
uni.showToast({
title: '不在配送范围!',
icon: 'error'
})
return;
}
main.SET_ADDRESS(address)
main.SET_ORDER_TYPE('takeout')
main.store.far = data
main.store.far_text = data + 'km'
main.SET_STORE(main.store)
main.SET_LOCATION({
latitude: address.lat,
longitude: address.lng
});
if (scene.value == 'menu') {
uni.switchTab({
url: '/pages/menu/menu'
})
} else if (scene.value == 'pay') {
uni.navigateBack();
}
} else if (scene.value == 'scoreShop') {
main.SET_ADDRESS(address)
uni.navigateBack()
}
}
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
}
.main {
width: 100%;
padding: 30rpx;
display: flex;
flex-direction: column;
padding-bottom: 100rpx;
.address-wrapper {
margin-bottom: 30rpx;
}
.address {
width: 100%;
padding: 40rpx 30rpx;
background-color: #FFFFFF;
display: flex;
justify-content: space-between;
align-items: center;
.right {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.edit-icon {
width: 50rpx;
height: 50rpx;
flex-shrink: 0;
}
}
}
.btn-box {
height: 100rpx;
background-color: #FFFFFF;
box-shadow: 0 0 20rpx rgba($color: $text-color-assist, $alpha: 0.1);
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10rpx 0;
display: flex;
align-items: center;
justify-content: center;
button {
height: 80rpx;
width: 80%;
border-radius: 50rem !important;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
@@ -0,0 +1,229 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view>
<view class="wrap">
<view class="bg-white" v-if="cate == 0">
<uv-tabs activeColor="#f29100" ref="tabs" :list="list" :current="current" @change="change" :scrollable="false" swiperWidth="750"></uv-tabs>
</view>
<view class="bg-white" v-else>
<uv-tabs activeColor="#f29100" ref="tabs" :list="list1" :current="current" @change="change" :scrollable="false" swiperWidth="750"></uv-tabs>
</view>
<swiper class="swiper-box" :current="swiperCurrent" @transition="transition" @animationfinish="animationfinish">
<swiper-item class="swiper-item" v-for="(item, index) in orderList" :key="index">
<scroll-view scroll-y style="height: 100%;width: 100%;" @scrolltolower="reachBottom">
<view class="page-box">
<view v-if="item.length == 0">
<view class="centre" v-if="loadStatus[index] != 'loading'">
<view class="explain">
您还没有相关的账单
<view class="tips">可以去看看有那些想买的</view>
</view>
<view class="btn">
<navigator open-type="switchTab" url="/pages/menu/menu">随便逛逛</navigator>
</view>
</view>
<view v-else>
<u-loadmore :status="loadStatus[index]" bgColor="#f2f2f2"></u-loadmore>
</view>
</view>
<view v-else>
<view class="order" v-for="(res, resIndex) in orderList[index]" :key="resIndex">
<view class="type">
<view>{{res.title}}</view>
<view>{{res.mark}}</view>
</view>
<view class="total">
<view>
{{res.pm == 0 ? '-':'+'}}¥{{res.number}}元
</view>
<view>
{{formatDateTime(res.createTime)}}
</view>
</view>
</view>
<u-loadmore :status="loadStatus[index]" bgColor="#f2f2f2"></u-loadmore>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script setup>
import {
ref,
watch
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow} from '@dcloudio/uni-app'
import { formatDateTime } from '@/utils/util'
import {
balanceGetBillList
} from '@/api/user'
const main = useMainStore()
const { isLogin } = storeToRefs(main)
const title = ref('账单')
const orderList = ref([[], [], [], []])
const list = ref([
{
name: '全部'
},
{
name: '消费'
},
{
name: '充值'
},
{
name: '退款'
}
]
)
const list1 = ref([
{
name: '全部'
},
{
name: '消费'
}
]
)
const current = ref(0)
const swiperCurrent = ref(0)
const tabsHeight =ref(0)
const dx = ref(0)
const loadStatus = ref(['loadmore','loadmore','loadmore','loadmore'])
const page = ref(1)
const pageSize = ref(20)
const tabs = ref()
const cate = ref(0)
onLoad((option) => {
console.log('cate:',option.cate)
cate.value = option.cate
getBill();
})
const getBill = async() => {
if (loadStatus.value[swiperCurrent.value] == 'loading') {
return;
}
loadStatus.value.splice(swiperCurrent.value,1,"loading")
let data = await balanceGetBillList({cate:cate.value,type:swiperCurrent.value,page:page.value,pagesize:pageSize.value});
if (page.value == 1 ){
orderList.value[swiperCurrent.value] = [];
}
if (data && data.length > 0) {
page.value++;
orderList.value[swiperCurrent.value] = orderList.value[swiperCurrent.value].concat(data);
loadStatus.value.splice(swiperCurrent.value,1,"loadmore")
} else{
loadStatus.value.splice(swiperCurrent.value,1,"nomore")
}
}
const reachBottom = () => {
if (loadStatus.value[swiperCurrent.value] == 'nomore') {
return;
}
getBill();
}
// tab栏切换
const change = (e) => {
swiperCurrent.value = e.index;
page.value = 1;
}
const transition = ({ detail: { dx } }) => {
}
const animationfinish = ({ detail }) => {
swiperCurrent.value = detail.current;
current.value = detail.current;
page.value = 1;
getBill();
}
</script>
<style>
/* #ifndef H5 */
page {
height: 100%;
background-color: #f2f2f2;
}
/* #endif */
</style>
<style lang="scss" scoped>
.order {
width: 710rpx;
background-color: #ffffff;
margin: 20rpx auto;
border-radius: 20rpx;
box-sizing: border-box;
padding: 20rpx;
font-size: 28rpx;
.total {
display: inline-block;
text-align: right;
.total-price {
font-size: 36rpx;
}
float: right;
}
.type {
display: inline-block;
}
}
.centre {
text-align: center;
margin: 200rpx auto;
font-size: 32rpx;
image {
width: 164rpx;
height: 164rpx;
border-radius: 50%;
margin-bottom: 20rpx;
}
.tips {
font-size: 24rpx;
color: #999999;
margin-top: 20rpx;
}
.btn {
margin: 80rpx auto;
width: 200rpx;
border-radius: 32rpx;
line-height: 64rpx;
color: #ffffff;
font-size: 26rpx;
background: linear-gradient(270deg, rgba(249, 116, 90, 1) 0%, rgba(255, 158, 1, 1) 100%);
}
}
.wrap {
display: flex;
flex-direction: column;
height: calc(100vh - var(--window-top));
width: 100%;
}
.swiper-box {
flex: 1;
}
.swiper-item {
height: 100%;
}
</style>
@@ -0,0 +1,557 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container position-relative w-100 h-100 overflow-hidden">
<view class="exchange-box">
<view class="input-box">
<input type="text" v-model="exchange_code" placeholder="请输入兑换码" placeholder-class="text-color-assist font-size-base" />
<button type="primary" @click="exchange">兑换</button>
</view>
</view>
<view class="tabbar">
<view class="tab" :class="{active: activeTabIndex == index}"
v-for="(item, index) in tabs" :key="index" @tap="handleTab(index)">
<view class="title">{{ item.title }}</view>
</view>
</view>
<view class="flex-fill">
<scroll-view scroll-y class="coupon-list" @scrolltolower="getCoupons(activeTabIndex)">
<view class="wrapper" v-if="0 === activeTabIndex">
<uv-empty v-if="myCoupons.length == 0" mode="list"></uv-empty>
<view class="coupon" v-for="(item, index) in myCoupons" :key="index" @tap="openDetailModal(item,index)">
<view class="taobao">
<view class="ticket">
<view class="left">
<image
class="picture"
:src="item.image"
mode="aspectFill"
></image>
<view class="introduce">
<view class="top">
¥
<text class="big">{{item.value}}</text>
<view>
满{{item.least}}减{{item.value}}
</view>
</view>
<view class="type">{{ item.title }}</view>
<view class="date u-line-1">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>
</view>
</view>
<view class="right" @click.stop="" v-if="activeTabIndex == 1">
<view class="use immediate-use" :round="true" @tap="receive(item, index)" >立即领取</view>
</view>
<view class="right" @click.stop="" v-if="activeTabIndex == 0">
<view v-if="item.status == 0" class="use immediate-use" :round="true" @tap="useCouponWith(item)" >立即使用</view>
<view v-else class="used">已使用</view>
</view>
</view>
</view>
</view>
</view>
<view class="wrapper" v-if="1 === activeTabIndex">
<uv-empty v-if="notCoupons.length == 0" mode="list"></uv-empty>
<view class="coupon" v-for="(item, index) in notCoupons" :key="index" @tap="openDetailModal(item,index)">
<view class="taobao">
<view class="ticket">
<view class="left">
<image
class="picture"
:src="item.image"
mode="aspectFill"
></image>
<view class="introduce">
<view class="top">
¥
<text class="big">{{item.value}}</text>
<view>
满{{item.least}}减{{item.value}}
</view>
</view>
<view class="type">{{ item.title }}</view>
<view class="date u-line-1">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>
</view>
</view>
<view class="right" @click.stop="" v-if="activeTabIndex == 1">
<view class="use immediate-use" :round="true" @tap="receive(item, index)" >立即领取</view>
</view>
<view class="right" @click.stop="" v-if="activeTabIndex == 0">
<view v-if="item.status == 0" class="use immediate-use" :round="true" @tap="useCouponWith(item)" >立即使用</view>
<view v-else class="used">已使用</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<modal custom :show="detailModalVisible" @cancel="closeDetailModal" width="90%">
<view class="modal-content">
<view class="d-flex font-size-extra-lg text-color-base just-content-center mb-20">{{ coupon.title }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">
有效期:{{formatDateTime(coupon.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(coupon.endTime, 'yyyy-MM-dd')}}
</view>
<view class="d-flex font-size-sm text-color-base mb-20">
领取时间:{{formatDateTime(coupon.createTime)}}
</view>
<view class="d-flex font-size-sm text-color-base mb-20">
券价值:满{{ coupon.least }}减{{ coupon.value }}
</view>
<view class="d-flex font-size-sm text-color-base mb-20" v-if="activeTabIndex == 1">
每人限领:{{ coupon.limit }} 张
</view>
<view class="d-flex font-size-sm text-color-base mb-20">
适用范围:{{typeInfo(coupon.type)}}
</view>
<view class="d-flex font-size-sm text-color-base mb-20">
适用店铺:{{coupon.shopName}}
</view>
<view class="d-flex align-items-center just-content-center" v-if="activeTabIndex == 0">
<button type="primary" @tap="useCoupon" class="use-coupon-btn">立即使用</button>
</view>
<view class="d-flex align-items-center just-content-center" v-if="activeTabIndex == 1">
<button type="primary" @tap="receive(coupon, couponIndex)" class="use-coupon-btn">立即领取</button>
</view>
</view>
</modal>
<!--轻提示-->
<uv-toast ref="uToast"></uv-toast>
</view>
</template>
<script setup>
import {
ref,
watch
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'
import { formatDateTime,kmUnit } from '@/utils/util'
import {
couponReceive,
couponMine,
couponIndexApi
} from '@/api/coupon'
const main = useMainStore()
const { isLogin } = storeToRefs(main)
const title = ref('优惠券')
const tabs = ref([
{title: '我的优惠券', page:1, pagesize:10,
coupons: []
},
{title: '未领优惠券', page:1, pagesize:10,
coupons: []
}
])
const activeTabIndex = ref(0)
const detailModalVisible = ref(false)
const coupon = ref({})
const couponIndex = ref(0)
const exchange_code = ref('')
const uToast = ref()
const myCoupons = ref([])
const notCoupons = ref([])
onShow(() => {
getCoupons(0)
})
onPullDownRefresh(() => {
if(activeTabIndex.value == 0) {
myCoupons.value = []
}
if(activeTabIndex.value == 1) {
notCoupons.value = []
}
tabs.value[activeTabIndex.value].page = 1;
getCoupons(activeTabIndex.value)
})
watch(activeTabIndex, () => {
getCoupons(activeTabIndex.value)
})
// 兑换
const exchange = async() => {
let data = await couponReceive({code:this.exchange_code});
if (data) {
uToast.value.show({
message: '兑换成功',
type: 'success'
});
tabs.value[0].coupons = [];
tabs.value[0].page = 1;
getCoupons(0)
tabs.value[1].coupons = [];
tabs.value[1].page = 1;
getCoupons(1)
}
}
// 使用范围
const typeInfo = (type) => {
if (type == 0) {
return '通用'
}
if (type == 1) {
return '自取'
}
if (type == 2) {
return '外卖'
}
}
const handleTab = (index) => {
console.log('activeTabIndex2:',index)
activeTabIndex.value = index
}
const getCoupons = async(type) => {
let page = tabs.value[type].page;
let pagesize = tabs.value[type].pagesize;
// 我的优惠券
let data = [];
if (type == 0) {
myCoupons.value = await couponMine({page:page,pagesize:pagesize});
}
// 未领优惠券
if (type == 1) {
notCoupons.value = await couponIndexApi({page:page,pagesize:pagesize});
}
//console.log('data:',data)
uni.stopPullDownRefresh();
console.log('tabs.value:',tabs.value[type].title)
//tabs.value[type].page++;
}
const openDetailModal = (coupon,index) => {
couponIndex.value = index;
coupon.value = coupon
detailModalVisible.value = true
}
const useCouponWith = (coupon) => {
//coupon.value = coupon
useCoupon();
}
const closeDetailModal = () => {
detailModalVisible.value = false
coupon.value = {}
}
const useCoupon = () => {
uni.switchTab({
url: '/pages/menu/menu'
})
}
const showTip1 = () => {
uni.showToast({
title: '您暂时还没有赠送中卡券哦~',
icon: 'none'
})
}
const showTip2 = () => {
uni.showToast({
title: '您暂时还没有券码哦~',
icon: 'none'
})
}
// 领取优惠券
const receive = async(coupon,index) => {
let data = await couponReceive({id:coupon.id});
if (data) {
uToast.value.show({
message: '领取成功',
type: 'success'
});
detailModalVisible.value = false
}
}
</script>
<style lang="scss" scoped>
/* #ifdef H5 */
page {
height: 100%;
}
/* #endif */
.container {
display: flex;
flex-direction: column;
}
.exchange-box {
flex-shrink: 0;
height: 200rpx;
background-color: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.input-box {
display: flex;
align-items: stretch;
width: 70%;
flex-shrink: 0;
input {
flex: 1;
height: 80rpx;
border: 1rpx solid #eee;
border-right: 0;
border-radius: 8rpx 0 0 8rpx;
// padding: 20rpx;
font-size: $font-size-base;
color: $text-color-base;
}
button {
border-radius: 0 8rpx 8rpx 0;
display: flex;
align-items: center;
}
}
}
.tabbar {
flex-shrink: 0;
width: 100%;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
.tab {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: $font-size-base;
color: $text-color-base;
position: relative;
.title {
padding: 15rpx 0;
}
&.active {
color: $color-primary;
.title {
border-bottom: 5rpx solid $color-primary;
}
}
}
}
.bottom-box {
height: 80rpx;
flex-shrink: 0;
.item {
padding: 0 20rpx;
position: relative;
&::after {
content: " ";
border-right: 1rpx solid $text-color-assist;
height: 100%;
position: absolute;
right: 0;
top: 0;
transform: scaleX(0.5) scaleY(0.8);
}
&:nth-last-child(1)::after {
border-right: 0;
}
}
}
.coupon-list {
height: calc(100vh - 120rpx - 200rpx);
/* #ifdef H5 */
height: calc(100vh - 120rpx - 200rpx - 44px);
/* #endif */
}
.wrapper {
padding: 0 20rpx;
display: flex;
flex-direction: column;
.coupon {
display: flex;
flex-direction: column;
background-color: #FFFFFF;
margin-bottom: 30rpx;
//padding: 0 30rpx;
border-radius: 6rpx;
box-shadow: 0 10rpx 10rpx -10rpx rgba(15, 15, 15, 0.1);
position: relative;
&::before {
content: "";
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
left: -15rpx;
border-radius: 100%;
}
&::after {
content: "";
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
right: -15rpx;
border-radius: 100%;
}
.detail {
padding: 20rpx 0;
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-bottom: 1rpx dashed #c6c6c6;
transform: scaleY(0.5);
}
.coupon-img {
width: 150rpx;
height: 150rpx;
margin-right: 40rpx;
}
}
}
}
.use-coupon-btn {
width: 95%;
border-radius: 50rem !important;
}
.taobao {
background-color: white;
.title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
font-size: 30rpx;
.left {
display: flex;
align-items: center;
}
.store {
font-weight: 500;
}
.buddha {
width: 70rpx;
height: 70rpx;
border-radius: 10rpx;
margin-right: 10rpx;
}
.entrance {
color: $uv-info;
border: solid 2rpx $uv-info;
line-height: 48rpx;
padding: 0 30rpx;
background: none;
border-radius: 15px;
}
}
.ticket {
display: flex;
.left {
width: 70%;
padding: 20rpx;
background-color: white;//rgb(255, 245, 244);
border-radius: 20rpx;
border-right: dashed 2rpx rgb(224, 215, 211);
display: flex;
.picture {
//width: 172rpx;
border-radius: 20rpx;
width: 190rpx;
height: 190rpx;
}
.introduce {
margin-left: 10rpx;
.top{
color:$uv-warning;
font-size: 28rpx;
.big{
font-size: 60rpx;
font-weight: bold;
margin-right: 10rpx;
}
}
.type{
font-size: 28rpx;
color: $uv-info-dark;
}
.date{
margin-top: 10rpx;
font-size: 20rpx;
color: $uv-info-dark;
}
}
}
.right {
width: 30%;
padding: 40rpx 20rpx;
background-color: white;//rgb(255, 245, 244);
border-radius: 20rpx;
display: flex;
align-items: center;
.use {
height: auto;
padding: 0 20rpx;
font-size: 24rpx;
border-radius: 40rpx;
color: #ffffff!important;
background-color: $uv-warning!important;
line-height: 40rpx;
color: rgb(117, 142, 165);
margin-left: 20rpx;
}
.used {
height: auto;
padding: 0 20rpx;
font-size: 24rpx;
border-radius: 40rpx;
//color: #ffffff!important;
//background-color: $u-type-warning!important;
line-height: 40rpx;
//color: rgb(117, 142, 165);
margin-left: 20rpx;
}
}
.right_log {
text-align: center;
width: 30%;
padding: 80rpx 0rpx;
background-color: white;//rgb(255, 245, 244);
border-radius: 20rpx;
align-items: center;
}
}
}
</style>
@@ -0,0 +1,326 @@
<template>
<layout>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="wrap">
<view class="top"></view>
<view class="content">
<view class="title">欢迎登录</view>
<input class="u-border-bottom" type="number" v-model="mobile" placeholder="请输入手机号" />
<view class="tips">未注册的手机号验证后自动创建账号</view>
<view style="display: flex;">
<view style="width: 50%;">
<input style="height: 100rpx;" class="u-border-bottom" type="number" v-model="captcha" placeholder="请输入验证码" />
</view>
<view style="width: 50%;">
<button style="height: 100rpx;" @tap="getCaptcha" :style="[captchaStyle]" class="getCaptcha">
{{captchaText}}
<uv-code :seconds="seconds" @end="endCaptcha" @start="startCaptcha" ref="uCode" @change="changeCapcha"></uv-code>
<!-- <u-verification-code :seconds="seconds" @end="endCaptcha" @change="changeCapcha" @start="startCaptcha" ref="uCode" >
</u-verification-code> -->
</button>
</view>
</view>
<button @tap="submit" style="color:#fff;background-color: #f9ae3d;" type="primary" class="login">登录</button>
</view>
<view class="buttom">
<view class="loginType">
<!-- #ifdef MP-WEIXIN -->
<button type="primary" size="default" class="login-btn" open-type="getPhoneNumber" @getphonenumber="loginForWechatMini">
<!-- <image src="/static/images/mine/wechat.png"></image> -->
手机号快捷登录
</button>
<!-- #endif -->
</view>
<view class="hint">
<!-- <label class="label"> -->
<radio value="isChecked" @tap.stop="onChange" />
我已经阅读并遵守
<text class="link" @tap="serv(29,'用户协议')">《用户协议》</text>与
<text class="link" @tap="serv(30,'隐私政策')">《隐私政策》</text>
<!-- </label> -->
</view>
</view>
<uv-toast ref="uToast"></uv-toast>
</view>
</layout>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useMainStore } from '@/store/store'
import {
userLoginForWechatMini,
smsSend,
userLogin
} from '@/api/auth'
import * as util from '@/utils/util'
import { mobile as testMobible } from '@/uni_modules/uv-ui-tools/libs/function/test'
const main = useMainStore()
const title = ref('登录')
const mobile = ref('')
const captcha = ref('')
const captchaText = ref('获取验证码')
const password = ref('')
const seconds = ref(60)
const isChecked = ref(false)
const openid = ref(main.openid)
const uToast = ref()
const uCode = ref()
const captchaStyle = computed(() => {
let style = {};
if(mobile.value && captchaText.value == '获取验证码') {
style.color = "#fff";
style.backgroundColor = '#f9ae3d';
}
return style;
});
const loginForWechatMini = async (e) => {
if(!isChecked.value){
uToast.value.show({
message: '请勾选下面协议',
type: 'error'
});
return
}
if (e.detail.encryptedData && e.detail.iv) {
let data = await userLoginForWechatMini({
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
openid: openid.value
});
if (data) {
main.SET_MEMBER(data.userInfo);
main.SET_TOKEN(data.accessToken);
uToast.value.show({
title: '登录成功',
type: 'success'
});
setTimeout(function() {
uni.navigateBack();
}, 2000);
}
}
}
const getCaptcha = async () => {
if (testMobible(mobile.value) == false) {
uToast.value.show({
message: '手机号码格式不对',
type: 'error'
});
return
}
let data = await smsSend({
mobile: mobile.value,
scene: 1
});
if (data) {
uCode.value.start();
}
}
// 验证码开始计时
const startCaptcha = () => {
console.log('start')
}
// 验证码结束
const endCaptcha = () => {
console.log('end')
captchaText.value = '获取验证码';
}
const changeCapcha = (text) => {
console.log('change:' + text)
captchaText.value = text;
}
// 提交
const submit = () => {
if (testMobible(mobile.value) == false) {
uToast.value.show({
message: '手机号码格式不对',
type: 'error'
});
return
}
if(!isChecked.value){
uToast.value.show({
message: '请勾选下面协议',
type: 'error'
});
return
}
login()
}
// 登录
const login = async () => {
let from = 'routine'
// #ifdef H5
from = 'h5'
if(util.isWeixin()){
from = 'wechat'
}
// #endif
let data = await userLogin({
mobile: mobile.value,
code: captcha.value,
from: from,
openid: openid.value
})
if (data) {
uni.setStorage({
key: 'userinfo',
data: data.userInfo
});
uni.setStorage({
key: 'accessToken',
data: data.accessToken
});
main.SET_MEMBER(data.userInfo);
main.SET_TOKEN(data.accessToken);
uToast.value.show({
message: '登录成功',
type: 'success'
});
setTimeout(function() {
uni.navigateBack();
}, 2000);
}
}
const serv = (id,name) => {
uni.navigateTo({
url: '/pages/components/pages/mine/content?id=' + id + '&name=' + name
})
}
const onChange = () => {
isChecked.value = !isChecked.value
}
</script>
<style lang="scss" scoped>
.wrap {
background-color: #ffffff;
font-size: 28rpx;
position: relative;
height: 100%;
.content {
width: 600rpx;
margin: 0 auto;
.title {
text-align: left;
font-size: 60rpx;
font-weight: 500;
margin-bottom: 100rpx;
}
input {
text-align: left;
margin-bottom: 10rpx;
padding-bottom: 6rpx;
}
.tips {
color: $uv-info;
margin-bottom: 60rpx;
margin-top: 8rpx;
}
.getCaptcha {
background-color: rgb(253, 243, 208);
color: $uv-tips-color;
border: none;
font-size: 30rpx;
padding: 12rpx 0;
&::after {
border: none;
}
}
.login {
background-color: rgb(253, 243, 208);
color: $uv-tips-color;
border: none;
font-size: 30rpx;
padding: 12rpx 0;
margin-top: 40rpx;
&::after {
border: none;
}
}
.alternative {
color: $uv-tips-color;
display: flex;
justify-content: space-between;
margin-top: 30rpx;
}
}
.buttom {
//position: absolute;
bottom: 0;
//display: flex;
//flex-direction: column;
//align-items: center;
//justify-content: center;
.loginType {
padding: 80rpx;
//justify-content:space-between;
.login-btn {
background-color: #1aad19!important;
width: 100%;
border-radius: 50rem !important;
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx 0;
text-align: center;
image {
width: 36rpx;
height: 30rpx;
margin-right: 10rpx;
vertical-align: middle;
}
}
}
.hint {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 40rpx;
font-size: 20rpx;
color: $uv-tips-color;
.link {
color: $uv-warning;
}
}
}
}
</style>
@@ -0,0 +1,54 @@
<template>
<!-- <layout> -->
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="top">
<button type="primary" size="default" class="login-btn" @click="logout">
确定退出登录
</button>
<uv-toast ref="uToast"></uv-toast>
</view>
<!-- </layout> -->
</template>
<script setup>
import {
ref
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
const main = useMainStore()
const { member,isLogin } = storeToRefs(main)
const uToast = ref()
const title = ref('退出')
const logout = () => {
uni.removeStorageSync( 'userinfo');
uni.removeStorageSync( 'accessToken');
main.SET_MEMBER({});
main.SET_TOKEN('');
uToast.value.show({
message: '已退出',
type: 'success'
});
setTimeout(function() {
uni.navigateBack();
}, 2000);
}
</script>
<style lang="scss" scoped>
.top {
display: flex;
height: 100%;
}
button{
margin: auto;
}
</style>
@@ -0,0 +1,48 @@
<template>
<layout>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view>
<rich-text :nodes="content"></rich-text>
</view>
</layout>
</template>
<script setup>
import {
ref
} from 'vue'
import { onLoad,onShow} from '@dcloudio/uni-app'
import {
mineServiceContent
} from '@/api/user'
const title = ref('内容')
const content = ref('')
onLoad((option) => {
if (option.name) {
title.value = option.name
}
if (option.id) {
getContent(option.id);
}
})
const getContent = async(id) => {
let data = await mineServiceContent({id:id});
if (data) {
content.value = data.content;
}
}
</script>
<style lang="scss" scoped>
</style>
@@ -0,0 +1,236 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container d-flex flex-column">
<view class="flex-fill form">
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">头像</view>
<view class="input flex-fill">
<view class="form-input w-100 d-flex align-items-center" style="position: relative;">
<view class="flex user-box">
<view class="mr-1">
<uv-avatar :src="member.avatar" size="60"></uv-avatar>
</view>
<view v-if="!member.avatar" class="flex-1" style="right:0;height: 60rpx;">
<button style="background-color: #dd524d;color:#fff;" size='mini' open-type="chooseAvatar" @chooseavatar="chooseavatar" type="success">更改头像</button>
</view>
</view>
</view>
</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">昵称</view>
<view class="input flex-fill">
<input type="nickname" placeholder="请填写昵称" placeholder-class="text-color-assist font-size-base"
v-model="member.nickname">
</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center" style="position: relative;">
<view class="label">手机号码</view>
<view class="input flex-fill">
<input type="text" v-model="member.mobile" disabled>
</view>
</view>
</list-cell>
<list-cell :hover="false">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">性别</view>
<view class="input flex-fill">
<view class="radio-group">
<view class="radio" :class="{'checked': member.gender == '0'}" style="margin-right: 10rpx;" @tap="member.gender=0">先生</view>
<view class="radio" :class="{'checked': member.gender == '1'}" @tap="member.gender=1">女士</view>
</view>
</view>
</view>
</list-cell>
<list-cell :hover="false" :arrow="!member.birthday">
<view class="form-input w-100 d-flex align-items-center">
<view class="label">生日</view>
<view class="input flex-fill">
<picker mode="date" :value="member.birthday" :start="startDate" :end="endDate" @change="handleDateChange">
{{member.birthday ? member.birthday : '无'}}
</picker>
</view>
</view>
</list-cell>
<list-cell :hover="false" last>
<view class="form-input w-100 d-flex align-items-center">
<view class="label">加入时间</view>
<view class="input flex-fill">
<input type="text" v-model="member.createTime" disabled>
</view>
</view>
</list-cell>
</view>
<view class="btn-box d-flex align-items-center just-content-center">
<button type="primary" class="save-btn" @tap="save">保存</button>
</view>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow} from '@dcloudio/uni-app'
import { formatDateTime } from '@/utils/util'
import {
userEdit
} from '@/api/user'
import { VUE_APP_UPLOAD_URL } from '@/config';
const main = useMainStore()
const { openid, lang } = storeToRefs(main)
const title = ref('用户设置')
//const currentDate = ref('')
const date = ref('')
const member = ref({})
onLoad(() => {
member.value = main.member;
//this.$util
member.value.createTime = formatDateTime(member.value.createTime);
})
onShow(() => {
date.value = getDate({format: true})
})
const startDate = computed(() => {
return getDate('start');
})
const endDate = computed(() => {
return getDate('end');
})
const chooseavatar = (e) => {
console.log('detal:',e.detail);
uni.uploadFile({
url: VUE_APP_UPLOAD_URL,
filePath: e.detail.avatarUrl,
name: 'file',
header: {
Authorization: 'Bearer ' + member.value.accessToken,
lang: lang.value,
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
success(uploadFileResult){
console.log('uploadFileResult:',uploadFileResult)
if (uploadFileResult) {
const upload = JSON.parse(uploadFileResult.data);
console.log('upload:',upload.data)
member.value.avatar = upload.data;
}
},
fail(e){
console.log('网络链接错误');
console.log(e)
}
});
}
const getDate = (type) => {
const date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
if (type === 'start') {
year = year - 60;
} else if (type === 'end') {
year = year + 2;
}
month = month > 9 ? month : '0' + month;;
day = day > 9 ? day : '0' + day;
return `${year}-${month}-${day}`;
}
const handleDateChange = (e) => {
member.value.birthday = e.target.value
}
const save = async() => {
let data = await userEdit({
nickname: member.value.nickname,
mobile: member.value.mobile,
gender: member.value.gender,
birthday: member.value.birthday,
avatar: member.value.avatar
});
if (data) {
const member = Object.assign(main.member, member.value)
main.SET_MEMBER(member)
}
}
</script>
<style lang="scss" scoped>
page {
height: 100%;
}
.container {
padding: 20rpx 30rpx;
}
.form {
border-radius: 8rpx;
}
.form-input {
.label {
width: 160rpx;
font-size: $font-size-base;
color: $text-color-base;
}
.input {
}
.radio-group {
display: flex;
justify-content: flex-start;
.radio {
padding: 10rpx 30rpx;
border-radius: 6rpx;
border: 2rpx solid $text-color-assist;
color: $text-color-assist;
font-size: $font-size-base;
&.checked {
background-color: $color-primary;
color: $text-color-white;
border: 2rpx solid $color-primary;
}
}
}
}
.btn-box {
height: calc((100vh - 40rpx) / 2);
}
.save-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
}
.logout-btn {
width: 90%;
border-radius: 50rem !important;
font-size: $font-size-lg;
background-color:rgb(237, 81, 29);
}
</style>
@@ -0,0 +1,488 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container" style="padding:20rpx;">
<view style="padding-bottom: 100rpx;">
<view class="bg-white">
<view class="section">
<!-- store info begin -->
<list-cell :hover="false">
<view class="w-100 d-flex align-items-center">
<view class="d-flex flex-column w-60">
<view class="w-100 font-size-lg text-color-base text-truncate">{{ order.shop.name }}</view>
</view>
<view class="d-flex justify-content-end align-items-center w-40">
<view class="iconfont-yshop icon-mobile" @click="makePhoneCall(order.shop)" style="font-size: 45rpx;margin-right: 40rpx;"></view>
<view class="iconfont-yshop icon-location" @click="openLocation(order.shop)" style="font-size: 45rpx;"></view>
</view>
</view>
</list-cell>
<!-- store info end -->
<list-cell :hover="false" padding="50rpx 30rpx">
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center just-content-center">
<view class="sort-num">{{ order.numberId }}</view>
</view>
<!-- steps begin -->
<view class="d-flex just-content-center">
<view class="steps d-flex flex-column w-80">
<view class="steps__img-column">
<view class="steps__img-column-item">
<view class="iconfont-yshop icon-lamp"></view>
</view>
<view class="steps__img-column-item">
<view class="iconfont-yshop icon-daojishi" v-if="{active: order.paid == 1 && order.status == 0}"></view>
<view class="iconfont-yshop icon-daojishi unactive" v-else></view>
</view>
<view class="steps__img-column-item" v-if="order.orderType == 'takeout'">
<view class="iconfont-yshop icon-takeout" v-if="order.status == 1"></view>
<view class="iconfont-yshop icon-takeout unactive" v-else></view>
</view>
<view class="steps__img-column-item" >
<view class="iconfont-yshop icon-doorbell" v-if="order.status >= 2"></view>
<view class="iconfont-yshop icon-doorbell unactive" v-else></view>
</view>
</view>
<view class="steps__text-column">
<view class="steps__text-column-item active">
<view class="steps__column-item-line bg-transparent"></view>
<view class="steps__text-column-item-text">已下单</view>
<view class="steps__column-item-line"></view>
</view>
<view class="steps__text-column-item activ" :class="{active: order.paid == 1}">
<view class="steps__column-item-line"></view>
<view class="steps__text-column-item-text">制作中</view>
<view class="steps__column-item-line"></view>
</view>
<view class="steps__text-column-item" :class="{active: order.status == 1}" v-if="order.orderType == 'takeout'">
<view class="steps__column-item-line"></view>
<view class="steps__text-column-item-text">配送中</view>
<view class="steps__column-item-line bg-transparent"></view>
</view>
<view class="steps__text-column-item" :class="{active: order.status >= 2}">
<view class="steps__column-item-line"></view>
<view class="steps__text-column-item-text">
{{ order.orderType == 'takeout' ? '已送达' : '请取餐' }}
</view>
<view class="steps__column-item-line bg-transparent"></view>
</view>
</view>
</view>
</view>
<!-- steps end -->
<view v-if="order.status==0 && order.paid > 0" class="d-flex just-content-center align-items-center font-size-base text-color-assist mb-40">
您前面还有 <text class="text-color-primary mr-10 ml-10">{{order.preNum}}</text> 单待制作
</view>
<!-- goods begin -->
<view class="w-100 d-flex flex-column position-relative mt-30" style="margin-bottom: -40rpx;">
<view class="w-100 d-flex align-items-center mb-40" v-for="(good, index) in order.products" :key="index">
<view class="d-flex flex-column w-60 overflow-hidden">
<view class="font-size-lg text-color-base mb-10 text-truncate">{{ good.title }}</view>
<view class="font-size-sm text-color-assist text-truncate">{{ good.spec }}</view>
</view>
<view class="d-flex w-40 align-items-center justify-content-between pl-30">
<view class="font-size-base text-color-base">x{{ good.number }}</view>
<view class="font-size-base text-color-base font-weight-bold">¥{{ good.price }}</view>
</view>
</view>
</view>
<!-- goods end -->
</view>
</list-cell>
</view>
<view class="section">
<!-- goods begin -->
<list-cell :hover="false" padding="30rpx 30rpx">
<view class="w-100 d-flex flex-column position-relative">
<view class="w-100 d-flex align-items-center mb-40" v-for="(good, index) in order.cartInfo" :key="index">
<image :src="good.image" mode="aspectFill" class="image"></image>
<view class="d-flex flex-column w-60 overflow-hidden">
<view class="font-size-lg text-color-base mb-10 text-truncate">{{ good.title }}</view>
<view class="font-size-sm text-color-assist text-truncate">{{ good.spec }}</view>
</view>
<view class="d-flex w-40 align-items-center justify-content-between pl-30">
<view class="font-size-base text-color-base">x{{ good.number }}</view>
<view class="font-size-base text-color-base font-weight-bold">¥{{ good.price }}</view>
</view>
</view>
</view>
</list-cell>
<!-- goods end -->
<!-- payment and amount begin -->
<list-cell :hover="false" padding="50rpx 30rpx">
<view class="w-100 d-flex flex-column">
<view class="pay-cell">
<view>支付方式</view>
<view class="font-weight-bold">{{ order._status._payType }}</view>
</view>
<view class="pay-cell">
<view>订单金额</view>
<view class="font-weight-bold">¥{{ order.totalPrice }}</view>
</view>
<view class="pay-cell" v-if="order.orderType == 'takeout'">
<view>配送费</view>
<view class="font-weight-bold">¥{{ order.payPostage }}</view>
</view>
<view class="pay-cell">
<view>优惠金额</view>
<view class="font-weight-bold">¥{{ order.couponPrice }}</view>
</view>
<view class="pay-cell">
<view>实付金额</view>
<view class="font-weight-bold">¥{{ order.payPrice }}</view>
</view>
</view>
</list-cell>
<!-- payment and amount end -->
</view>
<view class="section">
<!-- order info begin -->
<list-cell :hover="false" padding="50rpx 30rpx">
<view class="w-100 d-flex flex-column">
<view class="pay-cell">
<view>下单时间</view>
<view class="font-weight-bold">{{ formatDateTime(order.createTime )}}</view>
</view>
<view class="pay-cell">
<view>下单门店</view>
<view class="font-weight-bold">{{ order.shop.name }}</view>
</view>
<view class="pay-cell">
<view>订单号</view>
<view class="font-weight-bold">{{ order.id }}</view>
</view>
</view>
</list-cell>
<!-- order info end -->
</view>
<!-- order other info begin -->
<list-cell :hover="false" padding="50rpx 30rpx 20rpx" last>
<view class="w-100 d-flex flex-column">
<view class="pay-cell">
<view>享用方式</view>
<view class="font-weight-bold">{{order.orderType == 'takein' ? '自取' : '外卖'}}</view>
</view>
<view class="pay-cell">
<view>取餐时间</view>
<view class="font-weight-bold">{{order.getTime ? formatDateTime(order.getTime) : '立即取餐'}}</view>
</view>
<view class="pay-cell">
<view>制作完成时间</view>
<view class="font-weight-bold">{{ order.deliveryTime ? formatDateTime(order.deliveryTime) : '无' }}</view>
</view>
<view class="pay-cell">
<view>备注</view>
<view class="font-weight-bold">{{ order.remark ? order.remark : '无' }}</view>
</view>
</view>
</list-cell>
<!-- order other info end -->
</view>
<view class="fixed-bottom flex justify-end bg-white p-2" v-if="order.paid > 0 && order.refundStatus == 0">
<view class="mr-1"><uv-button type="success" v-if="order.status < 2" :plain="true" size="small" text="确认收到餐" @click="receive(order)"></uv-button></view>
<view><uv-button type="warning" :plain="true" size="small" text="申请退款" @click="refund(order)"></uv-button></view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import { onLoad} from '@dcloudio/uni-app'
import { formatDateTime } from '@/utils/util'
import {
orderDetail,
orderReceive,
} from '@/api/order'
const title = ref('订单详情')
const order = ref({
shop:{name:''},
_status:{_payType:''}
})
const numForMading = ref(5)
onLoad((option) => {
detail(option.id);
})
const detail = async(id) => {
let data = await orderDetail(id);
if (data) {
order.value = data;
}
}
const openLocation = () => {
let shop = order.value.shop;
uni.openLocation({
address: shop.addressMap + shop.address + " " + shop.name,
latitude: parseFloat(shop.lat),
longitude: parseFloat(shop.lng),
fail(res) {
console.log(res);
}
});
}
const makePhoneCall = () =>{
let shop = order.value.shop;
uni.makePhoneCall({
phoneNumber: shop.mobile,
fail(res) {
console.log(res)
}
})
}
// 确认收到货
const receive = async(order) => {
let data = await orderReceive({uni:order.orderId});
if (data) {
await getOrders(true)
}
}
//提交退款
const refund = (order) => {
uni.navigateTo({
url: '/pages/components/pages/orders/refund?orderId=' + order.orderId + '&payPrice=' + order.payPrice + '&totalPrice=' + order.totalPrice
})
}
</script>
<style lang="scss" scoped>
.image {
width: 120rpx;
height: 120rpx;
margin-right: 30rpx;
border-radius: 8rpx;
}
// @mixin arch {
// content: "";
// position: absolute;
// background-color: $bg-color;
// width: 30rpx;
// height: 30rpx;
// bottom: -15rpx;
// z-index: 10;
// border-radius: 100%;
// }
// .section {
// position: relative;
// &::before {
// @include arch;
// left: -15rpx;
// }
// &::after {
// @include arch;
// right: -15rpx;
// }
// }
.pay-cell {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
font-size: $font-size-base;
color: $text-color-base;
margin-bottom: 40rpx;
&:nth-last-child(1) {
margin-bottom: 0;
}
}
.invote-box {
position: absolute;
width: 100%;
left: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
image {
width: 30rpx;
height: 30rpx;
}
}
.btn-box {
background-color: #ffffff;
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.1);
display: flex;
align-items: center;
justify-content: space-evenly;
z-index: 11;
.item {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 10rpx;
flex: 1;
flex-shrink: 0;
button {
width: 100%;
border-radius: 50rem !important;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
}
}
/* #ifdef H5 */
page {
min-height: 100%;
background-color: $bg-color;
}
/* #endif */
.order-box {
padding: 20rpx;
/* #ifdef H5 */
margin-bottom: 100rpx;
/* #endif */
}
.drinks-img {
width: 260rpx;
height: 260rpx;
}
.tips {
margin: 60rpx 0 80rpx;
line-height: 48rpx;
}
.drink-btn {
width: 320rpx;
border-radius: 50rem !important;
margin-bottom: 40rpx;
font-size: $font-size-base;
line-height: 3.0;
}
@mixin arch {
content: "";
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: -15rpx;
z-index: 10;
border-radius: 100%;
}
.section {
position: relative;
&::before {
@include arch;
left: -15rpx;
}
&::after {
@include arch;
right: -15rpx;
}
}
.pay-cell {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
font-size: $font-size-base;
color: $text-color-base;
margin-bottom: 40rpx;
&:nth-last-child(1) {
margin-bottom: 0;
}
}
.sort-num {
font-size: 64rpx;
font-weight: bold;
color: $text-color-base;
line-height: 2;
}
.steps__img-column {
display: flex;
margin: 30rpx 0;
.steps__img-column-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
image {
width: 80rpx;
height: 80rpx;
}
.unactive {
color: #919293;
}
}
}
.steps__text-column {
display: flex;
margin-bottom: 40rpx;
.steps__text-column-item {
flex: 1;
display: inline-flex;
display: flex;
align-items: center;
justify-content: center;
font-size: $font-size-base;
color: $text-color-assist;
&.active {
color: $text-color-base;
font-weight: bold;
.steps__column-item-line {
background-color: $text-color-base;
}
}
.steps__column-item-line{
flex: 1;
height: 2rpx;
background-color: #919293;
transform: scaleY(0.5);
}
.steps__text-column-item-text {
margin: 0 8px;
}
}
}
.icon-lamp, .icon-daojishi, .icon-takeout, .icon-doorbell{
font-size: 60rpx;
}
.iconfont-yshop {
color: #dd524d;
}
</style>
@@ -0,0 +1,181 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container">
<view class="bg-white">
<uv-tabs :list="tabList" :current="current" @change="change" keyName="name" :scrollable="false"></uv-tabs>
</view>
<view class="orders-list d-flex flex-column w-100" style="padding: 20rpx; padding-bottom: 0;">
<view class="order-item" v-for="(item, index) in orders" :key="index" style="margin-bottom: 30rpx;" @tap="detail(item.orderId)">
<list-cell :hover="false">
<view class="w-100 d-flex align-items-center">
<view class="flex-fill d-flex flex-column">
<view class="font-size-lg text-color-base" style="margin-bottom: 20rpx;">
{{ item.shop.name }}
</view>
<view class="font-size-sm text-color-assist">取餐号:{{ item.numberId }}</view>
<view class="font-size-sm text-color-assist">订单编号:{{ item.orderId }}</view>
</view>
<view class="font-size-lg text-color-primary">
{{ item._status._title }}
</view>
</view>
</list-cell>
<list-cell :hover="false" last>
<view class="w-100 d-flex flex-column">
<view class="w-100 text-truncate font-size-lg text-color-base" style="margin-bottom: 20rpx;">
<!-- {{ orderGoodsName(item.cartInfo) }} -->
<view class="flex mb-2" v-for="(good,index) in item.cartInfo" :key="index">
<image :src="good.image" mode="aspectFill" class="image"></image>
<view class="flex flex-column">
<view class="font-size-medium mt-1 text-color-base">{{ good.title }}</view>
<view class="font-size-sm mt-1">{{ good.spec }}</view>
<view class="font-size-sm mt-2">×{{ good.number }} ¥{{ good.price }}</view>
</view>
</view>
</view>
<view class="d-flex justify-content-between align-items-center" style="margin-bottom: 30rpx;">
<view class="font-size-sm text-color-assist">
{{formatDateTime(item.createTime) }}
</view>
<view class="d-flex font-size-sm text-color-base align-items-center">
<view style="margin-right: 10rpx;">共{{ goodsNum(item.cartInfo) }}件商品,实付</view>
<view class="font-size-lg">¥{{ item.payPrice }}</view>
</view>
</view>
<view class="d-flex align-items-center justify-content-end">
<view>
<button v-if="item.paid > 0 && item.status < 2 && item.refundStatus == 0" class="left-margin" plain size="mini" @tap.stop="receive(item)">确认收到餐</button>
<button class="left-margin" plain size="mini" @tap="detail(item.orderId)">订单详情</button>
</view>
</view>
</view>
</list-cell>
</view>
</view>
<uv-empty v-if="orders.length == 0" mode="order"></uv-empty>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'
import { formatDateTime,kmUnit } from '@/utils/util'
import {
orderGetOrders,
orderReceive
} from '@/api/order'
const main = useMainStore()
const { isLogin } = storeToRefs(main)
const title = ref('我的订单')
const page = ref(1)
const pageSize = ref(10)
const orders = ref([])
const tabList = ref([{
type: -1,
name: '全部',
}, {
type: 0,
name: '待支付',
}, {
type: 1,
name: '进行中'
}, {
type: 4,
name: '已完成'
}, {
type: -3,
name: '退款单'
}]
)
const current = ref(0)
const type = ref(-1)
const orderGoodsName = computed(() => { //计算单个饮品添加到购物车的数量
return (goods) => {
let arr = []
goods.forEach(good => arr.push(good.title + '*' + good.number))
return arr.join(',')
}
})
const goodsNum = computed(() => { //计算单个饮品添加到购物车的数量
return (goods) => {
let num = 0;
goods.forEach(good => num += parseInt(good.number))
return num;
}
})
onLoad(() => {
if(!isLogin.value) {
uni.navigateTo({url: '/pages/components/pages/login/login'})
}
getOrders(false)
})
onPullDownRefresh(() => {
getOrders(false)
})
onReachBottom(() => {
getOrders(false)
})
// tab栏切换
const change = (e) => {
type.value = e.type
getOrders(true)
}
const getOrders = async(isRefresh = false) => {
uni.showLoading({
title: '加载中'
})
if(isRefresh) {
orders.value = []
page.value = 1
}
let ordersData = await orderGetOrders({page:page.value, limit:pageSize.value,type:type.value});
if(ordersData) {
orders.value = orders.value.concat(ordersData)
page.value += 1
}
uni.stopPullDownRefresh();
uni.hideLoading()
}
const detail = (id) => {
uni.navigateTo({
url: '/pages/components/pages/orders/detail?id=' + id
})
}
// 确认收到货
const receive = async(order) => {
let data = await orderReceive({uni:order.orderId});
if (data) {
await getOrders(true)
}
}
</script>
<style lang="scss" scoped>
.left-margin {
margin-left: 10rpx;
}
.image {
width: 160rpx;
height: 160rpx;
margin-right: 30rpx;
border-radius: 8rpx;
}
</style>
@@ -0,0 +1,59 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="p-2">
<uv-list>
<uv-list-item title="订单金额" :rightText="'¥'+totalPrice"></uv-list-item>
<uv-list-item title="退款金额" :rightText="'¥'+payPrice"></uv-list-item>
<uv-list-item title="退款类型" rightText="整个订单"></uv-list-item>
<uv-list-item title="退款原因" rightText="协商一致"></uv-list-item>
<uv-list-item title="退款留言" >
<template #footer><uv-input placeholder="请输入内容" border="none" v-model="refundReasonWapExplain"></uv-input></template>
</uv-list-item>
</uv-list>
<view class="mt-2">
<uv-button shape="circle" type="warning" :plain="true" size="small" text="申请退款" @click="refund()"></uv-button>
</view>
<uv-toast ref="toast"></uv-toast>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import { onLoad} from '@dcloudio/uni-app'
import {
orderRefund
} from '@/api/order'
const title = ref('申请退款')
const orderId = ref('')
const payPrice = ref('')
const totalPrice = ref('')
const refundReasonWapExplain = ref('')
const toast = ref()
onLoad((option) => {
console.log('option:',option)
orderId.value = option.orderId
payPrice.value = option.payPrice
totalPrice.value = option.totalPrice
})
const refund = async() => {
let data = await orderRefund({refundReasonWapExplain:refundReasonWapExplain.value,uni:orderId.value,text:'协商一致'});
if (data) {
uni.showToast({
title: '提交成功',
icon: 'success'
})
}
}
</script>
<style>
</style>
@@ -0,0 +1,395 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container position-relative w-100 h-100 overflow-hidden">
<uv-empty v-if="coupons.length == 0" mode="coupon"></uv-empty>
<scroll-view scroll-y class="coupon-list">
<view class="wrapper">
<view class="coupon" v-for="(item, index) in coupons" :key="index" @tap="openDetailModal(item, index)">
<view class="taobao">
<view class="ticket" :style="{border: item.id == coupon_id ? '1rpx solid red':''}">
<view class="left">
<image class="picture" :src="item.image" mode="aspectFill"></image>
<view class="introduce">
<view class="top">
¥
<text class="big">{{ item.value }}</text>
<view>满{{ item.least }}减{{ item.value }}</view>
</view>
<view class="type">{{ item.title }}</view>
<view class="date u-line-1">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>
</view>
</view>
<view class="right" @click.stop="" v-if="activeTabIndex == 0">
<view v-if="item.id != coupon_id" class="use immediate-use" :round="true" @tap="useCouponWith(item)">立即使用</view>
<view v-else class="use immediate-use" :round="true" @tap="cancelCoupon(item)">取消使用</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<modal custom :show="detailModalVisible" @cancel="closeDetailModal" width="90%">
<view class="modal-content">
<view class="d-flex font-size-extra-lg text-color-base just-content-center mb-20">{{ coupon.title }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">有效期:{{formatDateTime(coupon.startTime, 'yyyy-MM-dd')}}至{{formatDateTime(coupon.endTime, 'yyyy-MM-dd')}}</view>
<view class="d-flex font-size-sm text-color-base mb-20">领取时间:{{ coupon.createtime_text }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">卷价值:满{{ coupon.least }}减{{ coupon.value }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">适用范围:{{ typeInfo(coupon.type) }}</view>
<view class="d-flex font-size-sm text-color-base mb-20">适用店铺:{{ coupon.shopName }}</view>
<view class="d-flex align-items-center just-content-center" v-if="activeTabIndex == 0">
<button type="primary" @tap="useCoupon" class="use-coupon-btn">立即使用</button>
</view>
</view>
</modal>
<!--轻提示-->
<uv-toast ref="uToast"></uv-toast>
</view>
</template>
<script setup>
import {
ref,
watch,
toRaw
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'
import { formatDateTime,prePage } from '@/utils/util'
import {
couponMine
} from '@/api/coupon'
const main = useMainStore()
const { isLogin } = storeToRefs(main)
const title = ref('优惠券')
const activeTabIndex = ref(0)
const detailModalVisible = ref(false)
const coupon = ref({})
const couponIndex = ref(0)
const coupons = ref([])
const amount = ref(0)
const buttonLock = ref(false)
const coupon_id = ref(0)
const shop_id = ref(0)
const type = ref(0)
const uToast = ref()
onLoad((options) => {
if (options.amount) {
amount.value = options.amount;
}
if (options.coupon_id) {
coupon_id.value = options.coupon_id
}
if (options.shop_id) {
shop_id.value = options.shop_id;
}
if (options.type) {
type.value = options.type;
}
activeTabIndex.value = 0;
getCoupons();
})
onPullDownRefresh(() => {
getCoupons();
})
// 使用范围
const typeInfo = (type) => {
if (type == 0) {
return '外卖和自取';
}
if (type == 1) {
return '自取';
}
if (type == 2) {
return '外卖';
}
}
const getCoupons = async() => {
let data = await couponMine({shop_id: shop_id.value, type: type.value, page:1, pagesize:10000});
uni.stopPullDownRefresh();
if (data) {
coupons.value = data;
}
}
const openDetailModal = (mycoupon, index) => {
couponIndex.value = index;
coupon.value = mycoupon;
detailModalVisible.value = true;
}
// 使用优惠券
const useCouponWith = (mycoupon) => {
coupon.value = mycoupon;
useCoupon();
}
// 取消优惠券
const cancelCoupon = () => {
coupon.value = {}
coupon_id.value = 0
prePage().coupon = {}
}
const closeDetailModal = () => {
detailModalVisible.value = false;
coupon.value = {};
}
// 使用优惠及
const useCoupon = () => {
if (buttonLock.value == true) {
return;
}
buttonLock.value = true
//console.log('coupon:',coupon.value);
if (parseFloat(coupon.value.least) > parseFloat(amount.value)) {
//console.log('pages3:');
uToast.value.show({
message: '订单金额满'+coupon.value.least+'才能使用',
type: 'error'
});
buttonLock.value = false
} else {
main.SET_COUPON(coupon)
console.log('main.myconpon:',main.mycoupon)
//prePage().coupon = coupon.value;
//prePage().coupons = 1; // 哨兵
uni.navigateBack({
})
}
}
</script>
<style lang="scss" scoped>
/* #ifdef H5 */
page {
height: 100%;
}
/* #endif */
.container {
display: flex;
flex-direction: column;
}
.bottom-box {
height: 80rpx;
flex-shrink: 0;
.item {
padding: 0 20rpx;
position: relative;
&::after {
content: ' ';
border-right: 1rpx solid $text-color-assist;
height: 100%;
position: absolute;
right: 0;
top: 0;
transform: scaleX(0.5) scaleY(0.8);
}
&:nth-last-child(1)::after {
border-right: 0;
}
}
}
.coupon-list {
margin-top: 30rpx;
height:calc(100vh - 120rpx);
// height: calc(100vh - 120rpx - 200rpx);
/* #ifdef H5 */
// height: calc(100vh - 120rpx - 200rpx - 44px);
/* #endif */
}
.wrapper {
padding: 0 20rpx;
display: flex;
flex-direction: column;
.coupon {
display: flex;
flex-direction: column;
background-color: #ffffff;
margin-bottom: 30rpx;
//padding: 0 30rpx;
border-radius: 6rpx;
box-shadow: 0 10rpx 10rpx -10rpx rgba(15, 15, 15, 0.1);
position: relative;
&::before {
content: '';
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
left: -15rpx;
border-radius: 100%;
}
&::after {
content: '';
position: absolute;
background-color: $bg-color;
width: 30rpx;
height: 30rpx;
bottom: 65rpx;
right: -15rpx;
border-radius: 100%;
}
.detail {
padding: 20rpx 0;
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-bottom: 1rpx dashed #c6c6c6;
transform: scaleY(0.5);
}
.coupon-img {
width: 150rpx;
height: 150rpx;
margin-right: 40rpx;
}
}
}
}
.use-coupon-btn {
width: 95%;
border-radius: 50rem !important;
}
.taobao {
background-color: white;
.title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
font-size: 30rpx;
.left {
display: flex;
align-items: center;
}
.store {
font-weight: 500;
}
.buddha {
width: 70rpx;
height: 70rpx;
border-radius: 10rpx;
margin-right: 10rpx;
}
.entrance {
color: $uv-info;
border: solid 2rpx $uv-info;
line-height: 48rpx;
padding: 0 30rpx;
background: none;
border-radius: 15px;
}
}
.ticket {
display: flex;
.left {
width: 70%;
padding: 20rpx;
background-color: white; //rgb(255, 245, 244);
border-radius: 20rpx;
border-right: dashed 2rpx rgb(224, 215, 211);
display: flex;
.picture {
//width: 172rpx;
border-radius: 20rpx;
width: 190rpx;
height: 190rpx;
}
.introduce {
margin-left: 10rpx;
.top {
color: $uv-warning;
font-size: 28rpx;
.big {
font-size: 60rpx;
font-weight: bold;
margin-right: 10rpx;
}
}
.type {
font-size: 28rpx;
color: $uv-info-dark;
}
.date {
margin-top: 10rpx;
font-size: 20rpx;
color: $uv-info-dark;
}
}
}
.right {
width: 30%;
padding: 40rpx 20rpx;
background-color: white; //rgb(255, 245, 244);
border-radius: 20rpx;
display: flex;
align-items: center;
.use {
height: auto;
padding: 0 20rpx;
font-size: 24rpx;
border-radius: 40rpx;
color: #ffffff !important;
background-color: $uv-warning !important;
line-height: 40rpx;
color: rgb(117, 142, 165);
margin-left: 20rpx;
}
.used {
height: auto;
padding: 0 20rpx;
font-size: 24rpx;
border-radius: 40rpx;
//color: #ffffff!important;
//background-color: $u-type-warning!important;
line-height: 40rpx;
//color: rgb(117, 142, 165);
margin-left: 20rpx;
}
}
.right_log {
text-align: center;
width: 30%;
padding: 80rpx 0rpx;
background-color: white; //rgb(255, 245, 244);
border-radius: 20rpx;
align-items: center;
}
}
}
</style>
@@ -0,0 +1,814 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container position-relative">
<view style="margin-bottom: 130rpx;">
<view class="section-1">
<template v-if="store.distance > 0">
<list-cell class="location">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="store-name flex-fill">{{ orderType == 'takeout' ? '外卖配送' : '点餐自取' }}</view>
<uv-switch activeColor="#dd524d" v-model="active" @change="takout">
</uv-switch>
</view>
</list-cell>
</template>
<template v-if="orderType == 'takeout'">
<list-cell @click="chooseAddress">
<view v-if="address.realName" class="w-100 d-flex flex-column">
<view class="d-flex align-items-center justify-content-between mb-10">
<view class="font-size-lg text-color-base">
{{ address.address + ' ' + address.detail }}
</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
<view class="d-flex text-color-assist font-size-sm align-items-center">
<view class="mr-10">{{ address.realName }}</view>
<view>{{ address.phone }}</view>
</view>
</view>
<view v-else class="flex-fill d-flex justify-content-between align-items-center">
<view class="store-name flex-fill">选择收货地址</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
</list-cell>
</template>
</view>
<view class="section-1">
<template>
<list-cell class="location" @click="goToShop">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="store-name flex-fill">{{ store.name }}</view>
<image src="/static/images/navigator-1.png" class="arrow"></image>
</view>
</list-cell>
</template>
<template>
<list-cell arrow class="meal-time" v-if="orderType == 'takein'">
<view class="flex-fill d-flex justify-content-between align-items-center"
@click="takeinTIme = !takeinTIme">
<view class="title">取餐时间</view>
<view class="time">
{{ takeinRange[defaultSelector[0]].name }}
<u-picker v-model="takeinTIme" :range="takeinRange" range-key="name" mode="selector"
@cancel="takeinCancelTime" @confirm="takeinConfirmTime"
:default-selector="defaultSelector"></u-picker>
</view>
</view>
</list-cell>
<list-cell class="contact" last :hover="false" v-if="orderType == 'takein'">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="title flex-fill">联系电话</view>
<view class="time"><input class="text-right" placeholder="请输入手机号码" :value="member.mobile" />
</view>
<button class="contact-tip font-size-sm">自动填写</button>
</view>
</list-cell>
</template>
<template v-if="orderType == 'takeout'">
<list-cell>
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center font-size-base text-color-base">
<view class="flex-fill">预计送达时间</view>
<view class="mr-10">
{{ defaultTime }}
<u-picker :default-time="defaultTime" v-model="takeoutTIme" :params="paramsTime"
mode="time" @cancel="cancelTime" @confirm="choiceTime"></u-picker>
</view>
</view>
</view>
</list-cell>
</template>
</view>
<!-- 购物车列表 begin -->
<view class="section-2">
<view class="cart d-flex flex-column">
<list-cell last v-for="(item, index) in cart" :key="index">
<view class="w-100 d-flex flex-column">
<view class="d-flex align-items-center mb-10">
<view
class="d-flex flex-fill justify-content-between align-items-center text-color-base font-size-lg">
<image style="width: 80rpx;height: 80rpx;" mode="aspectFill" :src="item.image">
</image>
</view>
<view class="name-and-props overflow-hidden">
<view class="text-color-base font-size-lg">{{ item.name }}</view>
</view>
<view
class="d-flex flex-fill justify-content-between align-items-center text-color-base font-size-lg">
<view>x{{ item.number }}</view>
<view>¥{{ item.price }}</view>
</view>
</view>
<view class="text-truncate font-size-base text-color-assist">{{ item.valueStr }}</view>
</view>
</list-cell>
</view>
<list-cell arrow @click="goToPackages">
<view class="flex-fill d-flex justify-content-between align-items-center">
<view class="text-color-base">优惠券</view>
<view v-if="coupons == 0" class="text-color-base">暂无可用</view>
<view v-else-if="coupon.title" class="text-color-danger">
{{ coupon.title }}(满{{ coupon.least }}减{{ coupon.value }})
</view>
<view v-else class="text-color-primary">可用优惠券{{ coupons }}张</view>
</view>
</list-cell>
<list-cell last>
<view class="flex-fill d-flex justify-content-end align-items-center">
<view>
总计¥{{ total }}
<text v-if="orderType == 'takeout'">,配送费¥{{ store.deliveryPrice }}</text>
<text v-if="coupon.value">,¥-{{ coupon.value }}</text>
,实付
</view>
<view class="font-size-extra-lg font-weight-bold">¥{{ amount }}</view>
</view>
</list-cell>
</view>
<!-- 购物车列表 end -->
<view class="d-flex align-items-center justify-content-start font-size-sm text-color-warning"
style="padding: 20rpx 0;">
</view>
<!-- 支付方式 begin -->
<view class="payment">
<list-cell last :hover="false"><text>支付方式</text></list-cell>
<list-cell>
<view class="d-flex align-items-center justify-content-between w-100 disabled"
@click="setPayType('yue')">
<view class="iconfont iconbalance line-height-100 payment-icon"></view>
<view class="flex-fill">余额支付(余额¥{{ member.nowMoney }})</view>
<view class="font-size-sm" v-if="member.nowMoney == 0">余额不足</view>
<view class="iconfont line-height-100 checkbox checked iconradio-button-on" v-if="payType == 'yue'">
</view>
<view class="iconfont line-height-100 checkbox iconradio-button-off" v-else></view>
</view>
</list-cell>
<list-cell last>
<view class="d-flex align-items-center justify-content-between w-100" @click="setPayType('weixin')">
<view class="iconfont iconwxpay line-height-100 payment-icon" style="color: #7EB73A"></view>
<view class="flex-fill">微信支付</view>
<view class="iconfont line-height-100 checkbox checked iconradio-button-on" v-if="payType == 'weixin'">
</view>
<view class="iconfont line-height-100 checkbox iconradio-button-off" v-else></view>
</view>
</list-cell>
<!-- #ifdef H5 -->
<list-cell>
<view class="d-flex align-items-center justify-content-between w-100" @click="setPayType('alipay')">
<view class="iconfont-yshop icon-alipay line-height-100 payment-icon" style="color:#07b4fd" ></view>
<view class="flex-fill">支付宝</view>
<view class="iconfont line-height-100 checkbox checked iconradio-button-on" v-if="payType == 'alipay'" ></view>
<view class="iconfont line-height-100 checkbox iconradio-button-off" v-else ></view>
</view>
</list-cell>
<!-- #endif -->
</view>
<!-- 支付方式 end -->
<!-- 备注 begin -->
<list-cell last @click="goToRemark">
<view class="d-flex flex-fill align-items-center justify-content-between overflow-hidden" style="margin-bottom: 110rpx;">
<view class="flex-shrink-0 mr-20">备注</view>
<view class="text-color-primary flex-fill text-truncate text-right">{{ form.remark || '点击填写备注' }}
</view>
</view>
</list-cell>
<!-- 备注 end -->
</view>
<!-- 付款栏 begin -->
<view style="z-index: 1;"
class="w-100 pay-box position-fixed fixed-bottom d-flex align-items-center justify-content-between bg-white">
<view class="font-size-sm" style="margin-left: 20rpx;">合计:</view>
<view class="font-size-lg flex-fill">¥{{ amount }}</view>
<view class="bg-primary h-100 d-flex align-items-center just-content-center text-color-white font-size-base"
style="padding: 0 60rpx;" @tap="debounce(submit, 500)">付款</view>
</view>
<!-- 付款栏 end -->
<modal :show="ensureAddressModalVisible" custom :mask-closable="false" :radius="'0rpx'" width="90%">
<view class="modal-content">
<view class="d-flex justify-content-end">
<image src="/static/images/pay/close.png" style="width: 40rpx; height: 40rpx;"
@tap="ensureAddressModalVisible = false"></image>
</view>
<view class="d-flex just-content-center align-items-center" style="margin-bottom: 40px;">
<view class="font-size-extra-lg text-color-base">请再次确认下单地址</view>
</view>
<view
class="d-flex font-size-base text-color-base font-weight-bold align-items-center justify-content-between mb-20">
<view>{{ address.realName }}</view>
<view>{{ address.phone }}</view>
</view>
<view class="d-flex font-size-sm text-color-assist align-items-center justify-content-between mb-40">
<view style="max-width: 60%;">{{ address.address + address.detail }}</view>
<button type="primary" size="mini" plain class="change-address-btn" style="white-space: nowrap;"
@click="chooseAddress">修改地址</button>
</view>
<button type="primary" class="pay_btn" @tap="debounce(pay, 500)">确认并付款</button>
</view>
</modal>
<uv-toast ref="uToast"></uv-toast>
</view>
</template>
<script setup>
import {
ref,
computed,
nextTick
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'
import { formatDateTime,isWeixin } from '@/utils/util'
import debounce from '@/uni_modules/uv-ui-tools/libs/function/debounce'
import {
orderSubmit,
payUnify,
getWechatConfig
} from '@/api/order'
import {
couponCount
} from '@/api/coupon'
// #ifdef H5
import * as jweixin from 'weixin-js-sdk'
// #endif
const main = useMainStore()
const { orderType,address, store,location,isLogin,member,mycoupon } = storeToRefs(main)
const active = ref(false)
const title = ref('支付')
const jsStr = ref('')
const cart = ref([])
const form = ref({
remark: ''
})
const ensureAddressModalVisible = ref(false)
const takeoutTIme = ref(false) // 外卖取餐时间picker
const paramsTime = ref({
year: false,
month: false,
day: false,
hour: true,
minute: true,
second: false
})
const defaultTime = ref('00:00')
const takeinTIme = ref(false) // 到店自取时间selector
const takeinRange = ref([{
name: '立即用餐',
value: 0
},
{
name: '10分钟后',
value: 10
},
{
name: '20分钟后',
value: 20
},
{
name: '30分钟后',
value: 30
},
{
name: '40分钟后',
value: 40
},
{
name: '50分钟后',
value: 50
}
])
const defaultSelector = ref([0])
const payType = ref('weixin') // 付款方式
const coupons = ref(0) // 可用优惠券数量
const coupon = ref(main.mycoupon) // 选中的
const subscribeMss = ref({
'takein': '',
'takeout': '',
'takein_made': '',
'takeout_made': ''
})// 微信订阅信息
const uToast = ref()
const total = computed(() =>{
return cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);
})
const amount = computed(() =>{
let amount = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);
// 加配送费
if (store.value.distance > 0 && orderType.value == 'takeout') {
amount += parseFloat(store.value.deliveryPrice);
}
// 减去优惠券
if (main.mycoupon.hasOwnProperty('id')) {
amount -= parseFloat(main.mycoupon.value);
}
return amount.toFixed(2);
})
onShow(() => {
coupon.value = main.mycoupon
let date = new Date(new Date().getTime() + 3600000); // 一个小时后
let hour = date.getHours();
let minute = date.getMinutes();
if (hour < 10) {
hour = '0' + hour;
}
if (minute < 10) {
minute = '0' + minute;
}
defaultTime.value = hour + ':' + minute;
console.log('member:',member.value)
if(orderType.value == 'takeout'){
active.value = true
}else{
active.value = false
}
getCoupons();
let paytype = uni.getStorageSync('paytype');
payType.value = paytype ? paytype : 'weixin';
})
onHide(() => {
subscribeMss.value = [];
coupons.value = 0;
})
onLoad((option) => {
cart.value = uni.getStorageSync('cart')
if(option.remark) {
form.value.remark = option.remark
}
})
const getSubscribeMss = async() => {
let data = []
if (data) {
subscribeMss.value = data;
}
}
// 更改支付方式
const setPayType = (paytype) => {
payType.value = 'weixin';
payType.value= paytype;
uni.setStorage({
key: 'paytype',
data: paytype
})
}
const getCoupons = async() => {
//0=通用,1=自取,2=外卖
let type = orderType.value == 'takein' ? 1 : 2;
let data = await couponCount({
shop_id: store.value.id ? store.value.id : 0,
type: type
});
if (data) {
coupons.value = data;
}
}
// 选择时间
const choiceTime = (value) => {
let hour = value.hour;
let minute = value.minute;
let date = new Date(new Date().getTime() + 3600000); // 一个小时后
let nowhour = date.getHours();
let nowminute = date.getMinutes();
if ((hour * 60 * 60 + minute * 60) * 1000 - 3600000 < (nowhour * 60 * 60 + nowminute * 60) * 1000) {
uToast.value.show({
message: '请至少选择一个小时之后',
type: 'error'
});
return
}
if (hour < 10) {
hour = '0' + hour;
}
if (minute < 10) {
minute = '0' + minute;
}
defaultTime,value = hour + ':' + minute;
takeoutTIme.value = false;
}
const cancelTime = (value) => {
takeoutTIme.value = false;
}
// 到店自取-取消选择取餐时间
const takeinCancelTime = (value) => {
takeinTIme.value = false;
}
// 到店自取-选择取餐时间
const takeinConfirmTime = (value) => {
defaultSelector.value = value;
}
// 是否外卖开关
const takout = (value) => {
let type = 'takeout';
if (value == false) {
type = 'takein';
}
main.SET_ORDER_TYPE(type);
// 如果存在优惠券看看需不需要清除
if (coupon.value.hasOwnProperty('type')) {
//0=通用,1=自取,2=外卖
if (coupon.value.type != 0) {
if (coupon.value.type == 1 && orderType.value == 'takeout') {
coupon.value = {};
}
if (coupon.value.type == 2 && orderType.value == 'takeint') {
coupon.value = {};
}
}
}
subscribeMss.value = [];
coupons.value = 0;
getCoupons();
}
const goToRemark = () => {
uni.navigateTo({
url: '/pages/components/pages/remark/remark?remark=' + form.value.remark
});
}
const chooseAddress = () => {
uni.navigateTo({
url: '/pages/components/pages/address/address?is_choose=true&scene=pay'
});
}
const goToPackages = () => {
let newamount = amount.value;
let coupon_id = coupon.value.id ? coupon.value.id : 0;
let type = orderType.value == 'takein' ? 1 : 2;
let shop_id = store.value.id;
uni.navigateTo({
url: '/pages/components/pages/packages/index?amount=' + newamount + '&coupon_id=' + coupon_id +
'&shop_id=' + shop_id + '&type=' + type
});
}
const goToShop = () => {
uni.navigateTo({
url: `/pages/components/pages/shop/shop`
});
}
const submit = () => {
if (orderType.value == 'takeout') {
// 外卖类型
if (typeof address.value.id == 'undefined') {
uToast.value.show({
message: '请选择收货地址',
type: 'error'
});
return
}
// 起送价钱
if (store.value.min_price > total.value) {
uToast.value.show({
message: '本店外卖起送价为¥' + store.value.min_price,
type: 'error'
});
return
}
pay();
} else {
pay();
}
}
const pay = async() => {
let that = this;
// #ifdef MP-WEIXIN
await new Promise(function(revolve) {
//订阅号信息id
let subscribeMss = ['KBtfY9G1IWCzC6q-ZKo-Q-MmdP7aaF79nx0XFcBf3h4'];
wx.showModal({
title: '温馨提示',
content: '为更好的促进您与商家的交流,小程序需要在您成交时向您发送消息',
confirmText: "同意",
cancelText: "拒绝",
success: function(res) {
if (res.confirm) {
uni.requestSubscribeMessage({
tmplIds: subscribeMss,
complete(res) {
console.log(res)
revolve(true)
}
});
} else {
revolve(true)
}
}
})
});
// #endif
uni.showLoading({
title: '加载中'
});
let data = {
orderType: orderType.value, // 购买类型:takein=自取,takeout=外卖
addressId:orderType.value == 'takeout' ? address.value.id : 0, // 外卖配送地址
shopId: store.value.id, // 店铺id
mobile: member.value.mobile, // 联系电话
gettime: takeinRange.value[defaultSelector.value[0]].value, // 取餐时间
payType: payType.value, // 支付类型
remark: form.value.remark, // 备注
productId: [],
spec: [],
number: [],
couponId: coupon.value.id ? coupon.value.id : 0 // 优惠券id
};
cart.value.forEach((item, index) => {
data.productId.push(item.id);
data.spec.push(item.valueStr.replace(/,/g, '|'));
//data.spec.push(item.valueStr);
data.number.push(item.number);
});
//console.log(data);
let order = await orderSubmit(data);
if (!order) {
uni.hideLoading();
return;
}
if (payType.value == 'weixin') {
// 微信支付
weixinPay(order);
} else if (payType.value == 'yue') {
// 余额支付
balancePay(order);
} else if (payType.value == 'alipay') {
// 余额支付
aliPay(order);
}
uni.hideLoading()
return
}
const balancePay = async(order) => {
let from = 'routine'
// #ifdef H5
from = 'h5'
// #endif
let pay = await payUnify({
uni: order.orderId,
from: from,
paytype: 'yue'
});
uni.hideLoading();
if (!pay) {
return;
}
member.value.money -= amount.value
main.SET_MEMBER(member.value)
uni.removeStorageSync('cart');
uni.switchTab({
url: '/pages/order/order',
fail(res) {
console.log(res);
}
});
}
const weixinPay = async(order) => {
let from = 'routine'
// #ifdef H5
from = 'h5'
if(isWeixin()){
from = 'wechat'
}
// #endif
//let that = this;
let data = await payUnify({
uni: order.orderId,
from: from,
paytype: 'weixin'
});
console.log('param2:',data)
if (!data) {
uni.hideLoading();
return;
}
if (data.trade_type == 'MWEB') {
// #ifdef H5
// 微信外的H5
console.log('data:',data)
location.href = data.data;
// #endif
console.log('data1:',data)
} else if (data.trade_type == 'JSAPI') {
console.log('param:',data)
// #ifdef MP-WEIXIN
uni.requestPayment({
provider: 'wxpay',
timeStamp: data.data.timeStamp,
nonceStr: data.data.nonceStr,
package: data.data.package,
signType: 'MD5',
paySign: data.data.paySign,
success: function(res) {
uni.removeStorageSync('cart');
uni.switchTab({
url: '/pages/order/order'
});
},
fail: function(err) {
console.log('fail:' + JSON.stringify(err));
}
});
// #endif
} else if (data.trade_type == 'W-JSAPI'){
//公众号支付
}else if (data.trade_type == 'APP') {
}
}
const aliPay = async(order) => {
// #ifdef H5
//let that = this;
if(isWeixin()){
uni.showToast({
title: '请普通浏览器打开进行支付宝支付~',
icon: 'none'
})
return
}
let data = await payUnify({
uni: order.orderId,
from: 'h5',
paytype: 'alipay'
});
console.log('data:',data.data)
// 支付宝支付,这里只要提交表单
let form = data.data
const div = document.createElement('formdiv');
div.innerHTML = form;
document.body.appendChild(div);
//document.forms[0].setAttribute('target', ' self');
document.forms[0].submit();
//div.remove();
// #endif
}
</script>
<style lang="scss" scoped>
.container {
padding: 30rpx;
}
.arrow {
width: 50rpx;
height: 50rpx;
position: relative;
margin-right: -10rpx;
}
.location {
.store-name {
font-size: $font-size-lg;
}
.iconfont {
font-size: 50rpx;
line-height: 100%;
color: $color-primary;
}
}
.section-1 {
margin-bottom: 30rpx;
.contact {
.contact-tip {
margin-left: 10rpx;
border: 2rpx solid $color-primary;
padding: 6rpx 10rpx;
color: $color-primary;
}
}
}
.section-2 {
.name-and-props {
width: 65%;
}
}
.payment {
margin-bottom: 30rpx;
.disabled {
color: $text-color-grey;
}
.payment-icon {
font-size: 44rpx;
margin-right: 10rpx;
}
.checkbox {
font-size: 36rpx;
margin-left: 10rpx;
}
.checked {
color: $color-primary;
}
}
.pay-box {
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.1);
height: 100rpx;
}
.modal-content {
.change-address-btn {
line-height: 2;
padding: 0 1em;
}
.pay_btn {
width: 100%;
border-radius: 50rem !important;
line-height: 3;
}
}
.choice {
background-color: $bg-color-grey;
border-radius: 38rpx;
display: flex;
align-items: center;
font-size: $font-size-sm;
padding: 0 38rpx;
color: $text-color-assist;
.dinein,
.takeout {
width: 50%;
position: relative;
display: flex;
align-items: center;
&.active {
padding: 14rpx 38rpx;
color: #ffffff;
background-color: $color-primary;
border-radius: 38rpx;
}
}
.takeout {
margin-left: 20rpx;
height: 100%;
flex: 1;
padding: 14rpx 0;
}
.dinein.active {
//margin-left: -38rpx;
}
.takeout.active {
//margin-right: -38rpx;
}
}
</style>
@@ -0,0 +1,117 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container w-100 h-100 overflow-hidden">
<view class="textarea">
<textarea placeholder-class="text-color-assist font-size-base" v-model="remark"
class="bg-white w-100 border-box font-size-base remark"
:class="{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}"
placeholder="请填写备注信息" focus/>
<view class="tips" :class="{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}">
{{ remarkLength }}/50
</view>
</view>
<view class="d-flex font-size-base text-color-assist" style="margin: 40rpx 0;">
快捷输入
</view>
<view class="quick-inputs d-flex flex-wrap justify-content-start">
<view class="quick-input" v-for="(item, index) in quickInputs" :key="index" @tap="handleQuickInput(item)">
{{ item }}
</view>
</view>
<view class="d-flex just-content-center align-items-center" style="margin-top: 60rpx;">
<button type="primary" class="submit-btn font-size-base" @tap="submit">完成</button>
</view>
<!--轻提示-->
<uv-toast ref="uToast"></uv-toast>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { onLoad} from '@dcloudio/uni-app'
const remark = ref('')
const quickInputs = ref( ['请放门把手上', '请放门口', '请放前台桌上', '如地址封闭管理,请电话与我联系'])
const uToast = ref()
onLoad((opt) => {
remark.value = opt.remark
})
const remarkLength = computed(() => {
return remark.value.length
})
const isDanger = computed(() => {
return remark.value.length > 50
})
const handleQuickInput = (item) => {
remark.value = remark.value.concat(" ", item)
}
const submit = () => {
if (remark.value.length > 50) {
uToast.value.show({
message: '不能超过50个字符',
type: 'error'
});
return
}
uni.navigateTo({
url: "/pages/components/pages/pay/pay?remark=" + remark.value
})
}
</script>
<style lang="scss" scoped>
.container {
padding: 30rpx 40rpx;
.textarea {
position: relative;
.remark {
border-radius: 8rpx;
padding: 30rpx 40rpx;
height: 320rpx;
color: $font-size-base;
}
.tips {
position: absolute;
bottom: 30rpx;
right: 40rpx;
}
}
.quick-inputs {
padding-right: 20rpx;
.quick-input {
background-color: #FFFFFF;
border: 2rpx solid $color-primary;
color: $color-primary;
font-size: $font-size-base;
padding: 16rpx 26rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
}
}
.submit-btn {
width: 90%;
height: 80rpx;
border-radius: 40rpx;
line-height: 80rpx;
}
}
</style>
@@ -0,0 +1,135 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view>
<view>
<uv-search margin="30rpx" v-model="keywork" @custom="search(keywork)"></uv-search>
</view>
<view v-for="(item,index) in list" :key="index">
<uni-card @click="choice(item)" :border="item.id == store.id" :title="item.name" :thumbnail="item.image" :thumb-width="80" :sub-title="item.status_text">
<view class="body">
<view class="body-left">
<view>距离您 {{kmUnit(item.dis)}}</view>
<view v-if="item.distance > 0">配送距离:{{item.distance + 'km '}} & 配送费:{{item.delivery_price}}</view>
<view v-else>外卖不配送</view>
<view>{{item.addressMap + ' ' + item.address}}</view>
<view>营业时间 {{formatDateTime(item.startTime,'hh:mm')}} - {{formatDateTime(item.endTime,'hh:mm')}}</view>
</view>
<view class="body-right">
<uv-button @click="openLocation(item)">导航</uv-button>
<uv-button @click="call(item.mobile)">致电</uv-button>
</view>
</view>
</uni-card>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'
import { formatDateTime,kmUnit,prePage } from '@/utils/util'
import {
shopNearby,
menuGoods
} from '@/api/goods'
import {
shopGetList
} from '@/api/market'
const main = useMainStore()
const { store,location } = storeToRefs(main)
const title = ref('店铺')
const list = ref([])
const keywork = ref('')
const page = ref(1)
const pagesize = ref(10)
onLoad(() => {
getShop();
})
const getShop = async(keywork = '') => {
let data = await shopGetList({
lat: location.value.latitude ? location.value.latitude : 0,
lng: location.value.longitude ? location.value.longitude : 0,
kw: keywork,
shop_id: 0
});
if (data) {
//console.log(data);
if (page.value == 1) {
list.value = data;
} else {
for(let i in data) {
list.value.push(data[i]);
}
}
}
}
//打开定位
const openLocation = (shop) => {
//console.log(shop);
uni.openLocation({
latitude: parseFloat(shop.lat),
longitude: parseFloat(shop.lng),
name:shop.name,
address: shop.addressMap + shop.address,
fail: (res) => {
console.log(res);
}
})
}
// 打电话
const call = (mobile) => {
uni.makePhoneCall({
phoneNumber:mobile
})
}
// 搜索按钮
const search = (keywork) => {
page.value = 1;
getShop(keywork);
}
// 选中店铺
const choice = (shop) => {
main.SET_STORE(shop);
uni.$emit('refreshMenu')
uni.switchTab({
url:'/pages/menu/menu',
success(res) {
},
fail(res) {
console.log(res);
}
});
}
</script>
<style lang="scss">
.body {
.body-left {
display: inline-block;
width: 77%;
padding-left: 6rpx;
}
.body-right {
display: inline-block;
width: 20%;
}
}
.u-border {
border: 1rpx solid #1296db;
}
</style>
@@ -0,0 +1,314 @@
<template>
<layout>
<uv-sticky
bg-color="#F5F5F5"
offset-top="0"
customNavHeight="0"
>
<uv-navbar
:fixed="false"
:safeAreaInsetTop="true"
height="0"
bgColor="transparent"
leftIcon=""
/>
<!-- #ifndef MP -->
<blank size="10"></blank>
<!-- #endif -->
<blank size="5"></blank>
</uv-sticky>
<blank size="10"></blank>
<view class="container">
<view class="banner">
<uv-swiper class="bg" height="300" imgMode="aspectFill" keyName="image" :list="listAds" indicatorMode="dot" indicatorStyle="bottom"></uv-swiper>
<view class="intro">
<view class="greet">您好,{{ isLogin ? member.nickname : '游客' }}</view>
<view class="note">java-springboot-意向点餐外卖系统</view>
</view>
</view>
<view class="content">
<view class="entrance">
<view class="item " @tap="takein">
<view>
<view class="font-md">自取</view>
<view class="font-small text-light-black flex-1">下单免排队</view>
</view>
<view class="index-img">
<image src="/static/images/index002.png" mode="aspectFit" class="img-01"></image>
</view>
</view>
<view class="item" @tap="takeout">
<view>
<view class="font-md">外卖</view>
<view class="font-small text-light-black">美食送到家</view>
</view>
<view class="index-img"><image src="/static/images/index003.png" mode="aspectFit" class="img-02"></image></view>
</view>
</view>
<view class="info">
<view class="integral_section">
<view class="top">
<text class="title">我的卡券</text>
<text class="value">{{member.couponCount}}</text>
</view>
<view class="bottom">
可抵扣商品价格哦
</view>
</view>
<view class="qrcode_section" @tap="coupons">
去领个券
<view class="iconfont iconarrow-right"></view>
</view>
</view>
</view>
</view>
</layout>
</template>
<script setup>
import {
ref
} from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
menuAds
} from '@/api/market'
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store/store'
//
const main = useMainStore()
const { member,store, isLogin} = storeToRefs(main)
//const store = ref(main.store)
const listAds = ref([])
// const isLogin = ref(main.isLogin)
const handGetListAds = async () => {
let shop_id = store.id ? store.id : 0;
let data = await menuAds({
shop_id: shop_id
});
if (data) {
listAds.value = data.list;
console.log('listAds:',listAds.value)
uni.setStorage({
key: 'isActive',
data: data.isActive
});
if(data.list.length > 0){
uni.setStorage({
key: 'shopAd',
data: data.list[0].image
});
}
}
}
const takein = () => {
main.SET_ORDER_TYPE('takein')
uni.switchTab({
url: '/pages/menu/menu'
})
}
const takeout = () => {
main.SET_ORDER_TYPE('takeout')
uni.switchTab({
url: '/pages/menu/menu'
})
}
const coupons = () => {
console.log("--> % orderType:\n", main.orderType)
console.log("--> % isLogin:\n", main.isLogin)
if(!main.isLogin) {
uni.navigateTo({url: '/pages/components/pages/login/login'})
return
}
uni.navigateTo({
url: '/pages/components/pages/coupons/coupons'
})
}
const goScore = () => {
uni.navigateTo({
url: '/pages/components/pages/scoreproduct/list'
})
}
onLoad(() => {
// main.init()
handGetListAds()
})
</script>
<style lang="scss">
.index-img {
width: 120px;
height: 120rpx;
position: relative;
left:-20rpx
}
.img-01 {
width: 100px;
height: 100rpx;
margin-top: 30rpx;
}
.img-02 {
width: 110px;
height: 110rpx;
margin-top: 20rpx;
}
/* #ifdef H5 */
page {
height: auto;
min-height: 100%;
}
/* #endif */
page {
background-color: #ffffff!important;
}
.banner {
position: relative;
width: 100%;
//height: 300rpx;
.bg {
width: 100%;
height: 300rpx;
}
.intro {
position: absolute;
top: calc(50rpx + var(--status-bar-height));
left: 40rpx;
color: #FFFFFF;
display: flex;
flex-direction: column;
.greet {
font-size: $font-size-lg;
margin-bottom: 10rpx;
}
.note {
font-size: $font-size-sm;
}
}
}
.content {
padding: 0 50rpx;
}
.entrance {
position: relative;
//top:0;
margin-top: 30rpx;
margin-bottom: 30rpx;
border-radius: 10rpx;
background-color: #ffffff;
box-shadow: $box-shadow;
padding: 30rpx 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50rpx;
.item {
//flex: 1;
display: flex;
//flex-direction: column;
justify-content: center;
//align-items: center;
position: relative;
&:nth-child(1):after {
content: '';
position: absolute;
width: 1rpx;
background-color: #ddd;
right: 0;
height: 100%;
transform: scaleX(0.5) scaleY(0.8);
}
.icon {
font-size: 90rpx;
// color: #5A5B5C;
color: #dd524d;
}
.title {
font-size: 30rpx;
color: $text-color-base;
font-weight: 600;
}
}
}
.info {
position: relative;
margin-bottom: 30rpx;
border-radius: 10rpx;
background-color: #ffffff;
box-shadow: $box-shadow;
//padding: 30rpx;
display: flex;
align-items: center;
justify-content: center;
.integral_section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.top {
display: flex;
align-items: center;
.title {
color: $text-color-base;
font-size: $font-size-base;
margin-right: 10rpx;
}
.value {
font-size: 44rpx;
font-weight: bold;
}
}
.bottom {
font-size: $font-size-sm;
color: $text-color-assist;
display: flex;
align-items: center;
}
}
.qrcode_section {
color: $color-primary;
display: flex;
//flex-direction: column;
align-items: center;
justify-content: center;
font-size: $font-size-sm;
image {
width: 40rpx;
height: 40rpx;
margin-bottom: 10rpx;
}
}
}
</style>
File diff suppressed because it is too large. Load diff
+373
View File
@@ -0,0 +1,373 @@
<template>
<layout>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container">
<view style="padding: 0 30rpx;">
<!-- user box begin -->
<view class="d-flex flex-column bg-white user-box">
<view class="d-flex align-items-center">
<view class="avatar rounded-circle">
<image :src="isLogin ? member.avatar ? member.avatar : '/static/images/mine/default.png' : '/static/images/mine/default.png'"></image>
</view>
<view class="d-flex flex-column flex-fill overflow-hidden" style="margin-top: 20rpx;">
<view v-if="isLogin"
class="font-size-lg font-weight-bold d-flex justify-content-start align-items-center"
@tap="serv({type:'pages',pages:'/pages/components/pages/mine/userinfo'})">
<view class="text-truncate">{{ member.nickname }}</view>
<view class="iconfont iconarrow-right line-height-100"></view>
</view>
<view v-else class="font-size-lg font-weight-bold" @tap="login">游客</view>
</view>
</view>
<!-- user grid begin -->
<view class="w-100 d-flex align-items-center just-content-center">
<view class="user-grid" @tap="serv({type:'pages',pages:'/pages/components/pages/coupons/coupons'})">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.couponCount : 0}}
</view>
<view class="font-size-sm text-color-assist">优惠券</view>
</view>
<view class="user-grid" @tap="serv({type:'pages', pages: '/pages/components/pages/balance/bill?cate=1'})">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.integral : 0 }}
</view>
<view class="font-size-sm text-color-assist">积分</view>
</view>
<view class="user-grid">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.nowMoney : 0 }}
</view>
<view class="font-size-sm text-color-assist">余额</view>
</view>
<view class="user-grid" @tap="serv({type:'pages', pages: '/pages/components/pages/balance/bill?cate=0'})">
<view class="value font-size-extra-lg font-weight-bold text-color-base">
{{ isLogin ? member.sumMoney : 0 }}
</view>
<view class="font-size-sm text-color-assist">历史消费</view>
</view>
</view>
<!-- user grid end -->
</view>
<!-- user box end -->
</view>
<!-- service box begin -->
<view class="service-box">
<view class="font-size-lg text-color-base font-weight-bold" style="margin-bottom: 20rpx;">我的服务</view>
<view class="u-m-t-20">
<uv-cell-group>
<block v-for="(item, index) in services" :key='index'>
<uv-cell :title="item.name" v-if="item.type == 'contact'" :isLink="true">
<template #icon>
<image :src="item.image" style="width: 40rpx;height: 40rpx;" class="mr-1"></image>
</template>
</uv-cell>
<uv-cell :isLink="true" :title="item.name" v-else-if="item.type == 'call'" v-on:click="makePhoneCall(item.phone)">
<template #icon>
<image :src="item.image" style="width: 40rpx;height: 40rpx;" class="mr-1"></image>
</template>
</uv-cell>
<uv-cell :isLink="true" :title="item.name" v-else @tap="serv(item)">
<template #icon>
<image :src="item.image" style="width: 40rpx;height: 40rpx;" class="mr-1"></image>
</template>
</uv-cell>
</block>
</uv-cell-group>
</view>
</view>
</view>
</layout>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onShow} from '@dcloudio/uni-app'
import { formatDateTime,kmUnit } from '@/utils/util'
import {
userGetUserInfo,
mineService
} from '@/api/user'
const main = useMainStore()
const { member,isLogin } = storeToRefs(main)
const title = ref('个人中心')
const services = ref([])
const growthValue = computed(() => {
if (!isLogin.value) return 0
const {
currentValue,
needValue
} = member.value
return currentValue / (currentValue + needValue) * 100
})
onLoad(() => {
getServices();
})
onShow(() => {
getUserInfo();
})
const getUserInfo = async() => {
if (isLogin.value) {
let data = await userGetUserInfo();
if (data) {
main.SET_MEMBER(data);
}
}
}
const getServices = async() => {
let data = await mineService();
if (data) {
services.value = data;
}
}
const makePhoneCall = (phoneNumber) => {
uni.makePhoneCall({
phoneNumber: phoneNumber,
})
}
const login = () => {
uni.navigateTo({
url: '/pages/components/pages/login/login'
})
}
const packages = () => {
if (!isLogin.value) {
login()
return
}
uni.navigateTo({
url: '/pages/components/pages/packages/index'
})
}
const serv = (item) => {
switch (item.type) {
case 'pages':
if (!isLogin.value) {
login()
return
}
uni.navigateTo({
url: item.pages
})
break;
case 'miniprogram':
uni.navigateToMiniProgram({
appId: item.app_id
})
break;
case 'menu':
uni.navigateTo({
url: '/pages/components/pages/mine/service?id=' + item.id + '&name=' + item.name
})
break;
case 'content':
uni.navigateTo({
url: '/pages/components/pages/mine/content?id=' + item.id + '&name=' + item.name
})
break;
}
}
</script>
<style lang="scss" scoped>
page {
height: auto;
min-height: 100%;
}
.bg {
width: 100%;
height: calc(410 / 594 * 750rpx);
}
.hym-btn {
position: absolute;
top: 40rpx;
right: 40rpx;
color: $color-primary;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50rem;
font-size: $font-size-sm;
box-shadow: 0 0 20rpx rgba(66, 66, 66, 0.1);
&::after {
border: 0;
}
image {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
}
.user-box {
position: relative;
border-radius: 8rpx;
margin-bottom: 30rpx;
margin-top: 70rpx;
box-shadow: $box-shadow;
}
.avatar {
position: relative;
margin-top: -35rpx;
margin-left: 35rpx;
margin-right: 35rpx;
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #FFFFFF;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);
image {
width: 140rpx;
height: 140rpx;
border-radius: 100%;
}
.badge {
position: absolute;
right: -10rpx;
bottom: -10rpx;
background-color: #FFFFFF;
border-radius: 50rem;
display: flex;
align-items: center;
justify-content: center;
color: $color-warning;
font-size: 24rpx;
padding: 8rpx 16rpx;
box-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);
image {
width: 30rpx;
height: 30rpx;
}
}
}
.level-benefit {
margin-left: 35rpx;
padding: 10rpx 0 10rpx 30rpx;
border-radius: 50rem 0 0 50rem;
}
.user-grid {
width: 25%;
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.value {
margin-bottom: 20rpx;
}
}
.level-benefit-box {
border-radius: 8rpx;
margin-bottom: 30rpx;
box-shadow: 0 10rpx 8rpx rgba($color: #878889, $alpha: 0.1);
width: 100%;
display: flex;
padding: 30rpx;
flex-direction: column;
background-color: #FFFFFF;
.row {
display: flex;
padding: 30rpx 0 20rpx;
justify-content: space-around;
align-items: center;
.grid {
width: 20%;
display: flex;
flex-direction: column;
font-size: $font-size-sm;
color: $text-color-assist;
align-items: center;
image {
width: 80rpx;
height: 80rpx;
margin-bottom: 10rpx;
}
}
}
}
.banner {
width: 100%;
border-radius: 8rpx;
margin-bottom: 30rpx;
}
.service-box {
width: 100%;
background-color: #FFFFFF;
padding: 32rpx 30rpx 10rpx;
box-shadow: $box-shadow;
.row {
display: flex;
flex-wrap: wrap;
color: $text-color-assist;
font-size: $font-size-sm;
padding-bottom: -40rpx;
.grid {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
width: 25%;
position: relative;
image {
width: 80rpx;
height: 80rpx;
margin-bottom: 20rpx;
}
}
.opacity-0 {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
z-index: 10;
}
}
}
</style>
@@ -0,0 +1,183 @@
<template>
<uv-navbar
:fixed="false"
:title="title"
left-arrow
@leftClick="$onClickLeft"
/>
<view class="container">
<view class="bg-white">
<uv-tabs :list="tabList" :current="current" @change="change" keyName="name" :scrollable="false"></uv-tabs>
</view>
<view class="orders-list d-flex flex-column w-100" style="padding: 20rpx; padding-bottom: 0;">
<view class="order-item" v-for="(item, index) in orders" :key="index" style="margin-bottom: 30rpx;" >
<list-cell :hover="false">
<view class="w-100 d-flex align-items-center">
<view class="flex-fill d-flex flex-column">
<view class="font-size-lg text-color-base" style="margin-bottom: 20rpx;">
{{ item.shop.name }}
</view>
<view class="font-size-sm text-color-assist">取餐号:{{ item.numberId }}</view>
<view class="font-size-sm text-color-assist">订单编号:{{ item.orderId }}</view>
</view>
<view class="font-size-lg text-color-primary">
{{ item._status._title }}
</view>
</view>
</list-cell>
<list-cell :hover="false" last>
<view class="w-100 d-flex flex-column">
<view class="w-100 text-truncate font-size-lg text-color-base" style="margin-bottom: 20rpx;">
<!-- {{ orderGoodsName(item.cartInfo) }} -->
<view class="flex mb-2" v-for="(good,index) in item.cartInfo" :key="index">
<image :src="good.image" mode="aspectFill" class="image"></image>
<view class="flex flex-column">
<view class="font-size-medium mt-1 text-color-base">{{ good.title }}</view>
<view class="font-size-sm mt-1">{{ good.spec }}</view>
<view class="font-size-sm mt-2">×{{ good.number }} ¥{{ good.price }}</view>
</view>
</view>
</view>
<view class="d-flex justify-content-between align-items-center" style="margin-bottom: 30rpx;">
<view class="font-size-sm text-color-assist">
{{formatDateTime(item.createTime) }}
</view>
<view class="d-flex font-size-sm text-color-base align-items-center">
<view style="margin-right: 10rpx;">共{{ goodsNum(item.cartInfo) }}件商品,实付</view>
<view class="font-size-lg">¥{{ item.payPrice }}</view>
</view>
</view>
<view class="d-flex align-items-center justify-content-end">
<view>
<button v-if="item.paid > 0 && item.status < 2 && item.refundStatus == 0" class="left-margin" plain size="mini" @tap.stop="receive(item)">确认收到餐</button>
<button class="left-margin" plain size="mini" @tap="detail(item.orderId)">订单详情</button>
</view>
</view>
</view>
</list-cell>
</view>
</view>
<uv-empty v-if="orders.length == 0" mode="order"></uv-empty>
</view>
</template>
<script setup>
import {
ref,
computed
} from 'vue'
import { useMainStore } from '@/store/store'
import { storeToRefs } from 'pinia'
import { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'
import { formatDateTime,kmUnit } from '@/utils/util'
import {
orderGetOrders,
orderReceive
} from '@/api/order'
const main = useMainStore()
const { isLogin } = storeToRefs(main)
const title = ref('我的订单')
const page = ref(1)
const pageSize = ref(10)
const orders = ref([])
const tabList = ref([{
type: -1,
name: '全部',
}, {
type: 0,
name: '待支付',
}, {
type: 1,
name: '进行中'
}, {
type: 4,
name: '已完成'
}, {
type: -3,
name: '退款单'
}]
)
const current = ref(0)
const type = ref(-1)
const orderGoodsName = computed(() => { //计算单个饮品添加到购物车的数量
return (goods) => {
let arr = []
goods.forEach(good => arr.push(good.title + '*' + good.number))
return arr.join(',')
}
})
const goodsNum = computed(() => { //计算单个饮品添加到购物车的数量
return (goods) => {
let num = 0;
goods.forEach(good => num += parseInt(good.number))
return num;
}
})
onLoad(() => {
if(!isLogin.value) {
uni.navigateTo({url: '/pages/components/pages/login/login'})
}
getOrders(false)
})
onPullDownRefresh(() => {
getOrders(false)
})
onReachBottom(() => {
getOrders(false)
})
// tab栏切换
const change = (e) => {
//console.log('e;',e.type)
//console.log('e.index;',e.index)
type.value = e.type
getOrders(true)
}
const getOrders = async(isRefresh = false) => {
uni.showLoading({
title: '加载中'
})
if(isRefresh) {
orders.value = []
page.value = 1
}
let ordersData = await orderGetOrders({page:page.value, limit:pageSize.value,type:type.value});
if(ordersData) {
orders.value = orders.value.concat(ordersData)
page.value += 1
}
uni.stopPullDownRefresh();
uni.hideLoading()
}
const detail = (id) => {
uni.navigateTo({
url: '/pages/components/pages/orders/detail?id=' + id
})
}
// 确认收到货
const receive = async(order) => {
let data = await orderReceive({uni:order.orderId});
if (data) {
await getOrders(true)
}
}
</script>
<style lang="scss" scoped>
.left-margin {
margin-left: 10rpx;
}
.image {
width: 160rpx;
height: 160rpx;
margin-right: 30rpx;
border-radius: 8rpx;
}
</style>
@@ -0,0 +1,23 @@
@font-face {
font-family: "iconfont"; /* Project id 4205200 */
src: url('~@/static/iconfont/iconfont.woff2?t=1691643169117') format('woff2'),
url('~@/static/iconfont/iconfont.woff?t=1691643169117') format('woff'),
url('~@/static/iconfont/iconfont.ttf?t=1691643169117') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-waimai-:before {
content: "\e607";
}
.icon-tangshi:before {
content: "\e645";
}
@@ -0,0 +1,270 @@
@font-face {
font-family: 'iconfont-yshop';
src: url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.eot');
src: url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.woff') format('woff'),
url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.svg#iconfont-yshop') format('svg');
}
.iconfont-yshop {
font-family: 'iconfont-yshop' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #5A5B5C;
}
.icon-alipay:before {
content: '\e60a';
}
.icon-shop:before {
content: '\e664';
}
.icon-takein:before {
content: '\e666';
}
.icon-takeout:before {
content: '\e667';
}
.icon-location:before {
content: '\e633';
}
.icon-mobile:before {
content: '\e602';
}
.icon-box:before {
content: '\e665';
}
.icon-lamp:before {
content: '\e668';
}
.icon-doorbell:before {
content: '\e662';
}
.icon-daojishi:before {
content: '\e625';
}
@font-face {
font-family: 'iconfont';
src: url('//at.alicdn.com/t/font_1789197_z1gzlwq7idq.ttf?t=1589441233693') format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconshoucang:before {
content: '\e667';
}
.iconshoucangfill:before {
content: '\e6c9';
}
.iconjifen:before {
content: '\e642';
}
.iconradio-button-off:before {
content: '\e688';
}
.iconradio-button-on:before {
content: '\e689';
}
.iconhelp:before {
content: '\e752';
}
.iconwxpay:before {
content: '\e611';
}
.iconbalance:before {
content: '\e619';
}
.iconadd-select:before {
content: '\e7b0';
}
.iconsami-select:before {
content: '\e7b1';
}
.iconmap:before {
content: '\e758';
}
.iconsuccess:before {
content: '\e767';
}
.iconsuccess-fill:before {
content: '\e78d';
}
.iconiconset0136:before {
content: '\e623';
}
.iconzan:before {
content: '\e640';
}
.iconjifenqiandao:before {
content: '\e6a6';
}
.iconshouyeshouye:before {
content: '\e606';
}
.icondaohang:before {
content: '\e641';
}
.iconwodelianxikefu:before {
content: '\e671';
}
.iconwodexinyuan:before {
content: '\e675';
}
.iconphone:before {
content: '\e6dd';
}
.icondingdan:before {
content: '\e645';
}
.iconliwu:before {
content: '\e61c';
}
.iconyinpinyinliao:before {
content: '\e60d';
}
.iconyinpin:before {
content: '\e70b';
}
.iconwaimaixinxi:before {
content: '\e685';
}
.iconico:before {
content: '\e646';
}
.iconwode:before {
content: '\e616';
}
.icongengduofuwu:before {
content: '\e607';
}
.iconqucan:before {
content: '\e625';
}
.iconyou:before {
content: '\e618';
}
.iconshouhuodizhi:before {
content: '\e666';
}
.iconshangcheng:before {
content: '\e63b';
}
.iconadd:before {
content: '\e742';
}
.iconarrow-right:before {
content: '\e743';
}
.iconarrow-lift:before {
content: '\e744';
}
.iconarrow-up:before {
content: '\e745';
}
.iconclose:before {
content: '\e747';
}
.iconleftbutton:before {
content: '\e755';
}
.iconreduce:before {
content: '\e75e';
}
.iconseleted:before {
content: '\e763';
}
.iconRightbutton:before {
content: '\e765';
}
.iconleftbutton-fill:before {
content: '\e782';
}
.iconRightbutton-fill:before {
content: '\e78a';
}
.iconarrow-down:before {
content: '\e7b2';
}
.iconaixin1:before {
content: '\e63c';
}
@font-face {
font-family: "iconfont"; /* Project id 4205200 */
src: url('~@/static/iconfont/iconfont.woff2') format('woff2'),
url('~@/static/iconfont/iconfont.woff') format('woff'),
url('~@/static/iconfont/iconfont.ttf') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-waimai-:before {
content: "\e607";
}
.icon-tangshi:before {
content: "\e645";
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,435 @@
@import '~@/uni.scss';
page,
view,
scroll-view,
text,
image,
textarea,
button,
input {
box-sizing: border-box;
}
page {
background-color: #F1F8FA;
height: 100%;
}
.container {
width: 100%;
height: 100%;
}
button {
margin: 0;
&[type='primary'] {
background-color: $color-primary;
//background-color: #5A5B5C;
color: #ffffff;
font-size: $font-size-base;
&[disabled] {
background-color: #D1D78C;
}
&[plain] {
color: $color-primary;
border: 1rpx solid $color-primary;
}
&.button-hover {
background-color: #d5da91;
}
&::after {
border: 0;
}
}
&[type='default'] {
&[plain] {
color: $text-color-assist;
border: 1rpx solid $text-color-assist;
}
}
}
.bg-base {
background-color: $bg-color;
}
.bg-white {
background-color: #ffffff;
}
.bg-transparent {
background-color: transparent !important;
}
.bg-primary {
background-color: $color-primary;
}
.bg-warning {
background-color: $color-warning;
}
.d-flex {
display: flex;
}
.d-none {
display: none !important;
}
.invisible {
visibility: hidden !important;
}
.d-inline {
display: inline !important;
}
.d-inline-block {
display: inline-block !important;
}
.d-block {
display: block !important;
}
.flex-column {
-ms-flex-direction: column !important;
flex-direction: column !important;
}
.justify-content-start {
justify-content: flex-start;
}
.justify-content-end {
justify-content: flex-end;
}
.justify-content-between {
justify-content: space-between;
}
.just-content-center {
justify-content: center;
}
.justify-content-evenly {
justify-content: space-evenly !important;
}
.just-content-around {
justify-content: space-around;
}
.align-items-start {
align-items: flex-start;
}
.align-items-end {
align-items: flex-end;
}
.align-items-center {
align-items: center;
}
.align-items-between {
align-items: space-between;
}
.align-items-around {
align-items: space-around;
}
.align-items-stretch {
align-items: stretch;
}
.align-items-baseline {
-ms-flex-align: baseline !important;
align-items: baseline !important;
}
.flex-fill {
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
}
.flex-wrap {
-ms-flex-wrap: wrap !important;
flex-wrap: wrap !important;
}
.flex-nowrap {
-ms-flex-wrap: nowrap !important;
flex-wrap: nowrap !important;
}
.flex-shrink-0 {
-ms-flex-negative: 0 !important;
flex-shrink: 0 !important;
}
.font-size-base {
font-size: 28rpx;
}
.font-size-sm {
font-size: 24rpx;
}
.font-size-medium {
font-size: 26rpx;
}
.font-size-lg {
font-size: 32rpx;
}
.font-size-extra-lg {
font-size: 40rpx;
}
.text-color-base {
color: $text-color-base;
}
.text-color-assist {
color: $text-color-assist;
}
.text-color-primary {
color: $color-primary;
}
.text-color-danger {
color: $color-error;
}
.text-color-white {
color: #ffffff;
}
.text-color-warning {
color: $color-warning;
}
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.font-weight-bold {
font-weight: 700 !important;
}
.font-weight-light {
font-weight: 300 !important;
}
.font-weight-lighter {
font-weight: lighter !important;
}
.font-weight-normal {
font-weight: 400 !important;
}
.overflow-auto {
overflow: auto !important;
}
.overflow-hidden {
overflow: hidden !important;
}
.position-relative {
position: relative !important;
}
.position-absolute {
position: absolute !important;
}
.position-fixed {
position: fixed !important;
}
.fixed-top {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1030;
}
.fixed-bottom {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 1030;
}
.line-height-100 {
line-height: 100%;
}
.line-height-2 {
line-height: 2rem !important;
}
.line-height-50 {
line-height: 50rem !important;
}
.w-25 {
width: 25% !important;
}
.w-50 {
width: 50% !important;
}
.w-75 {
width: 75% !important;
}
.w-80 {
width: 80% !important;
}
.w-90 {
width: 90% !important;
}
.w-100 {
width: 100% !important;
}
.h-100 {
height: 100% !important;
}
.text-left {
text-align: left !important;
}
.text-right {
text-align: right !important;
}
.text-center {
text-align: center !important;
}
.border-box {
box-sizing: border-box;
}
.rounded-circle {
border-radius: 50% !important;
}
.rounded-pill {
border-radius: 50rem !important;
}
.border-radius-base {
border-radius: $border-radius-base;
}
.pre-line {
white-space: pre-line;
}
.align-top {
vertical-align: top !important;
}
.align-middle {
vertical-align: middle !important;
}
.align-bottom {
vertical-align: bottom !important;
}
.align-text-bottom {
vertical-align: text-bottom !important;
}
.align-text-top {
vertical-align: text-top !important;
}
.w-60 {
width: 60%;
}
.w-40 {
width: 40%;
}
.mb-10 {
margin-bottom: 10rpx;
}
.mb-20 {
margin-bottom: 20rpx;
}
.mb-30 {
margin-bottom: 30rpx;
}
.mb-40 {
margin-bottom: 40rpx;
}
.mb-50 {
margin-bottom: 50rpx;
}
.mt-30 {
margin-top: 30rpx;
}
.ml-10 {
margin-left: 10rpx;
}
.ml-20 {
margin-left: 20rpx;
}
.ml-30 {
margin-left: 30rpx;
}
.mr-10 {
margin-right: 10rpx;
}
.mr-20 {
margin-right: 20rpx;
}
.mr-30 {
margin-right: 30rpx;
}
.mr-40 {
margin-right: 40rpx;
}
.pl-30 {
padding-left: 30rpx;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because it is too large. Load diff
Loaded 100 of 4685 files, more files were not shown because too many files have changed in this diff. Show more