完善页面

This commit is contained in:
wangxiaowei
2025-08-28 18:20:17 +08:00
parent fdaa01f801
commit e1a4f57610
27 changed files with 953 additions and 178 deletions

View File

@ -7,8 +7,9 @@
使用Unibest开发版本号3.8.2 使用Unibest开发版本号3.8.2
</div> </div>
<div>开发规范</div> ### 开发规范
1. 页面或组件添加方法的时候前面加上<b>handle</b>关键字除了插件相关内容如mescroll #### 1. 页面或组件添加方法的时候前面加上<b>handle</b>关键字除了插件相关内容如mescroll
```
handleConfirmHour: () => { handleConfirmHour: () => {
if (totalHour.value <= 0) { if (totalHour.value <= 0) {
toast.info('至少起订N小时') toast.info('至少起订N小时')
@ -16,7 +17,9 @@
} }
showReservePopup.value = false showReservePopup.value = false
}, },
2. 页面或组件传创建的方法需要以当前文件名称作为对象按照驼峰命名 ```
#### 2. 页面或组件传创建的方法需要以当前文件名称作为对象按照驼峰命名
```
const detail = { const detail = {
handleConfirmHour: () => { handleConfirmHour: () => {
if (totalHour.value <= 0) { if (totalHour.value <= 0) {
@ -26,7 +29,10 @@
showReservePopup.value = false showReservePopup.value = false
} }
} }
3. 页面或组件传创建的内部方法需要放在函数最下面这种方法前面不需要加上handle ```
#### 3. 页面或组件传创建的内部方法需要放在函数最下面这种方法前面不需要加上handle
```
const detail = { const detail = {
handleConfirmHour: () => { handleConfirmHour: () => {
if (totalHour.value <= 0) { if (totalHour.value <= 0) {
@ -48,7 +54,10 @@
return `${year}-${month}-${day} ${hour}:${minute}` return `${year}-${month}-${day} ${hour}:${minute}`
} }
} }
4. 如果组件名称与定义的对象函数名冲突则对象函数名后面加上s ```
#### 4. 如果组件名称与定义的对象函数名冲突则对象函数名后面加上s
```
import coupon from '@/components/coupon/coupon.vue' import coupon from '@/components/coupon/coupon.vue'
const coupons = { const coupons = {
handleConfirmHour: () => { handleConfirmHour: () => {
@ -70,4 +79,10 @@
return `${year}-${month}-${day} ${hour}:${minute}` return `${year}-${month}-${day} ${hour}:${minute}`
} }
} }
```
#### 5. 组件名称需按照PascalCase来写
```
import UserProfile from './components/UserProfile.vue';
在页面模板中按照 <user-profile></user-profile>来使用
```

View File

@ -32,7 +32,7 @@
:checked="item.id === checkedId" :checked="item.id === checkedId"
:onCheck="coupons.handleCheck" :onCheck="coupons.handleCheck"
:class="index !== couponList.length - 1 ? 'mb-20rpx' : ''" :class="index !== couponList.length - 1 ? 'mb-20rpx' : ''"
/> ></coupon>
</wd-radio-group> </wd-radio-group>
</view> </view>
</view> </view>
@ -51,7 +51,7 @@
:checked="item.id === checkedId" :checked="item.id === checkedId"
:onCheck="coupons.handleCheck" :onCheck="coupons.handleCheck"
:class="index !== couponList.length - 1 ? 'mb-20rpx' : ''" :class="index !== couponList.length - 1 ? 'mb-20rpx' : ''"
/> ></coupon>
</wd-radio-group> </wd-radio-group>
</view> </view>
</view> </view>
@ -74,7 +74,7 @@
:checked="item.id === checkedId" :checked="item.id === checkedId"
:onCheck="coupons.handleCheck" :onCheck="coupons.handleCheck"
:class="index !== couponList.length - 1 ? 'mb-20rpx' : ''" :class="index !== couponList.length - 1 ? 'mb-20rpx' : ''"
/> ></group-coupon>
</wd-radio-group> </wd-radio-group>
</view> </view>
</view> </view>
@ -93,7 +93,7 @@
:checked="item.id === checkedId" :checked="item.id === checkedId"
:onCheck="coupons.handleCheck" :onCheck="coupons.handleCheck"
:class="index !== couponList.length - 1 ? 'mb-20rpx' : ''" :class="index !== couponList.length - 1 ? 'mb-20rpx' : ''"
/> ></group-coupon>
</wd-radio-group> </wd-radio-group>
</view> </view>
</view> </view>
@ -112,8 +112,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {ref} from 'vue' import {ref} from 'vue'
import coupon from '@/components/coupon/coupon.vue' import Coupon from '@/components/coupon/Coupon.vue'
import groupCoupon from '@/components/coupon/group-coupon.vue' import GroupCoupon from '@/components/coupon/GroupCoupon.vue'
const couponType = ref<number>(2) // couponType 1:优惠券 2:团购券 const couponType = ref<number>(2) // couponType 1:优惠券 2:团购券
const OSS = inject('OSS') const OSS = inject('OSS')

View File

@ -298,8 +298,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import {toast} from '@/utils/toast' import {toast} from '@/utils/toast'
import PriceFormat from '@/components/PriceFormat.vue'
const OSS = inject('OSS') const OSS = inject('OSS')
@ -310,7 +310,7 @@
]) ])
const current = ref<number>(0) const current = ref<number>(0)
const html: string = '<p>这里是富文本内容,需要后台传递</p>' const html: string = '<p>这里是富文本内容,需要后台传递</p>'
const isGroupBuying: boolean = true // 是否是团购套餐 const isGroupBuying: boolean = false // 是否是团购套餐
const pay = ref<number>(1) // 支付方式 const pay = ref<number>(1) // 支付方式
const payList = ref<Array<any>>([ const payList = ref<Array<any>>([
{ {
@ -431,7 +431,9 @@
uni.navigateTo({ url: `/bundle/coupon/coupon?type=${type}` }) uni.navigateTo({ url: `/bundle/coupon/coupon?type=${type}` })
}, },
handlePay: () => { handlePay: () => {
// 这里需要判断下如果是预约的话跳转结果通知是reserve的团购是pay的
// uni.navigateTo({ url: '/bundle/reserve-room/result' }) // uni.navigateTo({ url: '/bundle/reserve-room/result' })
}, },

View File

@ -1,49 +0,0 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "结果通知",
"navigationBarBackgroundColor": "#FFF"
}
}
</route>
<template>
<view class="mx-62rpx">
<view class="w-300rpx h-278rpx mt-84rpx mx-auto">
<wd-img width="300rpx" height='278rpx' :src="`${OSS}images/reserve_room_image4.png`"></wd-img>
</view>
<view class="mt-60rpx text-center">
<view class="text-[#303133] text-36rpx leading-50rpx">茶室预约成功</view>
<view class="text-[#9CA3AF] text-28rpx leading-40rpx font-400 mt-20rpx">可以点击下方查看预约单具体信息</view>
</view>
<view class="mt-76rpx mx-30rpx flex justify-between items-center text-[32rpx] text-center">
<view class='bg-[#fff] text-[#303133] rounded-24rpx h-90rpx leading-90rpx w-300rpx mr-28rpx' @click="result.handleSeeOrder">查看订单</view>
<view class='bg-[#4C9F44] text-[#fff] rounded-24rpx h-90rpx leading-90rpx w-300rpx' @click="result.handleDone">完成</view>
</view>
</view>
</template>
<script lang="ts" setup>
const OSS = inject('OSS')
const result = {
handleSeeOrder: () => {
uni.navigateTo({
url: '/bundle/reserve-room/order'
})
},
handleDone: () => {
uni.navigateBack({
delta: 1
})
},
}
</script>
<style lang="scss">
page {
background-color: $cz-page-background;
}
</style>

View File

@ -43,7 +43,7 @@
</view> </view>
<view class="flex flex-col items-end"> <view class="flex flex-col items-end">
<view @click="room.handleToRecharge"> <view @click="room.handleToRecharge">
<rechargeBtn name="充值" /> <recharge-btn name="充值"></recharge-btn>
</view> </view>
<view class="text-24rpx text-[#818CA9] mt-18rpx">1分钟前有人充值</view> <view class="text-24rpx text-[#818CA9] mt-18rpx">1分钟前有人充值</view>
</view> </view>
@ -81,14 +81,14 @@
<wd-tab title="茶室预定" v-if="storeType != 2"> <wd-tab title="茶室预定" v-if="storeType != 2">
<view class="content mx-30rpx mt-34rpx"> <view class="content mx-30rpx mt-34rpx">
<mescroll-body @init="mescrollInit" @down="downCallback" @up="room.upCallback"> <mescroll-body @init="mescrollInit" @down="downCallback" @up="room.upCallback">
<roomList :is-reserve="true" :store-type="storeType"></roomList> <room-list :is-reserve="true" :store-type="storeType"></room-list>
</mescroll-body> </mescroll-body>
</view> </view>
</wd-tab> </wd-tab>
<wd-tab title="团购套餐"> <wd-tab title="团购套餐">
<view class="content mx-30rpx mt-34rpx"> <view class="content mx-30rpx mt-34rpx">
<mescroll-body @init="mescrollInit" @down="downCallback" @up="room.upCallback"> <mescroll-body @init="mescrollInit" @down="downCallback" @up="room.upCallback">
<roomList :is-group-buying="true" :is-reserve="false" :store-type="storeType"></roomList> <room-list :is-group-buying="true" :is-reserve="false" :store-type="storeType"></room-list>
</mescroll-body> </mescroll-body>
</view> </view>
</wd-tab> </wd-tab>
@ -109,11 +109,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app' import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js"; import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
import rechargeBtn from '@/components/recharge-btn.vue' import RechargeBtn from '@/components/RechargeBtn.vue'
import roomList from '@/components/reserve/room-list.vue' import RoomList from '@/components/reserve/RoomList.vue'
import {toast} from '@/utils/toast' import {toast} from '@/utils/toast'
const rightPadding = inject('capsuleOffset') const rightPadding = inject('capsuleOffset')

View File

@ -41,14 +41,13 @@
<view class="flex-1 bg-[#F8F9FA] text-[#9CA3AF] rounded-8rpx ml-28rpx h-80rpx leading-80rpx rounded-8rpx pl-28rpx">上海浦东新区茶室店铺</view> <view class="flex-1 bg-[#F8F9FA] text-[#9CA3AF] rounded-8rpx ml-28rpx h-80rpx leading-80rpx rounded-8rpx pl-28rpx">上海浦东新区茶室店铺</view>
</view> </view>
<view @click="storeRecharge.handleRecharge" class="fixed left-0 right-0 bottom-0 z-50 mx-60rpx flex items-center justify-center bg-[#4C9F44] rounded-8rpx text-[#fff] text-30rpx font-bold" :style="{ height: '90rpx', bottom: 'calc(env(safe-area-inset-bottom) + 26rpx)', opacity: canRecharge ? 1 : 0.5 }"> <view @click="storeRecharge.handleRecharge" class="fixed left-0 right-0 bottom-0 z-50 mx-60rpx flex items-center justify-center bg-[#4C9F44] rounded-8rpx text-[#fff] text-30rpx font-bold" :style="{ height: '90rpx', bottom: 'calc(env(safe-area-inset-bottom) + 26rpx)', city: canRecharge ? 1 : 0.5 }">
确定转入 确定转入
</view> </view>
</view> </view>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import { getNavBarHeight } from '@/utils/index' import { getNavBarHeight } from '@/utils/index'
let navbarHeight = ref<number>(0) let navbarHeight = ref<number>(0)

View File

@ -6,7 +6,7 @@
</text> </text>
</template> </template>
<script lang="ts" setup name="priceFormat"> <script lang="ts" setup name="PriceFormat">
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch } from 'vue'
const props = defineProps({ const props = defineProps({

View File

@ -5,7 +5,7 @@
</view> </view>
</template> </template>
<script lang="ts" setup name="rechargeBtn"> <script lang="ts" setup name="RechargeBtn">
defineProps({ defineProps({
name: { name: {
type: String, type: String,

View File

@ -20,7 +20,7 @@
</view> </view>
</template> </template>
<script lang="ts" setup name="groupCoupon"> <script lang="ts" setup name="GroupCoupon">
const OSS = inject('OSS') const OSS = inject('OSS')
defineProps<{ defineProps<{
coupon: { coupon: {

View File

@ -21,7 +21,7 @@
</view> </view>
</template> </template>
<script lang="ts" setup name="groupCoupon"> <script lang="ts" setup name="Coupon">
defineProps<{ defineProps<{
coupon: { coupon: {
id: number id: number

View File

@ -23,9 +23,9 @@
<!-- </view> --> <!-- </view> -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup name="Navbar">
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { getNavBarHeight, getCapsuleOffset } from '@/utils/index' import { getNavBarHeight } from '@/utils/index'
const OSS = inject('OSS') const OSS = inject('OSS')
const navbarHeight = ref<number>(0) const navbarHeight = ref<number>(0)

View File

@ -0,0 +1,91 @@
<template>
<view class="text-center">
<view class="w-120rpx h-120rpx mx-auto">
<wd-img width="120rpx" height="120rpx" mode="aspectFill" :src="`${OSS}icon/${info.icon}`" />
</view>
<view class="mt-40rpx">
<view class="font-400 text-32rpx leading-44rpx text-[#303133]">{{ info.title }}</view>
<view class="font-bold text-40rpx text-[#121212] leading-56rpx mt-24rpx">{{ Number(props.money).toFixed(2) }}</view>
</view>
<view class="mt-60rpx mb-38rpx w-100%">
<wd-gap height="2rpx" bgColor="#F6F7F9"></wd-gap>
</view>
<view class="mx-30rpx">
<view class="flex justify-between items-center leading-40rpx text-28rpx font-400">
<view class="text-[#606266]">当前状态</view>
<view class="text-[#303133]">{{ info.desc }}</view>
</view>
<view class="flex justify-between items-center leading-40rpx text-28rpx font-400 mt-20rpx">
<view class="text-[#606266]">{{ info.timeDesc }}</view>
<view class="text-[#303133]">{{ time }}</view>
</view>
<view class="flex justify-between items-center leading-40rpx text-28rpx font-400 mt-20rpx">
<view class="text-[#606266]">交易单号</view>
<view class="text-[#303133]">{{ order }}</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup name="PayNotice">
const OSS = inject('OSS')
const props = defineProps({
// 类型
type: {
type: String,
default: ''
},
// 描述
money: {
type: Number,
default: 0.00
},
time: {
type: String,
default: ''
},
order: {
type: String,
default: ''
}
})
const bill = {
recharge: {
icon: 'icon_pay_success.png',
title: '充值成功',
desc: '充值成功',
timeDesc: '支付时间'
},
refund: {
icon: 'icon_refund.png',
title: '退款成功',
desc: '已退款',
timeDesc: '退款时间'
},
cashback: {
icon: 'icon_cashback.png',
title: '推荐会员提现',
desc: '已返现',
timeDesc: '返现时间'
}
}
const info = computed(() => {
return bill[props.type] || {}
})
</script>
<script lang="ts">
export default {}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,40 @@
<template>
<view class="text-center">
<view class="w-120rpx h-120rpx mx-auto">
<wd-img width="120rpx" height="120rpx" mode="aspectFill" :src="`${OSS}icon/icon_pay_success.png`" />
</view>
<view class="mt-94rpx">
<view class="text-[36rpx] text-[#303133] leading-50rpx">{{ title }}</view>
<view class="text-[28rpx] text-[#9CA3AF] leading-40rpx mt-20rpx">{{ desc }}</view>
</view>
<view class="mt-78rpx">
<slot name="layout"></slot>
</view>
</view>
</template>
<script lang="ts" setup name="PayNotice">
const OSS = inject('OSS')
const props = defineProps({
// 标题
title: {
type: String,
default: ''
},
// 描述
desc: {
type: String,
default: ''
}
})
</script>
<script lang="ts">
export default {}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,40 @@
<template>
<view class="text-center">
<view class="w-300rpx h-300rpx mx-auto">
<wd-img width="300rpx" height="300rpx" mode="aspectFill" :src="`${OSS}images/reserve_room_image4.png`"></wd-img>
</view>
<view class="mt-60rpx text-center">
<view class="text-[#303133] text-36rpx leading-50rpx">{{ title }}</view>
<view class="text-[#9CA3AF] text-28rpx leading-40rpx font-400 mt-20rpx">{{ desc }}</view>
</view>
<view class="mt-78rpx">
<slot name="layout"></slot>
</view>
</view>
</template>
<script lang="ts" setup name="ReserveNotice">
const OSS = inject('OSS')
const props = defineProps({
// 标题
title: {
type: String,
default: ''
},
// 描述
desc: {
type: String,
default: ''
}
})
</script>
<script lang="ts">
export default {}
</script>
<style lang="scss" scoped>
</style>

View File

@ -65,12 +65,10 @@
</view> </view>
</template> </template>
<script lang="ts" setup name="roomList"> <script lang="ts" setup name="RoomList">
import { ref, watch, inject } from 'vue' import PriceFormat from '@/components/PriceFormat.vue'
import priceFormat from '@/components/price-format.vue';
import store from '@/store';
let OSS = inject('OSS') const OSS = inject('OSS')
const spec = ref<boolean>(true) const spec = ref<boolean>(true)

View File

@ -3,7 +3,7 @@ import { createSSRApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { requestInterceptor } from './http/interceptor' import { requestInterceptor } from './http/interceptor'
import { routeInterceptor } from './router/interceptor' import { routeInterceptor } from './router/interceptor'
import navBar from '@/components/navbar.vue' import NavBar from '@/components/Navbar.vue'
import store from './store' import store from './store'
import '@/style/index.scss' import '@/style/index.scss'
@ -15,7 +15,7 @@ import { getNavBarHeight, getCapsuleOffset } from '@/utils/index'
export function createApp() { export function createApp() {
const app = createSSRApp(App) const app = createSSRApp(App)
/* 注册全局组件 */ /* 注册全局组件 */
app.component('navBar', navBar) // 注册全局组件 app.component('NavBar', NavBar) // 注册全局组件
app.use(store) app.use(store)
app.use(routeInterceptor) app.use(routeInterceptor)

View File

@ -88,6 +88,24 @@
"navigationBarTitleText": "选择城市" "navigationBarTitleText": "选择城市"
} }
}, },
{
"path": "pages/login/login",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/login/mobile",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
},
{ {
"path": "pages/my/my", "path": "pages/my/my",
"type": "page", "type": "page",
@ -97,6 +115,33 @@
"navigationBarTitleText": "我的" "navigationBarTitleText": "我的"
} }
}, },
{
"path": "pages/notice/bill",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/notice/pay",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/notice/reserve",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
},
{ {
"path": "pages/reserve/reserve", "path": "pages/reserve/reserve",
"type": "page", "type": "page",
@ -105,30 +150,6 @@
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{
"path": "pages/result/bill",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/result/pay",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/result/reserve",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
},
{ {
"path": "pages/search/search", "path": "pages/search/search",
"type": "page", "type": "page",
@ -166,15 +187,6 @@
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{
"path": "reserve-room/result",
"type": "page",
"layout": "default",
"style": {
"navigationBarTitleText": "结果通知",
"navigationBarBackgroundColor": "#FFF"
}
},
{ {
"path": "reserve-room/room", "path": "reserve-room/room",
"type": "page", "type": "page",

View File

@ -107,7 +107,6 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'
import { getNavBarHeight } from '@/utils/index' import { getNavBarHeight } from '@/utils/index'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app' import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js"; import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js";
@ -119,6 +118,7 @@
`${OSS}images/banner1.png`, `${OSS}images/banner1.png`,
`${OSS}images/banner1.png` `${OSS}images/banner1.png`
]) ])
const current = ref<number>(0) const current = ref<number>(0)
const { mescrollInit, downCallback } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook const { mescrollInit, downCallback } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook

83
src/pages/login/login.vue Normal file
View File

@ -0,0 +1,83 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
}</route>
<template>
<view>
<view class="mx-48rpx mt-50rpx">
<view class="text-[#303133] text-40rpx leading-56rpx">
<text class="font-400 mr-24rpx">欢迎使用</text>
<text class="font-700">茶址</text>
</view>
<view class="font-400 text-26rpx leading-36rpx text-[#606266] mt-20rpx">登录后可进行茶室预约开启您的专属茶席</view>
</view>
<view class="mt-176rpx w-162rpx h-160rpx mx-auto">
<wd-img :src="`${OSS}images/home_image5.png`" width="100%" height="100%" mode="aspectFill"></wd-img>
</view>
<view class="mt-124rpx mx-60rpx box-border">
<wd-button open-type="getPhoneNumber" @getphonenumber="login.handleGetPhoneNumber" custom-class="!bg-[#4C9F44] !rounded-8rpx !text-[#fff] !text-30rpx !leading-42rpx !h-90rpx !w-[100%] box-border">手机号一键登录</wd-button>
<view class="text-30rpx font-400 text-[#303133] leading-42rpx text-center mt-32rpx">其它手机号登录</view>
</view>
<view class="flex items-center mx-32rpx mt-64rpx">
<view class="w-32rpx h-32rpx">
<wd-checkbox v-model="agree" @change="login.handleAgree" checked-color="#4C9F44" size="large"> </wd-checkbox>
</view>
<view class="font-400 text-26rpx leading-40rpx text-[#8F959E] ml-14rpx flex-1" @click="agree = !agree">
我已阅读并同意 <text class="text-[#4C9F44]" @click.stop="login.handleToService">服务协议</text> <text class="text-[#4C9F44]" @click.stop="login.handleToPrivacy">隐私政策</text>未注册手机号登录后将自动你为您创建账号
</view>
</view>
</view>
</template>
<script lang="ts" setup>
const OSS = inject('OSS')
const agree = ref<boolean>(false)
const login = {
// 获取手机号
handleGetPhoneNumber: (e: object) => {
console.log("🚀 ~ e:", e)
},
handleAgree: (e: any) => {
console.log('e', e)
},
// 跳转到服务协议页面
handleToService: () => {
},
// 跳转到隐私政策页面
handleToPrivacy: () => {
},
}
</script>
<style lang="scss">
page {
background-color: #fff;
}
.service {
:deep() {
.wd-checkbox {
display: flex;
align-content: flex-start;
}
.wd-checkbox__label {
margin-left: 6rpx;
}
}
}
</style>

189
src/pages/login/mobile.vue Normal file
View File

@ -0,0 +1,189 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
}</route>
<template>
<view>
<view class="mx-60rpx mt-20rpx">
<view class="text-[#303133] text-48rpx leading-80rpx font-600">
其他手机号登录
</view>
<view class="font-400 text-28rpx leading-44rpx text-[#6B7280] mt-12rpx">请输入你要登录的手机号</view>
</view>
<view class="mt-106rpx mx-48rpx">
<wd-form ref="form" :model="model">
<view>
<view class="font-400 text-30rpx text-[#606266] leading-44rpx">手机号</view>
<view class="mt-20rpx">
<wd-input
v-model="model.mobile"
type="text"
placeholder="请输入手机号码"
inputmode="numeric"
no-border
custom-class="!bg-[#F6F7F8] !border !border-solid !border-[#EAECF0] !rounded-16rpx"
custom-input-class="!px-32rpx !h-104rpx"
@input="mobile.handleInputMobile"
/>
</view>
</view>
<view class="mt-40rpx">
<view class="font-400 text-30rpx text-[#606266] leading-44rpx">验证码</view>
<view class="mt-20rpx">
<wd-input type="text" placeholder="请输入验证码" v-model="model.code" inputmode="numeric" no-border custom-class="!bg-[#F6F7F8] !border !border-solid !border-[#EAECF0] !rounded-16rpx" custom-input-class="!px-32rpx !h-104rpx">
<template #suffix>
<view class="flex items-center mr-34rpx">
<view class="flex items-center">
<wd-divider color="#C9C9C9" vertical />
</view>
<view class="flex items-center">
<view class="text-[#4C9F44] text-32rpx font-400 leading-44rpx" v-if="!startCountDown" @click="mobile.handleCountDown">发送验证码</view>
<view class="!text-[#C9C9C9] text-32rpx font-400 leading-44rpx flex items-center" v-if="startCountDown">
<wd-count-down ref="countDown" :time="countDownTime" millisecond :auto-start="false" format="ss" custom-class="!text-[#C9C9C9] !text-32rpx" @finish="mobile.handleFinishCountDown"></wd-count-down>
<view> S后重发</view>
</view>
</view>
</view>
</template>
</wd-input>
</view>
</view>
</wd-form>
</view>
<view class="h-90rpx leading-90rpx mx-60rpx rounded-8rpx text-center mt-112rpx bg-[#4C9F44] text-[#fff]" :class="disabled ? 'opacity-40' : ''" @click="mobile.handleToLogin">登录</view>
<view class="flex items-center mx-52rpx mt-56rpx">
<view class="w-32rpx h-32rpx">
<wd-checkbox v-model="agree" @change="mobile.handleAgree" checked-color="#4C9F44" size="large"> </wd-checkbox>
</view>
<view class="font-400 text-26rpx leading-40rpx text-[#8F959E] ml-14rpx flex-1" @click="agree = !agree">
我已阅读并同意 <text class="text-[#4C9F44]" @click.stop="mobile.handleToService">服务协议</text> <text class="text-[#4C9F44]" @click.stop="mobile.handleToPrivacy">隐私政策</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import {mobile as testMobile} from '@/utils/test'
import { useToast } from 'wot-design-uni'
const toast = useToast()
const disabled = ref<boolean>(true)
// 验证码倒计时
const countDownTime = ref<number>(1 * 60 * 1000) // 60s倒计时
const startCountDown = ref<boolean>(false) // 是否开始倒计时
const countDown = ref<any>(null) // 倒计时组件
// 表单相关
const model = reactive<{
mobile: string
code: string
}>({
mobile: '',
code: ''
})
// 服务协议和隐私政策
const agree = ref<boolean>(false)
const mobile = {
// 验证手机号
handleInputMobile: (e: {value: string}) => {
model.mobile = e.value
disabled.value = !testMobile(model.mobile)
},
// 发送验证码
handleCountDown: () => {
if (disabled.value) {
toast.show({
iconClass: 'info-circle',
msg: '手机号码错误请重新输入',
direction: 'vertical'
})
return
}
startCountDown.value = true
nextTick(() => {
countDown.value?.start()
// 发送验证码请求
})
},
// 验证码倒计时结束
handleFinishCountDown: () => {
startCountDown.value = false
},
// 登录
handleToLogin: () => {
if (!testMobile(model.mobile)) {
toast.show({
iconClass: 'info-circle',
msg: '手机号码错误请重新输入',
direction: 'vertical'
})
return
}
if (!model.code) {
toast.show({
iconClass: 'info-circle',
msg: '验证码错误',
direction: 'vertical'
})
return
}
},
// 获取手机号
handleGetPhoneNumber: (e: object) => {
console.log("🚀 ~ e:", e)
},
handleAgree: (e: any) => {
console.log('e', e)
},
// 跳转到服务协议页面
handleToService: () => {
disabled.value = !disabled.value
console.log("🚀 ~ disabled:", disabled)
},
// 跳转到隐私政策页面
handleToPrivacy: () => {
},
}
</script>
<style lang="scss">
page {
background-color: #fff;
}
.service {
:deep() {
.wd-checkbox {
display: flex;
align-content: flex-start;
}
.wd-checkbox__label {
margin-left: 6rpx;
}
}
}
</style>

26
src/pages/notice/bill.vue Normal file
View File

@ -0,0 +1,26 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
}</route>
<template>
<view class="mt-26rpx mx-30rpx">
<bill-notice :type="type" :money="10.00" :time="'2025-04-25 04:43'" :order="'42000028122025082279'"></bill-notice>
</view>
</template>
<script lang="ts" setup>
import BillNotice from '@/components/notice/Bill.vue'
const type = ref<string>('') // 购买类型 recharge: 充值; refund: 退款; cashback: 返现
onLoad((args) => {
type.value = args.type || ''
})
</script>
<style lang="scss" scoped>
</style>

51
src/pages/notice/pay.vue Normal file
View File

@ -0,0 +1,51 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
}</route>
<template>
<view class="mt-94rpx flex justify-center items-center">
<view v-if="type == 'room'">
<pay-notice title="购买成功" desc="可以点击下方查看订单详情">
<template #layout>
<view class="pb-22rpx mt-40rpx mx-30rpx flex justify-between items-center text-[32rpx] text-center">
<view class='bg-[#F6F7F8] text-[#303133] rounded-8rpx h-90rpx leading-90rpx mr-28rpx w-300rpx' @click="pay.handleRoomSeeOrder">查看订单</view>
<view class='bg-[#4C9F44] text-[#fff] rounded-8rpx h-90rpx leading-90rpx w-300rpx' @click="pay.handleRoomDone">完成</view>
</view>
</template>
</pay-notice>
</view>
</view>
</template>
<script lang="ts" setup>
import PayNotice from '@/components/notice/Pay.vue'
const type = ref<string>('') // 购买类型 room: 预约茶室
onLoad((args) => {
type.value = args.type || ''
})
const pay = {
// 预约茶室 - 查看订单
handleRoomSeeOrder: () => {
uni.navigateTo({
url: '/bundle/reserve-room/order'
})
},
// 预约茶室 - 完成
handleRoomDone: () => {
uni.switchTab({
url: '/pages/index/index'
})
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,51 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#fff"
}
}</route>
<template>
<view class="mt-84rpx flex justify-center items-center">
<view v-if="type == 'room'">
<reserve-notice title="茶室预约成功" desc="可以点击下方查看预约单具体信息">
<template #layout>
<view class="pb-22rpx mt-40rpx mx-30rpx flex justify-between items-center text-[32rpx] text-center">
<view class='bg-[#F6F7F8] text-[#303133] rounded-8rpx h-90rpx leading-90rpx mr-28rpx w-300rpx' @click="reserve.handleRoomSeeOrder">查看订单</view>
<view class='bg-[#4C9F44] text-[#fff] rounded-8rpx h-90rpx leading-90rpx w-300rpx' @click="reserve.handleRoomDone">完成</view>
</view>
</template>
</reserve-notice>
</view>
</view>
</template>
<script lang="ts" setup>
import ReserveNotice from '@/components/notice/Reserve.vue'
const type = ref<string>('') // 购买类型 room: 预约茶室
onLoad((args) => {
type.value = args.type || ''
})
const reserve = {
// 预约茶室 - 查看订单
handleRoomSeeOrder: () => {
uni.navigateTo({
url: '/bundle/reserve-room/order'
})
},
// 预约茶室 - 完成
handleRoomDone: () => {
uni.switchTab({
url: '/pages/index/index'
})
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,19 +0,0 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
}</route>
<script lang="ts" setup>
//
</script>
<template>
<view class="">
<!-- 此处是流水明细结果通知页面 -->
</view>
</template>
<style lang="scss" scoped>
//</style>

View File

@ -1,20 +0,0 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
}</route>
<template>
<view class="">
<!-- 此处是支付结果通知页面 -->
</view>
</template>
<script lang="ts" setup>
//
</script>
<style lang="scss" scoped>
//</style>

View File

@ -1,20 +0,0 @@
<route lang="jsonc" type="page">{
"layout": "default",
"style": {
"navigationBarTitleText": ""
}
}</route>
<template>
<view class="">
<!-- 此处是预约结果通知页面 -->
</view>
</template>
<script lang="ts" setup>
//
</script>
<style lang="scss" scoped>
//</style>

287
src/utils/test.ts Normal file
View File

@ -0,0 +1,287 @@
/**
* 验证电子邮箱格式
*/
function email(value) {
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
}
/**
* 验证手机格式
*/
function mobile(value) {
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
}
/**
* 验证URL格式
*/
function url(value) {
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
.test(value)
}
/**
* 验证日期格式
*/
function date(value) {
if (!value) return false
// 判断是否数值或者字符串数值(意味着为时间戳)转为数值否则new Date无法识别字符串时间戳
if (number(value)) value = +value
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证字符串
*/
function string(value) {
return typeof value === 'string'
}
/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
}
/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
} if (value.length === 8) {
return xreg.test(value)
}
return false
}
/**
* 金额,只允许2位小数
*/
function amount(value) {
// 金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}
/**
* 中文
*/
function chinese(value) {
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}
/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value)
}
/**
* 只能是字母或者数字
*/
function enOrNum(value) {
// 英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}
/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}
/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}
/**
* 是否固定电话
*/
function landline(value) {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}
/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
}
/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value === 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
return false
}
/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
}
/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]'
}
/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value)
}
/**
* 是否函数方法
* @param {Object} value
*/
function func(value) {
return typeof value === 'function'
}
/**
* 是否promise对象
* @param {Object} value
*/
function promise(value) {
return object(value) && func(value.then) && func(value.catch)
}
/** 是否图片格式
* @param {Object} value
*/
function image(value) {
const newValue = value.split('?')[0]
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
return IMAGE_REGEXP.test(newValue)
}
/**
* 是否视频格式
* @param {Object} value
*/
function video(value) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
return VIDEO_REGEXP.test(value)
}
/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
}
export {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
jsonString,
landline,
object,
array,
code,
func,
promise,
video,
image,
regExp,
string
}