feat(pages): 新增多个页面组件并优化现有页面功能

- 新增 amendPassword、healthInformation、stopperSelection、feedback、post、remainingSum 和 review 页面组件
-优化 my 页面的菜单列表和弹窗功能
- 修复 amendPassword 页面的样式问题
- 优化 remainingSum 页面的自定义充值功能
This commit is contained in:
jjLv 2025-01-10 15:49:19 +08:00
parent fd4cca8fe5
commit daa4d702dd
15 changed files with 1827 additions and 43 deletions

View File

@ -137,6 +137,12 @@
"navigationBarTitleText": ""
}
},
{
"path": "pages/newPassword",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/system",
"style": {
@ -172,6 +178,43 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/post/index",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/me/healthInformation",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/me/myAddress",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/feedback/index",
"style": {
"navigationBarTitleText": "投诉建议"
}
},
{
"path": "pages/review/index",
"style": {
"navigationBarTitleText": "食堂评价"
}
},
{
"path": "pages/advanceOrder/stopperSelection/index",
"style": {
"navigationBarTitleText": "档口选择"
}
}
],
"tabBar": {

View File

@ -0,0 +1,169 @@
<template>
<view class="page-container">
<view class="merchant-list">
<view
class="merchant-box"
v-for="(item, index) in merchantList"
:key="index"
>
<view class="merchant-item" @click="goToMenu(item)">
<view class="merchant-info">
<image
:src="item.avatar"
class="merchant-avatar"
mode="aspectFill"
></image>
<view class="merchant-detail">
<view class="merchant-name">{{ item.name }}</view>
<view class="merchant-stats">
<text>月销 {{ item.monthlySales }}</text>
<text class="business-hours">营业时间 {{ item.businessHours }}</text>
</view>
</view>
</view>
<view class="status-box" :class="{ 'status-open': item.isOpen }">
{{ item.isOpen ? '营业中' : '休息中' }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
merchantList: [
{
id: 1,
name: '宏雷大厨外卖',
avatar: '/static/logo.png',
monthlySales: 1984,
businessHours: '00:00-23:59',
isOpen: true
},
{
id: 2,
name: '年货预订',
avatar: '/static/logo.png',
monthlySales: 2,
businessHours: '00:00-14:59',
isOpen: false
}
]
}
},
methods: {
updateMerchantStatus() {
this.merchantList.forEach(merchant => {
merchant.isOpen = this.checkIfOpen(merchant.businessHours);
});
},
checkIfOpen(businessHours) {
//
const [start, end] = businessHours.split('-');
const now = new Date();
const currentTime = now.getHours() * 60 + now.getMinutes();
const [startHour, startMinute] = start.split(':').map(Number);
const [endHour, endMinute] = end.split(':').map(Number);
const startTime = startHour * 60 + startMinute;
const endTime = endHour * 60 + endMinute;
return currentTime >= startTime && currentTime <= endTime;
},
goToMenu(item) {
uni.navigateTo({
url: '/pages/advanceOrder/index'
})
}
},
mounted() {
this.updateMerchantStatus();
//
setInterval(this.updateMerchantStatus, 60000); //
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
padding: 40rpx;
}
.merchant-list {
.merchant-box {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
.merchant-item {
background-color: #ffffff;
border-radius: 12rpx;
padding: 24rpx;
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
}
.merchant-info {
display: flex;
align-items: center;
flex: 1;
margin-right: 20rpx;
.merchant-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.merchant-detail {
flex: 1;
.merchant-name {
font-size: 28rpx;
color: #333333;
margin-bottom: 16rpx;
font-weight: 500;
}
.merchant-stats {
display: flex;
font-size: 12px;
color: #999999;
flex-direction: column;
align-items: flex-start;
.business-hours {
//margin-left: 24rpx;
}
}
}
}
.status-box {
font-size: 24rpx;
color: #999999;
padding: 4rpx 12rpx;
background-color: #f5f5f5;
border-radius: 4rpx;
position: absolute;
top: 24rpx;
right: 24rpx;
&.status-open {
color: #ff6633;
background-color: #fff2ef;
}
}
}
</style>

View File

@ -1,19 +1,154 @@
<template>
<view>
忘记密码
</view>
<view class="password-page">
<view class="title">修改密码</view>
<view class="subtitle">输入手机号获取验证码设置新密码</view>
<u--form labelPosition="top" :model="form" ref="uForm">
<u-form-item label="手机号" prop="phone" leftIcon="phone" leftIconStyle="font-size: 22px" labelWidth="80">
<u--input
v-model="form.phone"
placeholder="请输入手机号"
border="none"
clearable
></u--input>
</u-form-item>
<u-form-item label="验证码" prop="code" leftIcon="chat" leftIconStyle="font-size: 22px" labelWidth="80">
<u-input
v-model="form.code"
placeholder="请输入验证码"
border="none"
clearable
>
<template slot="suffix">
<span
class="verify-code"
:class="{ disabled: counting }"
@click="getVerifyCode"
>
{{ counting ? `${counter}s后重新获取` : '获取验证码' }}
</span>
</template>
</u-input>
</u-form-item>
</u--form>
<view class="button-wrap">
<u-button
type="warning"
text="下一步"
shape="circle"
@click="handleNext"
:customStyle="{
width: '100%',
height: '90rpx',
background: '#ff6b00',
border: 'none'
}"
></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
};
}
}
export default {
data() {
return {
form: {
phone: '',
code: ''
},
counter: 60,
counting: false
}
},
methods: {
getVerifyCode() {
if (this.counting) return
//
if (!this.form.phone) {
uni.$u.toast('请输入手机号')
return
}
this.counting = true
this.counter = 60
const timer = setInterval(() => {
if (this.counter > 0) {
this.counter--
} else {
this.counting = false
clearInterval(timer)
}
}, 1000)
//
uni.$u.toast('验证码已发送')
},
handleNext() {
if (!this.form.phone || !this.form.code) {
uni.$u.toast('请填写完整信息')
return
}
//
uni.navigateTo({
url: '/pages/newPassword'
})
}
}
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.password-page {
padding: 40rpx;
background-color: #ffffff;
min-height: 100vh;
</style>
.title {
font-size: 40rpx;
font-weight: 550;
color: #333;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #999;
margin-bottom: 60rpx;
}
.verify-code {
color: #3c9cff;
font-size: 28rpx;
padding-left: 24rpx;
&.disabled {
color: #999;
}
}
.button-wrap {
margin-top: 80rpx;
}
}
::v-deep .u-form-item {
margin-bottom: 30rpx;
&__body {
padding: 24rpx 0;
border-bottom: 1px solid #eee;
}
}
::v-deep .u-input {
&__content__prefix-icon {
margin-right: 10rpx;
}
}
</style>

248
pages/feedback/index.vue Normal file
View File

@ -0,0 +1,248 @@
<template>
<view class="feedback-page">
<!-- 问题类型选择 -->
<view class="type-section">
<text class="section-title">您想反馈的问题类型</text>
<view class="tab-box">
<view
v-for="(item, index) in tabList"
:key="index"
class="tab-item"
:class="{ active: currentTab === index }"
@click="currentTab = index"
>
{{ item }}
</view>
</view>
</view>
<!-- 反馈内容 -->
<view class="content-box">
<view class="border-box">
<u--textarea
v-model="content"
placeholder="请详细补充您的问题或建议"
:maxlength="300"
height="200"
count
>
<text slot="count" class="word-count">{{ content.length }}/300</text>
</u--textarea>
<!-- 图片上传 -->
<view class="upload-box">
<u-upload
:fileList="fileList"
@afterRead="afterRead"
@delete="deletePic"
:maxCount="4"
multiple
>
<view class="upload-btn">
<u-icon name="camera" size="44" color="#666666"></u-icon>
<text class="upload-text">添加图片</text>
</view>
</u-upload>
</view>
</view>
<!-- 联系方式 -->
<view class="contact-box">
<text class="contact-title">请留下您的联系方式</text>
<u--input
v-model="contactInfo"
placeholder="电话号码/电子邮箱(仅工作人员可见)"
border="bottom"
></u--input>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-btn">
<u-button
text="提交"
shape="circle"
@click="submitFeedback"
:customStyle="{
width: '100%',
height: '88rpx',
background: '#ff6633',
color: '#ffffff',
border: 'none'
}"
></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabList: ['优化建议', '功能问题', '其他'],
currentTab: 0,
content: '',
fileList: [],
contactInfo: ''
}
},
methods: {
afterRead(event) {
const { file } = event;
this.fileList.push({
url: file.url,
status: 'uploading',
message: '上传中'
});
},
deletePic(event) {
this.fileList.splice(event.index, 1);
},
submitFeedback() {
//
uni.showToast({
title: '反馈已提交',
icon: 'success'
});
}
}
}
</script>
<style lang="scss" scoped>
.feedback-page {
min-height: 100vh;
background-color: #f8f8f8;
padding-bottom: 120rpx;
.type-section {
background-color: #ffffff;
padding: 30rpx 30rpx 20rpx;
.section-title {
font-size: 28rpx;
color: #333333;
margin-bottom: 20rpx;
font-weight: 550;
display: block;
}
}
.tab-box {
display: flex;
.tab-item {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666666;
padding: 16rpx 0;
position: relative;
border: 1px solid rgba(15,39,75,0.4);;
margin-right: 20rpx;
border-radius: 2px;
&.active {
color: #333333;
background-color: #fff2ef;
border: 1px solid #FF6816;
}
}
}
.content-box {
margin-top: 20rpx;
//background-color: #ffffff;
padding: 30rpx;
.border-box{
background-color: #ffffff;
padding: 16rpx;
border-radius: 8px;
}
.word-count {
position: absolute;
right: 20rpx;
bottom: 20rpx;
font-size: 24rpx;
color: #999999;
}
}
.upload-box {
margin-top: 30rpx;
.upload-btn {
width: 160rpx;
height: 160rpx;
background-color: #f7f8fa;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 8rpx;
.upload-text {
font-size: 24rpx;
color: #666666;
margin-top: 10rpx;
}
}
}
.contact-box {
margin-top: 40rpx;
background-color: #ffffff;
padding: 20rpx 16rpx 16rpx 16rpx;
border-radius: 8px;
.contact-title {
font-size: 28rpx;
color: #333333;
margin-bottom: 20rpx;
}
}
.submit-btn {
position: fixed;
left: 30rpx;
right: 30rpx;
bottom: 0;
padding: 20rpx 30rpx;
}
}
::v-deep .u-textarea {
padding: 20rpx;
background-color: #f7f8fa;
border-radius: 8rpx;
}
::v-deep .u-input {
&__content {
&__field-wrapper {
&__field {
font-size: 28rpx;
}
}
}
}
::v-deep .u-upload {
&__wrap {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
}
.u-textarea {
background-color: #ffffff;
}
.upload-btn {
background-color: #ffffff !important;
border: 1px solid #dadbde;
}
</style>

View File

@ -90,7 +90,8 @@
goToOrder() {
//
uni.navigateTo({
url: '/pages/advanceOrder/index'
// url: '/pages/advanceOrder/index'
url: '/pages/advanceOrder/stopperSelection/index'
})
},
goCode() {

View File

@ -123,18 +123,18 @@ export default {
},
//
async pwdLogin() {
// this.$store
// .dispatch('Login', this.loginForm)
// .then(() => {
// this.$modal.closeLoading() //
// this.loginSuccess()
// })
// .catch(() => {
// // if (this.captchaEnabled) {
// // this.getCode()
// // }
// })
this.$tab.reLaunch('/pages/system')
this.$store
.dispatch('Login', this.loginForm)
.then(() => {
this.$modal.closeLoading() //
this.loginSuccess()
})
.catch(() => {
// if (this.captchaEnabled) {
// this.getCode()
// }
})
// this.$tab.reLaunch('/pages/system')
},
//
loginSuccess(result) {

View File

@ -0,0 +1,245 @@
<template>
<view class="health-form">
<Navbar title="健康信息" :showRightText="true" :isBack="false" />
<u--form labelPosition="left" :model="form" :rules="rules" ref="uForm">
<u-form-item label="身高" prop="height" borderBottom required labelWidth="200rpx">
<u--input
v-model.number="form.height"
border="none"
placeholder="请输入身高cm"
type="number"
></u--input>
</u-form-item>
<u-form-item label="体重" prop="weight" borderBottom required labelWidth="200rpx">
<u--input
v-model.number="form.weight"
border="none"
placeholder="请输入体重kg"
type="number"
></u--input>
</u-form-item>
<u-form-item label="体型控制" prop="bodyType" borderBottom labelWidth="200rpx" @click="showSelector('bodyType')">
<u--input
v-model="displayBodyTypeName"
disabled
disabledColor="#ffffff"
placeholder="请选择"
border="none"
></u--input>
<u-icon slot="right" name="arrow-right"></u-icon>
</u-form-item>
<u-form-item label="BMI" prop="bmi" borderBottom labelWidth="200rpx">
<u--input
v-model="form.bmi"
disabled
border="none"
disabledColor="#ffffff"
placeholder="请输入身高、体重"
></u--input>
</u-form-item>
<u-form-item label="劳动强度" prop="laborIntensity" borderBottom required labelWidth="200rpx" @click="showSelector('laborIntensity')">
<u--input
v-model="displayLaborIntensityName"
disabled
border="none"
disabledColor="#ffffff"
placeholder="请选择"
></u--input>
<u-icon slot="right" name="arrow-right"></u-icon>
</u-form-item>
<u-form-item label="是否住院" prop="housing" borderBottom labelWidth="200rpx" @click="showSelector('housing')">
<u--input
v-model="displayHousingName"
disabled
border="none"
disabledColor="#ffffff"
placeholder="请选择"
></u--input>
<u-icon slot="right" name="arrow-right"></u-icon>
</u-form-item>
<u-form-item label="医嘱" borderBottom labelWidth="200rpx">
<text class="u-content-color">暂无医嘱信息</text>
</u-form-item>
<u-form-item label="过敏源" prop="allergies" borderBottom labelWidth="200rpx">
<u--input
v-model="form.allergies"
border="none"
placeholder="请输入过敏源"
></u--input>
</u-form-item>
</u--form>
<view class="chronic-diseases">
<text class="chronic-diseases__label">慢性病</text>
<u-checkbox-group v-model="form.chronicDiseases" borderBottom placement="column" iconSize="16">
<u-checkbox
v-for="(item, index) in chronicDiseaseOptions"
:key="index"
:name="item.value"
:label="item.label"
shape="circle"
></u-checkbox>
</u-checkbox-group>
</view>
<view class="submit-btn">
<u-button type="warning" text="保存" @click="handleSubmit"></u-button>
</view>
<u-action-sheet
v-if="currentSelector"
:show="selectorVisible"
:actions="getSelectorOptions(currentSelector)"
:title="getSelectorTitle(currentSelector)"
description="如果选择保密会报错"
@close="hideSelector"
@select="handleSelectorSelect"
>
</u-action-sheet>
</view>
</template>
<script>
export default {
data() {
return {
form: {
height: null,
weight: null,
bodyType: '',
bmi: '',
laborIntensity: '',
housing: '',
allergies: '',
chronicDiseases: []
},
selectorVisible: false,
currentSelector: null,
selectors: {
bodyType: { options: [{ name: '偏瘦', value: '1' }, { name: '正常', value: '2' }, { name: '偏胖', value: '3' }], title: '请选择体型' },
laborIntensity: { options: [{ name: '轻度', value: '1' }, { name: '中度', value: '2' }, { name: '重度', value: '3' }], title: '请选择劳动强度' },
housing: { options: [{ name: '自住', value: '1' }, { name: '租房', value: '2' }], title: '请选择住房类型' }
},
chronicDiseaseOptions: [
{ label: '糖尿病', value: 'diabetes' },
{ label: '肾结石', value: 'kidney_stone' },
{ label: '心脏疾病', value: 'heart_disease' },
{ label: '痛风', value: 'gout' }
],
rules: {
height: { type: 'number', required: true, message: '请输入身高', trigger: ['blur', 'change'] },
weight: { type: 'number', required: true, message: '请输入体重', trigger: ['blur', 'change'] },
laborIntensity: { type: 'string', required: true, message: '请选择劳动强度', trigger: ['blur', 'change'] }
},
displayBodyTypeName: '',
displayLaborIntensityName: '',
displayHousingName: ''
}
},
watch: {
'form.height': 'calculateBMI',
'form.weight': 'calculateBMI',
'form.bodyType': 'updateDisplayBodyTypeName',
'form.laborIntensity': 'updateDisplayLaborIntensityName',
'form.housing': 'updateDisplayHousingName'
},
methods: {
calculateBMI() {
if (this.form.height && this.form.weight) {
const heightInMeters = this.form.height / 100
const bmi = (this.form.weight / (heightInMeters * heightInMeters)).toFixed(1)
this.form.bmi = bmi
}
},
showSelector(selector) {
this.currentSelector = selector;
this.selectorVisible = true;
},
hideSelector() {
this.currentSelector = null;
this.selectorVisible = false;
},
getSelectorOptions(selector) {
return this.selectors[selector]?.options || [];
},
getSelectorTitle(selector) {
return this.selectors[selector]?.title || '';
},
handleSelectorSelect(e) {
this.form[this.currentSelector] = e.value;
this.hideSelector();
},
updateDisplayBodyTypeName() {
this.displayBodyTypeName = this.getDisplayName('bodyType', this.form.bodyType);
},
updateDisplayLaborIntensityName() {
this.displayLaborIntensityName = this.getDisplayName('laborIntensity', this.form.laborIntensity);
},
updateDisplayHousingName() {
this.displayHousingName = this.getDisplayName('housing', this.form.housing);
},
getDisplayName(selector, value) {
const options = this.selectors[selector]?.options || [];
const selectedOption = options.find(option => option.value === value);
return selectedOption ? selectedOption.name : '';
},
handleSubmit() {
this.$refs.uForm.validate().then(valid => {
if (valid) {
console.log('Form submitted:', this.form);
uni.$u.toast('保存成功');
} else {
uni.$u.toast('请填写完整信息');
}
}).catch(errors => {
console.error('Validation failed:', errors);
uni.$u.toast('请检查并修正错误');
});
},
hideKeyboard() {
uni.hideKeyboard();
}
}
}
</script>
<style scoped lang="scss">
.health-form {
padding: 20rpx;
background-color: #ffffff;
.chronic-diseases {
margin-top: 20rpx;
&__label {
font-size: 28rpx;
color: $u-main-color;
margin-bottom: 20rpx;
display: block;
}
}
.submit-btn {
margin-top: 40rpx;
}
}
::v-deep .u-form-item {
min-height: 100rpx;
&__body__right__content__slot {
text-align: right;
}
}
::v-deep .u-checkbox {
margin-bottom: 16rpx;
}
</style>

View File

@ -1,12 +1,12 @@
<template>
<view class="container">
<!-- 顶部导航栏 -->
<!-- <view class="header">-->
<!-- <view class="back-icon" @click="goBack">-->
<!-- <image class="icon" :src="require('@/static/images/my/notice.png')" mode="aspectFit"></image>-->
<!-- </view>-->
<!-- <text class="title">个人信息</text>-->
<!-- </view>-->
<!-- <view class="header"> -->
<!-- <view class="back-icon" @click="goBack"> -->
<!-- <image class="icon" :src="require('@/static/images/my/notice.png')" mode="aspectFit"></image> -->
<!-- </view> -->
<!-- <text class="title">个人信息</text> -->
<!-- </view> -->
<!-- 菜单列表 -->
<view class="menu-list">
@ -14,17 +14,30 @@
v-for="(item, index) in menuItems"
:key="index"
class="menu-item"
@click="navigateTo(item.path)"
@click="handleMenuItemClick(item.path, item.title)"
>
<text class="menu-text">{{ item.title }}</text>
<image class="arrow-icon" :src="require('@/static/images/my/enter.png')" mode="aspectFit"></image>
</view>
</view>
<!-- uView 弹框组件 -->
<u-modal
:show="showDialog"
:show-cancel-button="true"
title="提示"
:content="dialogContent"
@confirm="handleConfirm"
@cancel="handleCancel"
></u-modal>
</view>
</template>
<script>
import UModal from '@/uni_modules/uview-ui/components/u-modal/u-modal.vue'
export default {
components: { UModal },
data() {
return {
menuItems: [
@ -34,11 +47,11 @@ export default {
},
{
title: '我的地址',
path: '/pages/my-address/index'
path: '/pages/mine/me/myAddress'
},
{
title: '健康信息',
path: '/pages/health-info/index'
path: '/pages/mine/me/healthInformation'
},
{
title: '人脸上传',
@ -52,17 +65,45 @@ export default {
title: '挂失解绑',
path: '/pages/unbind/index'
}
]
],
showDialog: false, //
dialogContent: '' //
}
},
methods: {
goBack() {
uni.navigateBack()
},
handleMenuItemClick(path, title) {
if (title === '挂失解绑') {
console.log('挂失解绑')
this.dialogContent = '您确定要执行挂失解绑操作吗?'
this.showDialog = true
} else {
this.navigateTo(path)
}
},
navigateTo(url) {
uni.navigateTo({
url
})
},
handleConfirm() {
console.log('用户点击了确认')
//
this.showDialog = false
//
this.unbind()
},
handleCancel() {
console.log('用户点击了取消')
//
this.showDialog = false
},
unbind() {
//
console.log('执行挂失解绑操作')
// API
}
}
}
@ -100,4 +141,3 @@ export default {
}
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<view class="address-list">
<Navbar title="我的地址" :showRightText="true" :isBack="false" :text="'新增地址'" @clickIcon="addAddress"/>
<view
v-for="(item, index) in addressList"
:key="index"
class="address-item"
@click="handleEdit(index)"
>
<view class="address-info">
<text class="address-text">{{ item.address }}</text>
<view class="contact-info">
<text class="contact-text">{{ item.name }}</text>
<text class="contact-text">{{ item.phone }}</text>
</view>
</view>
<u-icon name="edit-pen-fill" color="#c8c9cc" size="22"></u-icon>
</view>
</view>
</template>
<script>
export default {
data() {
return {
addressList: [
{
address: '府前西街与站前北街交叉口南200米',
name: '冯先生1',
phone: '153****5876'
},
{
address: '怡馨家园30号楼(近达人街商城)',
name: '冯先生',
phone: '153****5876'
},
{
address: '西辛南区46号楼2单元101室',
name: '冯先生',
phone: '153****5876'
}
]
}
},
methods: {
handleEdit(index) {
//
uni.$u.toast(`编辑第${index + 1}个地址`)
},
addAddress() {
console.log('🚀 ~ handleRightText ~ ')
},
}
}
</script>
<style lang="scss" scoped>
.address-list {
min-height: 100vh;
background-color: #f8f8f8;
padding: 30rpx;
}
.address-item {
background-color: #ffffff;
border-radius: 8rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.address-info {
flex: 1;
.address-text {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
margin-bottom: 8rpx;
}
.contact-info {
display: flex;
align-items: center;
.contact-text {
font-size: 26rpx;
color: #999999;
&:first-child {
margin-right: 20rpx;
}
}
}
}
</style>

148
pages/newPassword.vue Normal file
View File

@ -0,0 +1,148 @@
<template>
<view class="password-page">
<view class="title">设置新密码</view>
<view class="subtitle">新密码至少8个字符不能全是字母或数字</view>
<u--form labelPosition="top" :model="form" ref="uForm">
<u-form-item label="新密码" prop="password" leftIcon="lock" leftIconStyle="font-size: 22px" labelWidth="80">
<u-input
v-model="form.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入新密码(字母加数字组合)"
border="none"
clearable
>
<template slot="suffix">
<u-icon
:name="showPassword ? 'eye-fill' : 'eye-off'"
size="20"
color="#c0c4cc"
@click="showPassword = !showPassword"
></u-icon>
</template>
</u-input>
</u-form-item>
<u-form-item label="确认密码" prop="confirmPassword" leftIcon="lock" leftIconStyle="font-size: 22px" labelWidth="120">
<u-input
v-model="form.confirmPassword"
:type="showConfirmPassword ? 'text' : 'password'"
placeholder="请再次输入新密码(字母加数字组合)"
border="none"
clearable
>
<template slot="suffix">
<u-icon
:name="showConfirmPassword ? 'eye-fill' : 'eye-off'"
size="20"
color="#c0c4cc"
@click="showConfirmPassword = !showConfirmPassword"
></u-icon>
</template>
</u-input>
</u-form-item>
</u--form>
<view class="button-wrap">
<u-button
type="warning"
text="确定"
shape="circle"
@click="handleSubmit"
:customStyle="{
width: '100%',
height: '90rpx',
background: '#ff6b00',
border: 'none'
}"
></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
form: {
password: '',
confirmPassword: ''
},
showPassword: false,
showConfirmPassword: false
}
},
methods: {
validatePassword(password) {
// 8
const reg = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/
return reg.test(password)
},
handleSubmit() {
if (!this.form.password || !this.form.confirmPassword) {
uni.$u.toast('请填写完整信息')
return
}
if (!this.validatePassword(this.form.password)) {
uni.$u.toast('密码必须至少8个字符包含字母和数字')
return
}
if (this.form.password !== this.form.confirmPassword) {
uni.$u.toast('两次输入的密码不一致')
return
}
//
uni.$u.toast('密码修改成功')
//
}
}
}
</script>
<style lang="scss" scoped>
.password-page {
padding: 40rpx;
background-color: #ffffff;
min-height: 100vh;
.title {
font-size: 40rpx;
font-weight: 550;
color: #333;
margin-bottom: 20rpx;
}
.subtitle {
font-size: 28rpx;
color: #999;
margin-bottom: 60rpx;
}
.button-wrap {
margin-top: 80rpx;
}
}
::v-deep .u-form-item {
margin-bottom: 30rpx;
&__body {
padding: 24rpx 0;
border-bottom: 1px solid #eee;
}
}
::v-deep .u-input {
&__content__prefix-icon {
margin-right: 10rpx;
}
&__content__suffix-icon {
margin-left: 10rpx;
}
}
</style>

52
pages/post/index.vue Normal file
View File

@ -0,0 +1,52 @@
<template>
<view>
<web-view :update-title="false" :webview-styles="webviewStyles" :src="webViewSrc"></web-view>
</view>
</template>
<script>
let wv // webview
export default {
data() {
return {
webViewHeight: 0,
webviewStyles: {
top: "30px"
},
phoneNumber: '15996330508'
};
},
computed: {
webViewSrc() {
return `https://static-mp-0ff94970-f63b-4d95-afbe-cab23f55e253.next.bspapp.com/web/index.html#/?mobile=${this.phoneNumber}`;
}
},
onReady() {
//
/* uni.getSystemInfo({
success: (res) => {
this.webViewHeight = res.screenHeight - 50; // 50px
console.log('屏幕高度:', this.webViewHeight)
},
fail: (err) => {
console.error('获取系统信息失败:', err);
}
});*/
// #ifdef APP-PLUS
const currentWebview = this.$scope.$getAppWebview() // webview
setTimeout(function() {
wv = currentWebview.children()[0]
wv.setStyle({
scalable: true
})
}, 1000); //
// #endif
}
};
</script>
<style lang="scss" scoped>
page {
background: #fff;
}
</style>

View File

@ -25,7 +25,7 @@
<div class="up">快捷充值</div>
<div class="box">
<div class="box-item" :class= "{ active: active == 1 }" @click="handleRecharge(1)">
<div class="box-item" :class="{ active: active == 1 }" @click="handleRecharge(1)">
<span></span>
<span>50.00</span>
<u-icon v-if="active == 1" class="icon" name="../../static/images/active.png" size="12" />
@ -51,13 +51,66 @@
<u-icon v-if="active == 5" class="icon" name="../../static/images/active.png" size="12" />
</div>
<div class="box-item" :class="{ active: active == 6 }" @click="handleRecharge(6)">
自定义
<span v-if="amount === ''" style="font-size: 18px">自定义</span>
<span v-else><span style="font-size: 18px;">{{amount}}.00</span></span>
<u-icon v-if="active == 6" class="icon" name="../../static/images/active.png" size="12" />
</div>
</div>
<!-- 充值 -->
<u-button shape="circle" color="#FF6816" @click="" style="height: 32; margin-top: 100px">立即充值</u-button>
<u-button shape="circle" color="#FF6816" @click="" style="height: 32px; margin-top: 100px">立即充值</u-button>
<u-popup
:show="localShow"
@close="closePopup"
mode="center"
round="12"
:closeOnClickOverlay="false"
>
<view class="amount-popup">
<view class="popup-title">自定义金额</view>
<view class="input-box">
<u--input
v-model="amount"
placeholder="输入金额"
prefixIcon="¥"
:prefixIconStyle="{
color: '#333',
fontSize: '32rpx',
fontWeight: 'bold'
}"
border="surround"
clearable
></u--input>
</view>
<view class="button-group">
<u-button
text="取消"
:customStyle="{
width: '240rpx',
height: '80rpx',
border: 'none',
background: '#f5f5f5',
color: '#666666'
}"
@click="closePopup"
></u-button>
<u-button
type="warning"
text="确认"
:customStyle="{
width: '240rpx',
height: '80rpx',
background: '#ff6633',
border: 'none'
}"
@click="confirm"
></u-button>
</view>
</view>
</u-popup>
</div>
</view>
</template>
@ -65,8 +118,10 @@
<script>
import { getAccountBalance } from '@/api/dining-hall'
import uIcon from '../../uni_modules/uview-ui/components/u-icon/u-icon.vue'
import UUpload from '../../uni_modules/uview-ui/components/u-upload/u-upload.vue'
export default {
components: { UUpload },
data() {
return {
//
@ -83,10 +138,14 @@ export default {
rechargeAmount: 0, //
payChannel: '', //
payType: '', //
thirdRechargeNum: '' //
thirdRechargeNum: '', //
localShow: false, //
amount: ''
}
},
onLoad() {},
onLoad() {
},
methods: {
handleRightText() {
console.log('🚀 ~ handleRightText ~ ')
@ -118,7 +177,28 @@ export default {
this.rechargeAmount = 500
} else if (index == 6) {
this.rechargeAmount = 0
this.openPopup()
}
},
openPopup() {
this.localShow = true
},
closePopup() {
this.localShow = false
},
confirm() {
if (!this.amount) {
uni.$u.toast('请输入金额')
return
}else if (!/^\d+$/.test(this.amount)) {
this.amount = ''
this.rechargeAmount = 0
uni.$u.toast('金额必须为整数')
return
}
this.$emit('confirm', this.rechargeAmount)
this.closePopup()
}
}
}
@ -128,8 +208,10 @@ export default {
page {
background: #fff;
}
.content {
padding: 16px;
.top-cont {
padding: 16px;
height: 190px;
@ -149,12 +231,14 @@ page {
display: flex;
position: absolute;
bottom: 16px;
.money-item {
margin-right: 60px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
.item {
margin-top: 10px;
font-size: 14px;
@ -170,10 +254,12 @@ page {
color: #0f274b;
line-height: 16px;
}
.box {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.box-item {
margin-bottom: 12px;
width: 30%;
@ -187,19 +273,64 @@ page {
justify-content: center;
align-items: center;
position: relative;
:first-child {
font-size: 12px;
}
.icon {
position: absolute;
right: 0;
bottom: 0;
}
}
.active {
background: rgba(255, 104, 22, 0.1);
border: 1px solid #ff6816;
}
}
}
.amount-popup {
width: 600rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 40rpx 30rpx;
.popup-title {
text-align: center;
font-size: 32rpx;
color: #333333;
font-weight: 500;
margin-bottom: 30rpx;
}
.input-box {
margin-bottom: 40rpx;
}
.button-group {
display: flex;
justify-content: space-between;
}
}
::v-deep .u-input {
&__content {
height: 80rpx;
background-color: #ffffff;
&__prefix-icon {
margin-right: 10rpx;
padding-left: 10rpx;
}
&__field-wrapper {
&__field {
font-size: 28rpx;
}
}
}
}
</style>

153
pages/review/index.vue Normal file
View File

@ -0,0 +1,153 @@
<template>
<view class="rating-page">
<!-- 评分区域 -->
<view class="rating-box">
<view class="rating-row">
<view
v-for="(item, index) in 5"
:key="index"
class="rating-item"
>
<u-icon
:name="index < overallRating ? 'heart-fill' : 'heart'"
:color="index < overallRating ? '#FF6365' : '#e5e5e5'"
size="45"
@click="overallRating = index + 1"
></u-icon>
<text :class="['rating-text', { active: index === overallRating - 1 }]">
{{ ratingTexts[index] }}
</text>
</view>
</view>
</view>
<!-- 分项评分 -->
<view class="detail-rating">
<view class="rating-item" v-for="(item, index) in ratingItems" :key="index">
<text class="item-label">{{ item.label }}</text>
<view class="hearts-row">
<u-icon
v-for="n in 5"
:key="n"
:name="n <= item.value ? 'heart-fill' : 'heart'"
:color="n <= item.value ? '#FF6365' : '#e5e5e5'"
size="36"
@click="item.value = n"
></u-icon>
</view>
</view>
</view>
<!-- 评价输入 -->
<view class="comment-box">
<u--textarea
v-model="comment"
placeholder="口味、服务、环境如何?有哪些想要提的建议?"
height="160"
></u--textarea>
</view>
<!-- 提交按钮 -->
<view class="submit-btn">
<u-button
text="提交"
shape="circle"
:customStyle="{
width: '100%',
height: '88rpx',
background: '#ff6633',
color: '#ffffff',
border: 'none'
}"
></u-button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
overallRating: 5,
ratingTexts: ['非常差', '较差', '一般', '推荐', '超赞'],
ratingItems: [
{ label: '口味', value: 5 },
{ label: '环境', value: 0 },
{ label: '服务', value: 0 }
],
comment: ''
}
}
}
</script>
<style lang="scss" scoped>
.rating-page {
min-height: 100vh;
background-color: #ffffff;
padding: 40rpx;
padding-bottom: 120rpx;
.rating-box {
margin-bottom: 60rpx;
.rating-row {
display: flex;
justify-content: space-between;
padding: 0 20rpx;
}
.rating-item {
display: flex;
flex-direction: column;
align-items: center;
.rating-text {
font-size: 24rpx;
color: #999999;
margin-top: 10rpx;
&.active {
color: #333333;
}
}
}
}
.detail-rating {
.rating-item {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.item-label {
width: 80rpx;
font-size: 28rpx;
color: #333333;
}
.hearts-row {
display: flex;
gap: 20rpx;
}
}
}
.comment-box {
margin-top: 40rpx;
}
.submit-btn {
position: fixed;
left: 30rpx;
right: 30rpx;
bottom: 40rpx;
}
}
::v-deep .u-textarea {
background-color: #f7f8fa;
border-radius: 8rpx;
padding: 20rpx;
}
</style>

View File

@ -52,6 +52,14 @@ export default {
handleItem(index) {
if (index === 0) {
this.$tab.reLaunch('/pages/index')
} else if (index === 1) {
} else if (index === 2) {
uni.navigateTo(
{
url : '/pages/post/index'
}
)
}
}
}
@ -63,12 +71,14 @@ page {
min-height: 100vh;
background-image: url('../static/images/system/bg.png');
}
.index_main {
/* background: #f2f6fa; */
overflow: auto;
padding: 24rpx 0;
box-sizing: border-box;
}
.back-top {
position: fixed;
width: 100%;
@ -77,6 +87,7 @@ page {
top: 0;
left: 0;
}
.swiper_nav {
width: 100%;
height: 300rpx;
@ -85,6 +96,7 @@ page {
padding: 0 24rpx;
box-sizing: border-box;
}
.main_menu {
display: flex;
justify-content: space-between;
@ -92,6 +104,7 @@ page {
padding: 0 12rpx;
box-sizing: border-box;
}
.main_menu_item {
z-index: 99;
margin: 0 12rpx;
@ -104,6 +117,7 @@ page {
flex-direction: column;
align-items: center;
justify-content: space-between;
.main_menu_name {
font-family: PingFang SC, PingFang SC;
font-weight: bold;
@ -112,6 +126,7 @@ page {
font-style: normal;
text-transform: none;
}
image {
display: flex;
width: 88rpx;
@ -119,6 +134,7 @@ page {
border-radius: 34rpx;
}
}
.yiz_menu {
width: calc(100% - 48rpx);
min-height: 480rpx;
@ -127,6 +143,7 @@ page {
margin-top: 24rpx;
margin-left: 24rpx;
}
.yiz_menu_tle {
font-family: PingFang SC, PingFang SC;
font-weight: bold;
@ -136,10 +153,12 @@ page {
text-transform: none;
padding: 28rpx 28rpx 0 28rpx;
}
.yiz_menu_nav {
display: flex;
align-items: center;
flex-wrap: wrap;
.yiz_menu_nav_item {
width: calc(100% / 4);
margin-top: 48rpx;
@ -147,12 +166,14 @@ page {
flex-direction: column;
justify-content: center;
align-items: center;
image {
display: block;
width: 88rpx;
height: 88rpx;
border-radius: 34rpx;
}
.yiz_menu_name {
font-family: PingFang SC, PingFang SC;
font-weight: 400;

View File

@ -13,7 +13,7 @@
placeholderStyle="font-weight: 400;font-size: 10px;color: rgba(15,39,75,0.4);"
@blur="handleSearch"
></u-input>
<div class="top-filter">
<div class="top-filter" @click="openPopup">
<span style="font-size: 12px; color: #0f274b">筛选</span>
<u-icon name="../../static/images/pull.png" />
</div>
@ -68,15 +68,122 @@
<div v-else class="flex justify-center align-center" style="height: 50vh">
<u-empty icon="../../static/images/not_order.png" text="暂无相关订单" textColor="#000" />
</div>
<!-- 筛选部分-->
<u-popup
:show="localShow"
@close="closePopup"
mode="bottom"
round="20"
>
<view class="filter-popup">
<!-- 订单日期 -->
<view class="section">
<view class="section-title">订单日期</view>
<view class="date-quick-select">
<view
v-for="(item, index) in dateOptions"
:key="index"
class="date-option"
:class="{ active: currentDateOption === index }"
@click="selectDateOption(index)"
>
{{ item.label }}
</view>
</view>
<view class="date-range">
<view class="date-input" @click="showStartCalendar = true">
<text class="date-placeholder" v-if="!startDate">开始时间</text>
<text v-else class="selected-date">{{ startDate }}</text>
</view>
<text class="date-separator">-</text>
<view class="date-input" @click="showEndCalendar = true">
<text class="date-placeholder" v-if="!endDate">结束时间</text>
<text v-else class="selected-date">{{ endDate }}</text>
</view>
</view>
</view>
<!-- 订单类型 -->
<view class="section">
<view class="section-title">订单类型</view>
<u-grid
:border="false"
col="3"
>
<u-grid-item
v-for="(item, index) in orderTypes"
:key="index"
@click="selectOrderType(index)"
>
<view
class="order-type-item"
:class="{ active: selectedOrderTypes.includes(index) }"
>
{{ item }}
</view>
</u-grid-item>
</u-grid>
</view>
<!-- 底部按钮 -->
<view class="bottom-buttons">
<u-button
text="清空"
shape="circle"
:customStyle="{
border: '2rpx solid #DDDDDD',
color: '#666666',
background: '#ffffff',
marginRight: '24px'
}"
@click="clearAll"
></u-button>
<u-button
type="warning"
text="确定"
shape="circle"
:customStyle="{
background: '#ff6633'
}"
@click="confirm"
></u-button>
</view>
</view>
<!-- 日期选择器 -->
<u-datetime-picker
:show="showStartCalendar"
@cancel="showStartCalendar = false"
@confirm="selectStartDate"
mode="date"
></u-datetime-picker>
<u-datetime-picker
:show="showEndCalendar"
@cancel="showEndCalendar = false"
@confirm="selectEndDate"
mode="date"
></u-datetime-picker>
</u-popup>
</div>
</template>
<script>
import { getOrderList } from '@/api/dining-hall'
import Tabs from '@/pages/components/Tabs.vue'
import { getDate } from '../../uni_modules/uni-datetime-picker/components/uni-datetime-picker/util'
export default {
components: { Tabs },
props: {
show: {
type: Boolean,
default: false
}
},
data() {
return {
tabIndex: 0,
@ -131,7 +238,37 @@ export default {
}
] //
}
]
],
localShow: this.show,
monthNum: 12,
minData:getDate(new Date()),
dateOptions: [
{ label: '近1个月', value: 1 },
{ label: '近3个月', value: 3 },
{ label: '近6个月', value: 6 }
],
currentDateOption: -1,
startDate: '',
endDate: '',
showStartCalendar: false,
showEndCalendar: false,
orderTypes: [
'当餐点餐',
'预订餐',
'报餐',
'自助餐',
'商城',
'设备',
'收款码',
'美团-到店',
'美团-外卖'
],
selectedOrderTypes: []
}
},
watch: {
show(newVal) {
this.localShow = newVal; // prop
}
},
methods: {
@ -195,6 +332,71 @@ export default {
case 11:
return '已完成'
}
},
openPopup() {
this.localShow = true;
},
closePopup() {
this.localShow = false;
},
selectDateOption(index) {
this.currentDateOption = index
const months = this.dateOptions[index].value
const end = new Date()
const start = new Date()
start.setMonth(start.getMonth() - months)
this.startDate = this.formatDate(start)
this.endDate = this.formatDate(end)
},
selectStartDate(e) {
console.log('selectStartDate', e)
const timeFormat = uni.$u.timeFormat
this.startDate = timeFormat(e.value, 'yyyy-mm-dd')
this.currentDateOption = -1 //
this.showStartCalendar = false
},
selectEndDate(e) {
const timeFormat = uni.$u.timeFormat
this.endDate = timeFormat(e.value, 'yyyy-mm-dd')
this.currentDateOption = -1
this.showEndCalendar = false
},
selectOrderType(index) {
const position = this.selectedOrderTypes.indexOf(index)
if (position > -1) {
this.selectedOrderTypes.splice(position, 1)
} else {
this.selectedOrderTypes.push(index)
}
},
clearAll() {
this.currentDateOption = -1
this.startDate = ''
this.endDate = ''
this.selectedOrderTypes = []
},
confirm() {
if (!this.startDate || !this.endDate) {
uni.$u.toast('请选择完整的日期范围')
return
}
this.$emit('confirm', {
dateRange: {
start: this.startDate,
end: this.endDate
},
orderTypes: this.selectedOrderTypes.map(index => this.orderTypes[index])
})
this.closePopup()
},
formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
}
}
@ -293,4 +495,102 @@ export default {
}
}
}
.filter-popup {
padding: 30rpx;
min-height: 60vh;
max-height: 90vh;
.section {
margin-bottom: 40rpx;
.section-title {
font-size: 32rpx;
color: #333333;
font-weight: 500;
margin-bottom: 20rpx;
}
}
.date-quick-select {
display: flex;
gap: 20rpx;
margin-bottom: 20rpx;
.date-option {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
border-radius: 100rpx;
font-size: 28rpx;
color: #666666;
&.active {
background-color: #fff2ef;
color: #ff6633;
}
}
}
.date-range {
display: flex;
align-items: center;
gap: 20rpx;
.date-input {
flex: 1;
height: 80rpx;
background-color: #f8f8f8;
border-radius: 100rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
font-size: 28rpx;
.date-placeholder {
color: #999999;
}
.selected-date {
color: #333333;
}
}
.date-separator {
color: #999999;
font-size: 28rpx;
}
}
.order-type-item {
width: 200rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f8f8;
border-radius: 25px;
font-size: 28rpx;
color: #666666;
margin: 10rpx auto;
&.active {
background-color: #fff2ef;
color: #ff6633;
}
}
.bottom-buttons {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 28px 16rpx;
background-color: #ffffff;
display: flex;
justify-content: space-between;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
}
</style>