feat: 新增部分页面静态页面
This commit is contained in:
46
pages.json
46
pages.json
@@ -59,16 +59,58 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orderRecord",
|
||||
"path": "order/orderRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "接单记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "recordList",
|
||||
"path": "order/recordList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "salary/salaryManage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "薪资管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "salary/salaryList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "薪资明细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "account/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的账户"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "healthCertificate/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康证"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "contact/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "紧急联系人"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "privacyManage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "隐私管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "AuthorizaManage/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "权限管理"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<!-- 分类展示:first-one 风格 -->
|
||||
<template>
|
||||
<view class="ss-flex-col">
|
||||
<view class="goods-box" v-for="item in pagination.list" :key="item.id">
|
||||
<s-goods-column
|
||||
size="sl"
|
||||
:data="item"
|
||||
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const props = defineProps({
|
||||
pagination: Object,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-box {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,66 +0,0 @@
|
||||
<!-- 分类展示:first-two 风格 -->
|
||||
<template>
|
||||
<view>
|
||||
<view class="ss-flex flex-wrap">
|
||||
<view class="goods-box" v-for="item in pagination?.list" :key="item.id">
|
||||
<view @click="sheep.$router.go('/pages/goods/index', { id: item.id })">
|
||||
<view class="goods-img">
|
||||
<image class="goods-img" :src="item.picUrl" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="goods-content">
|
||||
<view class="goods-title ss-line-1 ss-m-b-28">{{ item.name }}</view>
|
||||
<view class="goods-price">¥{{ fen2yuan(item.price) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { fen2yuan } from '@/sheep/hooks/useGoods';
|
||||
|
||||
const props = defineProps({
|
||||
pagination: Object,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-box {
|
||||
width: calc((100% - 20rpx) / 2);
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
height: 246rpx;
|
||||
border-radius: 10rpx 10rpx 0px 0px;
|
||||
}
|
||||
|
||||
.goods-content {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 0px 20rpx 4rpx rgba(199, 199, 199, 0.22);
|
||||
padding: 20rpx 0 32rpx 16rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 10rpx 10rpx;
|
||||
|
||||
.goods-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 24rpx;
|
||||
font-family: OPPOSANS;
|
||||
font-weight: 500;
|
||||
color: #e1212b;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
354
pages/index/components/order-code-popup.vue
Normal file
354
pages/index/components/order-code-popup.vue
Normal file
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<up-popup :show="show" mode="center" :round="5" safeAreaInsetBottom @close="onClose" :closeable="true">
|
||||
<view class="popup-wrap" style="width:680rpx;">
|
||||
<!-- 标题 -->
|
||||
<view class="popup-header">
|
||||
<text class="title">催单</text>
|
||||
<text class="order-no">#{{ orderIndex }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 查询输入 -->
|
||||
<view class="search-row">
|
||||
<view style="width:620rpx;">
|
||||
<input class="search-input" v-model="orderCode" placeholder="输入收件人手机尾号4位+取单号,如8927#11" />
|
||||
</view>
|
||||
<view style="margin-left:15rpx;">
|
||||
<up-button type="primary" size="small" text="查询" @click="queryOrder"></up-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 取货点 -->
|
||||
<view class="section">
|
||||
<view class="section-title">
|
||||
<text class="icon">🏬</text>
|
||||
<text class="text">取货点店铺名称</text>
|
||||
</view>
|
||||
<text class="muted">{{ pickupName }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 送餐地址 -->
|
||||
<view class="section">
|
||||
<view class="section-title">
|
||||
<text class="icon">🚩</text>
|
||||
<text class="text">送餐详细地址</text>
|
||||
</view>
|
||||
<text class="muted">{{ address }}</text>
|
||||
<view class="recipient">收货人名称(先生) 尾号{{ recipientTail }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 客户备注 -->
|
||||
<view class="customer-note">
|
||||
<text>顾客:{{ customerNote }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 交接备注 下拉 -->
|
||||
<view class="form-row">
|
||||
<text class="label">交接备注:</text>
|
||||
<picker :range="remarks" @change="onRemarkChange">
|
||||
<view class="picker-display">{{ selectedRemarkText }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 凭证 上传 -->
|
||||
<view class="form-row proof-row">
|
||||
<text class="label required">凭证:</text>
|
||||
<view class="proof-list">
|
||||
<view v-for="(img, idx) in proofImages" :key="idx" class="proof-item">
|
||||
<image :src="img" mode="aspectFill" class="proof-img" />
|
||||
<view class="remove" @click="removeProofImage(idx)">✕</view>
|
||||
</view>
|
||||
<view class="proof-add" @click="addProofImage">+</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<view class="footer-row">
|
||||
<up-button type="success" text="联系上一个骑手" icon="phone" @click="callPhone('13131008612')"></up-button>
|
||||
<view style="margin-left:15rpx;width:600rpx;">
|
||||
<up-button type="primary" text="确认交接" @click="onConfirm"></up-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
computed
|
||||
} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'confirm'])
|
||||
|
||||
// 响应式数据
|
||||
const orderIndex = ref(1)
|
||||
const orderCode = ref('')
|
||||
const pickupName = ref('取货店铺详细地址广东省广州市天河区学院站荷光路')
|
||||
const address = ref('送餐详细地址广东省广州市天河区华景新城软件园区A栋303室')
|
||||
const recipientTail = ref('1254')
|
||||
const customerNote = ref('依据餐量提供餐具')
|
||||
|
||||
const remarks = ['请选择 备注内容', '已核对身份证', '缺少配件', '地址异常']
|
||||
const selectedRemark = ref(0)
|
||||
const selectedRemarkText = computed(() => remarks[selectedRemark.value] || '')
|
||||
|
||||
const proofImages = ref([])
|
||||
|
||||
// 方法
|
||||
const onClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const queryOrder = () => {
|
||||
// 简单模拟查询:如果输入有内容则把 orderIndex 加 1 并回填部分数据
|
||||
if (!orderCode.value) {
|
||||
uni.showToast({
|
||||
title: '请输入查询条件',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
orderIndex.value += 1
|
||||
// 这里可以调用接口查询并填充 pickupName/address 等
|
||||
uni.showToast({
|
||||
title: '查询成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
// 拨打电话
|
||||
const callPhone = (phone) => {
|
||||
if (!phone) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到联系电话');
|
||||
return;
|
||||
}
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phone
|
||||
});
|
||||
}
|
||||
|
||||
const onRemarkChange = (e) => {
|
||||
selectedRemark.value = e.detail.value
|
||||
}
|
||||
|
||||
const addProofImage = () => {
|
||||
// 使用 uni.chooseImage 上传
|
||||
uni.chooseImage({
|
||||
count: 4,
|
||||
success(res) {
|
||||
const tempFiles = res.tempFilePaths || res.tempFiles.map(f => f.path)
|
||||
proofImages.value = proofImages.value.concat(tempFiles)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const removeProofImage = (idx) => {
|
||||
proofImages.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
// 校验凭证
|
||||
if (proofImages.value.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请上传凭证',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
orderIndex: orderIndex.value,
|
||||
orderCode: orderCode.value,
|
||||
remark: selectedRemarkText.value,
|
||||
proofImages: proofImages.value
|
||||
}
|
||||
emit('confirm', payload)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.popup-wrap {
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #00a0df;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 120rpx;
|
||||
height: 64rpx;
|
||||
font-size: 22rpx;
|
||||
background: #fff;
|
||||
border-left: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 12rpx 0;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.section-title .icon {
|
||||
margin-right: 12rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.section-title .text {
|
||||
font-weight: 700;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #666;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
color: #999;
|
||||
font-size: 20rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.customer-note {
|
||||
background: #f7f7f7;
|
||||
padding: 12rpx;
|
||||
border-radius: 6rpx;
|
||||
margin: 12rpx 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 12rpx 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 180rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #e53935;
|
||||
}
|
||||
|
||||
.picker-display {
|
||||
flex: 1;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
border: 1rpx solid #e6e6e6;
|
||||
padding: 0 12rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.proof-row .proof-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.proof-item {
|
||||
position: relative;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
margin-right: 14rpx;
|
||||
}
|
||||
|
||||
.proof-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.remove {
|
||||
position: absolute;
|
||||
right: 4rpx;
|
||||
top: 4rpx;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
padding: 2rpx 6rpx;
|
||||
border-radius: 10rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.proof-add {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border: 1rpx dashed #ddd;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.footer-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.phone-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phone-icon {
|
||||
font-size: 30rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: #0a99e6;
|
||||
color: #fff;
|
||||
padding: 14rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,97 +0,0 @@
|
||||
<!-- 分类展示:second-one 风格 -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 一级分类的名字 -->
|
||||
<!-- <view class="title-box ss-flex ss-col-center ss-row-center ss-p-b-30">
|
||||
<view class="title-line-left" />
|
||||
<view class="title-text ss-p-x-20">{{ props.data[activeMenu].name }}</view>
|
||||
<view class="title-line-right" />
|
||||
</view> -->
|
||||
<view class="title-box ss-flex ss-p-b-30">
|
||||
<view class="theme-line"></view>
|
||||
<view class="title-text">{{ props.data[activeMenu].name }}</view>
|
||||
</view>
|
||||
<!-- 二级分类的名字 -->
|
||||
<view class="goods-item-box ss-flex ss-flex-wrap ss-p-b-20">
|
||||
<view
|
||||
class="goods-item"
|
||||
v-for="item in props.data[activeMenu].children"
|
||||
:key="item.id"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/goods/list', {
|
||||
categoryId: item.id,
|
||||
})
|
||||
"
|
||||
>
|
||||
<image class="goods-img" :src="item.picUrl" mode="aspectFill" />
|
||||
<view class="ss-p-10">
|
||||
<view class="goods-title ss-line-1">{{ item.name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
activeMenu: [Number, String],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-box {
|
||||
font-weight: 800;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
|
||||
.title-line-left,
|
||||
.title-line-right {
|
||||
width: 15px;
|
||||
height: 1px;
|
||||
background: #d2d2d2;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
width: calc((100% - 20px) / 3);
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: calc((100vw - 140px) / 3);
|
||||
height: calc((100vw - 140px) / 3);
|
||||
}
|
||||
|
||||
.goods-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
color: $red;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-line {
|
||||
margin-right: 15rpx;
|
||||
width: 8rpx;
|
||||
height: 28rpx;
|
||||
background: var(--ui-BG-Main);
|
||||
border-radius: 8rpx 8rpx 8rpx 8rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -6,7 +6,8 @@
|
||||
<view class="top-bg"></view>
|
||||
<view class="top-inner">
|
||||
<view class="user-info">
|
||||
<image class="user-avatar" :src="driverInfo.avatar || defaultAvatar" mode="cover" />
|
||||
<image class="user-avatar" @tap="sheep.$router.go('/pages/index/user')"
|
||||
:src="driverInfo.avatar || defaultAvatar" mode="cover" />
|
||||
<view class="user-meta">
|
||||
<!-- <text class="user-name">{{ driverInfo.nickName || '骑手姓名' }}</text> -->
|
||||
<text class="user-status" @click="toggleOnline">{{ driverInfo.isOnline ? '在线中' : '离线' }}</text>
|
||||
@@ -25,6 +26,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 输入用户单编码 弹窗 -->
|
||||
<order-code-popup :show="orderPopupShow" @close="orderPopupShow = false" @confirm="onConfirmCode" />
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<scroll-view class="order-list" scroll-y="true" :style="{ height: listHeight + 'px' }">
|
||||
<view v-for="order in filteredOrders" :key="order.id" class="order-card">
|
||||
@@ -89,6 +93,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import OrderCodePopup from './components/order-code-popup.vue';
|
||||
|
||||
// 驿站/骑手信息(从 store 获取或 mock)
|
||||
const driverInfo = ref({
|
||||
@@ -211,10 +216,18 @@ function scanQr() {
|
||||
});
|
||||
}
|
||||
|
||||
const orderPopupShow = ref(false);
|
||||
|
||||
function openManualInput() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/user' // 示例跳转,按需替换为真实手动输入页面
|
||||
});
|
||||
// 打开手动输入弹窗(使用 uView Plus 的 up-popup)
|
||||
orderPopupShow.value = true;
|
||||
}
|
||||
|
||||
function onConfirmCode(payload) {
|
||||
// payload 包含 code, result, remark, images
|
||||
// 这里简单展示提示,实际应调用后端或触发下一步逻辑
|
||||
console.log('confirmed code:', payload);
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('交接已确认');
|
||||
}
|
||||
|
||||
const headerStyle = ref({});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<view class="header-wrap" :style="headerStyle">
|
||||
<view class="header-bg"></view>
|
||||
<view class="header-inner">
|
||||
<image class="avatar" :src="userInfo.avatar || defautAvatar"></image>
|
||||
<image class="avatar" :src="userInfo.avatar || defautAvatar" @tap="sheep.$router.go('/pages/user/info')"></image>
|
||||
<view class="user-meta" v-if="userInfo.nickname">
|
||||
<view class="user-name">{{ userInfo.nickname + `(${userInfo.mobile})` }}</view>
|
||||
<view class="user-status" @click="handleStatusToggle">
|
||||
@@ -23,14 +23,15 @@
|
||||
<view class="stats-item">
|
||||
<text class="stats-title">今日预计收入(元)</text>
|
||||
<text class="stats-value"> {{ formatMoney(todayIncome) }} </text>
|
||||
<view class="stats-link" @click="openAccount">
|
||||
<view class="stats-link" @tap="sheep.$router.go('/pages/user/account/index')">
|
||||
<!-- @click="openAccount" -->
|
||||
我的账户 <uni-icons type="right" size="13"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-title">今日完成单量(单)</text>
|
||||
<text class="stats-value"> {{ todayOrders }} </text>
|
||||
<view class="stats-link" @click="openOrders">
|
||||
<view class="stats-link" @tap="sheep.$router.go('/pages/user/order/orderRecord')">
|
||||
订单统计 <uni-icons type="right" size="13"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
@@ -43,11 +44,11 @@
|
||||
<image class="shortcut-icon" src="/static/img/order1.png" mode="aspectFit" />
|
||||
<text class="shortcut-text">考勤排班</text>
|
||||
</view>
|
||||
<view class="shortcut" @click="openSalary">
|
||||
<view class="shortcut" @tap="sheep.$router.go('/pages/user/salary/salaryManage')">
|
||||
<image class="shortcut-icon" src="/static/img/order2.png" mode="aspectFit" />
|
||||
<text class="shortcut-text">薪资助手</text>
|
||||
</view>
|
||||
<view class="shortcut" @click="openSetting">
|
||||
<view class="shortcut" @tap="sheep.$router.go('/pages/public/setting')">
|
||||
<!-- <image class="shortcut-icon" src="/static/img/edit.png" mode="aspectFit" /> -->
|
||||
<view class="shortcut-icon">
|
||||
<uni-icons type="gear" size="43"></uni-icons>
|
||||
@@ -214,7 +215,6 @@
|
||||
|
||||
onPageScroll(() => {});
|
||||
|
||||
// 跳转/交互方法(保留路由调用位置,用户可按需实现)
|
||||
function goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
@@ -225,30 +225,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
function openOrders() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/index/order-list'
|
||||
});
|
||||
}
|
||||
|
||||
function openAttendance() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/public/faq'
|
||||
});
|
||||
}
|
||||
|
||||
function openSalary() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/public/richtext'
|
||||
});
|
||||
}
|
||||
|
||||
function openSetting() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/public/setting'
|
||||
});
|
||||
}
|
||||
|
||||
function login() {
|
||||
showAuthModal();
|
||||
}
|
||||
|
||||
@@ -11,23 +11,47 @@
|
||||
|
||||
<view class="container-list">
|
||||
<uni-list :border="false">
|
||||
<uni-list-item
|
||||
title="个人信息"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/user/info')
|
||||
"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="可兼职时段报备"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/user/info')
|
||||
"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="通知与提示音"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/user/info')
|
||||
"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="隐私管理"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/user/privacyManage/index')
|
||||
"
|
||||
/>
|
||||
<!-- <uni-list-item
|
||||
title="当前版本"
|
||||
:rightText="appInfo.version"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="onCheckUpdate"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="本地缓存"
|
||||
:rightText="storageSize"
|
||||
showArrow
|
||||
:border="false"
|
||||
class="list-border"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="关于我们"
|
||||
showArrow
|
||||
clickable
|
||||
@@ -39,6 +63,22 @@
|
||||
})
|
||||
"
|
||||
/> -->
|
||||
<uni-list-item
|
||||
title="本地缓存"
|
||||
:rightText="storageSize"
|
||||
showArrow
|
||||
:border="false"
|
||||
class="list-border"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="版本号"
|
||||
:rightText="appInfo.version"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="onCheckUpdate"
|
||||
/>
|
||||
<!-- 为了过审 只有 iOS-App 有注销账号功能 -->
|
||||
<uni-list-item
|
||||
v-if="isLogin && sheep.$platform.os === 'ios' && sheep.$platform.name === 'App'"
|
||||
@@ -52,35 +92,6 @@
|
||||
/>
|
||||
</uni-list>
|
||||
</view>
|
||||
<view class="set-footer ss-flex-col ss-row-center ss-col-center">
|
||||
<view class="agreement-box ss-flex ss-col-center ss-m-b-40">
|
||||
<view class="ss-flex ss-col-center ss-m-b-10">
|
||||
<!-- <view
|
||||
class="tcp-text"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/public/richtext', {
|
||||
title: '用户协议'
|
||||
})
|
||||
"
|
||||
>
|
||||
《用户协议》
|
||||
</view>
|
||||
<view class="agreement-text">与</view> -->
|
||||
<view
|
||||
class="tcp-text"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/public/richtext', {
|
||||
title: '隐私协议'
|
||||
})
|
||||
"
|
||||
>
|
||||
《隐私协议》
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="copyright-text ss-m-b-10">{{ appInfo.copyright }}</view> -->
|
||||
<!-- <view class="copyright-text">{{ appInfo.copytime }}</view> -->
|
||||
</view>
|
||||
<su-fixed bottom placeholder>
|
||||
<view class="ss-p-x-20 ss-p-b-40">
|
||||
<button
|
||||
|
||||
169
pages/user/AuthorizaManage/index.vue
Normal file
169
pages/user/AuthorizaManage/index.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<s-layout title="设置" class="auth-manage-page">
|
||||
<view class="card">
|
||||
<view class="auth-item" v-for="item in items" :key="item.key" @click="openDialog(item.key)">
|
||||
<text class="label">{{ item.label }}</text>
|
||||
<up-icon name="arrow-right" color="#999" size="20"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<up-popup :show="showPopup" mode="bottom" @close="showPopup = false" :round="12" safeAreaInsetBottom>
|
||||
<view class="popup-body">
|
||||
<view class="title">{{ current.title }}</view>
|
||||
<view class="desc">{{ current.desc }}</view>
|
||||
<view class="btn-row">
|
||||
<up-button plain @click="showPopup = false">再想想</up-button>
|
||||
<up-button type="primary" @click="onGoSetting">去设置</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const showPopup = ref(false);
|
||||
const currentKey = ref('');
|
||||
|
||||
const items = [
|
||||
{ key: 'album', label: '相册权限', title: '相册权限', desc: '关闭后,将无法上传相册中的照片或视频,也无法下载作品至你的相册' },
|
||||
{ key: 'camera', label: '相机权限', title: '相机权限', desc: '关闭后,将无法拍摄照片或视频上传' },
|
||||
{ key: 'location', label: '位置权限', title: '位置权限', desc: '关闭后,将无法获取位置信息,影响部分定位功能' },
|
||||
{ key: 'microphone', label: '麦克风权限', title: '麦克风权限', desc: '关闭后,将无法录制语音或视频的声音' },
|
||||
{ key: 'other', label: '其它权限', title: '其它权限', desc: '关闭后,可能影响部分功能的正常使用' },
|
||||
];
|
||||
|
||||
const current = reactive({ title: '', desc: '' });
|
||||
|
||||
function openDialog(key) {
|
||||
currentKey.value = key;
|
||||
const item = items.find((i) => i.key === key) || items[0];
|
||||
current.title = item.title;
|
||||
current.desc = item.desc;
|
||||
showPopup.value = true;
|
||||
}
|
||||
|
||||
function onGoSetting() {
|
||||
// 根据平台尝试申请或打开设置页
|
||||
const key = currentKey.value;
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU
|
||||
let mpScope = '';
|
||||
if (key === 'album') mpScope = 'scope.writePhotosAlbum';
|
||||
else if (key === 'camera') mpScope = 'scope.camera';
|
||||
else if (key === 'location') mpScope = 'scope.userLocation';
|
||||
else if (key === 'microphone') mpScope = 'scope.record';
|
||||
if (mpScope) {
|
||||
uni.authorize({
|
||||
scope: mpScope,
|
||||
success() {
|
||||
uni.showToast({ title: '授权成功', icon: 'none' });
|
||||
},
|
||||
fail() {
|
||||
// 打开小程序设置页
|
||||
if (uni.openSetting) {
|
||||
uni.openSetting({
|
||||
success() {
|
||||
//
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ title: '请在系统设置中开启权限', icon: 'none' });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5 无统一设置页,提示用户手动调整
|
||||
uni.showModal({
|
||||
title: current.title,
|
||||
content: '请在浏览器或系统设置中为本应用开启该权限',
|
||||
showCancel: true,
|
||||
confirmText: '知道了',
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// 在 APP 中打开系统设置或应用设置页
|
||||
openAppSetting();
|
||||
// #endif
|
||||
|
||||
showPopup.value = false;
|
||||
}
|
||||
|
||||
// APP 打开应用设置(参考 uview-plus 实现)
|
||||
function openAppSetting() {
|
||||
try {
|
||||
const isIOS = (plus.os.name && plus.os.name.toLowerCase().indexOf('ios') !== -1);
|
||||
if (isIOS) {
|
||||
var UIApplication = plus.ios.import('UIApplication');
|
||||
var application2 = UIApplication.sharedApplication();
|
||||
var NSURL2 = plus.ios.import('NSURL');
|
||||
var setting2 = NSURL2.URLWithString('app-settings:');
|
||||
application2.openURL(setting2);
|
||||
plus.ios.deleteObject(setting2);
|
||||
plus.ios.deleteObject(NSURL2);
|
||||
plus.ios.deleteObject(application2);
|
||||
} else {
|
||||
var Intent = plus.android.importClass('android.content.Intent');
|
||||
var Settings = plus.android.importClass('android.provider.Settings');
|
||||
var Uri = plus.android.importClass('android.net.Uri');
|
||||
var mainActivity = plus.android.runtimeMainActivity();
|
||||
var intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
var uri = Uri.fromParts('package', mainActivity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
mainActivity.startActivity(intent);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('openAppSetting fail', e);
|
||||
uni.showToast({ title: '打开设置失败,请手动前往系统设置', icon: 'none' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 6rpx 0;
|
||||
margin: 20rpx;
|
||||
}
|
||||
.auth-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.popup-body {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
.desc {
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.btn-row {
|
||||
display: flex;
|
||||
gap: 18rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-row up-button {
|
||||
width: 260rpx;
|
||||
}
|
||||
</style>
|
||||
270
pages/user/account/index.vue
Normal file
270
pages/user/account/index.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<s-layout title="我的账户" class="account-page">
|
||||
<view class="page-wrap">
|
||||
<!-- 顶部周期选择 -->
|
||||
<view class="top-row">
|
||||
<view class="period-select" @tap="showPicker = true">
|
||||
<text class="period-label">{{ selectedLabel }}</text>
|
||||
<up-icon name="arrow-down" color="#333" size="14"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 汇总卡片 -->
|
||||
<view class="summary-card">
|
||||
<text class="summary-amount">{{ formatSigned(totalAmount) }}</text>
|
||||
<text class="summary-sub">预计收入</text>
|
||||
<up-divider text=""></up-divider>
|
||||
<text class="summary-note">{{ summaryNote }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 交易列表 -->
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view class="txn-item" v-for="item in items" :key="item.id">
|
||||
<view class="left" style="width:530rpx;">
|
||||
<text class="txn-title">{{ item.desc }}</text>
|
||||
<text class="txn-sub">{{ item.date }} {{ item.time }}</text>
|
||||
</view>
|
||||
<view class="right">
|
||||
<text class="txn-amount" :class="{ positive: item.amount >= 0 }">
|
||||
{{ formatSigned(item.amount) }}
|
||||
</text>
|
||||
<text class="txn-status">{{ item.statusText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="no-more">没有更多了~</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- picker -->
|
||||
<up-picker
|
||||
:show="showPicker"
|
||||
:columns="columns"
|
||||
@confirm="onConfirm"
|
||||
@cancel="showPicker = false"
|
||||
@close="showPicker = false"
|
||||
></up-picker>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import PayWalletApi from '@/sheep/api/pay/wallet';
|
||||
|
||||
// picker
|
||||
const showPicker = ref(false);
|
||||
const selectedLabel = ref('今日账单');
|
||||
const columns = [
|
||||
[
|
||||
{ text: '今日账单', value: 'today' },
|
||||
{ text: '昨日账单', value: 'yesterday' },
|
||||
{ text: '本月账单', value: 'month' },
|
||||
],
|
||||
];
|
||||
|
||||
// 数据
|
||||
const items = ref([]);
|
||||
const totalAmount = ref(0);
|
||||
const summaryNote = ref('');
|
||||
|
||||
// 本地回退示例(便于开发)
|
||||
const testList = [
|
||||
{ id: 't1', time: '18:02', date: '06-15', desc: '配送收入-#59-林记番薯粥(潮汕白粥,小炒,海鲜鱼)', amount: 6.1, statusText: '未到账' },
|
||||
{ id: 't2', time: '17:49', date: '06-15', desc: '配送收入-#57-林记番薯粥(潮汕白粥,小炒,海鲜鱼)', amount: 5.3, statusText: '未到账' },
|
||||
{ id: 't3', time: '17:42', date: '06-15', desc: '配送收入-#25-桐坑粿条(原汤猪肠·柠檬粿条)', amount: 4.6, statusText: '未到账' },
|
||||
{ id: 't4', time: '17:27', date: '06-15', desc: '配送收入-#110-万辉超市(潮阳店)', amount: 3.6, statusText: '未到账' },
|
||||
{ id: 't5', time: '16:56', date: '06-15', desc: '配送收入-#145-仓鼠便利超市(潮阳店)', amount: 3.1, statusText: '未到账' },
|
||||
];
|
||||
|
||||
function formatSigned(val) {
|
||||
const n = Number(val) || 0;
|
||||
const sign = n > 0 ? '+' : n < 0 ? '' : '+';
|
||||
return `${sign}${Math.abs(n).toFixed(2)}`;
|
||||
}
|
||||
|
||||
function toDateParts(t) {
|
||||
const d = t ? new Date(t) : null;
|
||||
if (!d || isNaN(d.getTime())) return { date: '', time: '' };
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const mi = String(d.getMinutes()).padStart(2, '0');
|
||||
return { date: `${mm}-${dd}`, time: `${hh}:${mi}` };
|
||||
}
|
||||
|
||||
// 根据 picker value 计算时间范围(返回 start/end Date)
|
||||
function rangeFor(value) {
|
||||
const now = new Date();
|
||||
if (value === 'today') {
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
|
||||
return [start, end];
|
||||
}
|
||||
if (value === 'yesterday') {
|
||||
const y = new Date(now);
|
||||
y.setDate(now.getDate() - 1);
|
||||
const start = new Date(y.getFullYear(), y.getMonth(), y.getDate(), 0, 0, 0);
|
||||
const end = new Date(y.getFullYear(), y.getMonth(), y.getDate(), 23, 59, 59);
|
||||
return [start, end];
|
||||
}
|
||||
// month
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0);
|
||||
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
|
||||
return [start, end];
|
||||
}
|
||||
|
||||
// 加载并过滤流水
|
||||
async function load() {
|
||||
try {
|
||||
const res = await PayWalletApi.getWalletTransactionPage({ page: 1, size: 200 });
|
||||
let records = [];
|
||||
if (res && res.code === 0) {
|
||||
if (Array.isArray(res.data)) {
|
||||
records = res.data;
|
||||
} else if (res.data && Array.isArray(res.data.records)) {
|
||||
records = res.data.records;
|
||||
}
|
||||
}
|
||||
|
||||
const pickerValue = columns[0].find((c) => c.text === selectedLabel.value)?.value ?? 'today';
|
||||
const [start, end] = rangeFor(pickerValue);
|
||||
|
||||
let filtered = [];
|
||||
if (records && records.length) {
|
||||
filtered = records.filter((r) => {
|
||||
const t = r.createTime ?? r.createdAt ?? r.time ?? r.create_date ?? r.date;
|
||||
if (!t) return false;
|
||||
const d = new Date(t);
|
||||
if (isNaN(d.getTime())) return false;
|
||||
return d >= start && d <= end;
|
||||
});
|
||||
}
|
||||
|
||||
if (!filtered.length) {
|
||||
// 使用本地回退示例
|
||||
items.value = testList;
|
||||
} else {
|
||||
items.value = filtered.map((r, idx) => {
|
||||
const t = r.createTime ?? r.createdAt ?? r.time ?? r.create_date ?? r.date;
|
||||
const parts = toDateParts(t);
|
||||
return {
|
||||
id: r.id ?? `r-${idx}`,
|
||||
date: parts.date,
|
||||
time: parts.time,
|
||||
desc: r.remark ?? r.note ?? r.title ?? r.typeName ?? r.description ?? '',
|
||||
amount: Number(r.amount ?? r.price ?? r.income ?? r.value ?? r.money ?? 0),
|
||||
statusText: r.statusText ?? r.stateText ?? r.payStatus ?? '',
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 回退示例
|
||||
items.value = testList;
|
||||
}
|
||||
|
||||
// 计算汇总
|
||||
totalAmount.value = items.value.reduce((s, it) => s + (Number(it.amount) || 0), 0);
|
||||
|
||||
// 汇总说明(取前两条简短描述拼接)
|
||||
summaryNote.value = items.value.slice(0, 2).map((i) => i.desc).join(' ');
|
||||
}
|
||||
|
||||
function onConfirm(e) {
|
||||
const v = e && e.value && e.value[0];
|
||||
if (v) {
|
||||
selectedLabel.value = v.text || String(v);
|
||||
}
|
||||
showPicker.value = false;
|
||||
load();
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
load();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-wrap {
|
||||
padding: 12px;
|
||||
background: transparent;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.top-row {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.period-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.period-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
.summary-card {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.summary-amount {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #e74c3c;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.summary-sub {
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
.summary-note {
|
||||
color: #bbb;
|
||||
font-size: 13px;
|
||||
}
|
||||
.list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.txn-item {
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.txn-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.txn-sub {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
.txn-amount {
|
||||
font-weight: 700;
|
||||
color: #e74c3c;
|
||||
text-align: right;
|
||||
}
|
||||
.txn-amount.positive {
|
||||
color: #e74c3c;
|
||||
}
|
||||
.txn-status {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
.no-more {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
74
pages/user/contact/index.vue
Normal file
74
pages/user/contact/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<s-layout title="紧急联系人设置" class="contact-page">
|
||||
<view class="list-wrap">
|
||||
<view class="list-item">
|
||||
<text class="item-label">紧急联系人姓名</text>
|
||||
<!-- <text class="item-value">紧急联系人姓名</text> -->
|
||||
<view style="width:280rpx;">
|
||||
<up-input border="0" inputAlign="right" v-model="displayName" placeholder="紧急联系人" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list-item">
|
||||
<text class="item-label">联系电话</text>
|
||||
<view style="width:280rpx;">
|
||||
<up-input border="0" inputAlign="right" type="number" v-model="displayPhone" placeholder="联系电话" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<su-fixed bottom placeholder bg="none">
|
||||
<view class="footer-box ss-p-20">
|
||||
<up-button type="primary" block @click="onSave">保存</up-button>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo || {});
|
||||
|
||||
const displayName = computed(() => {
|
||||
return userInfo.value.emergencyContactName || userInfo.value.emergencyContact?.name || '';
|
||||
});
|
||||
|
||||
const displayPhone = computed(() => {
|
||||
return userInfo.value.emergencyContactPhone || userInfo.value.emergencyContact?.phone || '';
|
||||
});
|
||||
|
||||
async function onSave() {
|
||||
// 直接刷新本地用户信息并返回(如果需要,可在此处额外校验或请求后端)
|
||||
await sheep.$store('user').updateUserData();
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('保存成功');
|
||||
uni.navigateBack();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list-wrap {
|
||||
background: #fff;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.item-label {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.item-value {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.footer-box {
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
219
pages/user/healthCertificate/index.vue
Normal file
219
pages/user/healthCertificate/index.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<s-layout title="上传健康证" class="health-cert-page">
|
||||
<view class="page-body">
|
||||
<view class="form-row">
|
||||
<text class="label">编号:</text>
|
||||
<up-input v-model="healthNumber" placeholder="请输入健康证编号" clearable />
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="label">类别:</text>
|
||||
<up-input v-model="healthCategory" placeholder="请输入健康证类别" clearable />
|
||||
</view>
|
||||
|
||||
<view class="form-row date-row">
|
||||
<text class="label">有效日期:</text>
|
||||
<up-datetime-picker hasInput v-model="healthValidStart" mode="date" placeholder="年/月/日" class="date-input" />
|
||||
<text class="dash"> — </text>
|
||||
<up-datetime-picker hasInput v-model="healthValidEnd" mode="date" placeholder="年/月/日" class="date-input" />
|
||||
</view>
|
||||
|
||||
<view class="upload-section">
|
||||
<text class="upload-title">上传健康证:</text>
|
||||
<view class="upload-row">
|
||||
<view v-for="(img, idx) in healthImages" :key="idx" class="upload-item">
|
||||
<up-image :src="img" class="upload-thumb" mode="aspectFill" @click="previewImage(idx)" />
|
||||
<view class="remove-btn" @click.stop="removeHealthImage(idx)">×</view>
|
||||
</view>
|
||||
|
||||
<up-upload :max-count="1" :show-file-list="false" @change="onUploadHealth">
|
||||
<view class="upload-add" v-if="healthImages.length < 4">
|
||||
<up-icon name="plus" size="36" color="#999999" />
|
||||
</view>
|
||||
</up-upload>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="save-row">
|
||||
<up-button type="primary" block @click="onSave">保存</up-button>
|
||||
</view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeMount } from 'vue';
|
||||
import FileApi from '@/sheep/api/infra/file';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const healthNumber = ref('');
|
||||
const healthCategory = ref('');
|
||||
const healthValidStart = ref('');
|
||||
const healthValidEnd = ref('');
|
||||
const healthImages = ref([]); // 存放图片 url
|
||||
|
||||
// 页面初始化,从用户信息恢复(若已有则填充)
|
||||
async function init() {
|
||||
const user = await sheep.$store('user').getInfo();
|
||||
if (user) {
|
||||
healthNumber.value = user.healthCertNumber || user.healthCertNo || '';
|
||||
healthCategory.value = user.healthCertCategory || '';
|
||||
healthValidStart.value = user.healthCertValidStart || '';
|
||||
healthValidEnd.value = user.healthCertValidEnd || '';
|
||||
// 健康证可能只保留首张图片字符串,为兼容性处理成数组
|
||||
const img = user.healthCert || user.healthCertImage || user.healthCertUrl || '';
|
||||
if (img) {
|
||||
healthImages.value = Array.isArray(img) ? img.slice() : [img];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function previewImage(index) {
|
||||
const urls = healthImages.value.slice();
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls,
|
||||
});
|
||||
}
|
||||
|
||||
function removeHealthImage(index) {
|
||||
if (index >= 0 && index < healthImages.value.length) {
|
||||
healthImages.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function onUploadHealth(event) {
|
||||
const file = Array.isArray(event) ? event[0] : (event.detail || event);
|
||||
const url = file?.url || file?.path || file?.thumb || '';
|
||||
if (url) {
|
||||
// 限制最多 4 张
|
||||
if (healthImages.value.length < 4) {
|
||||
healthImages.value.push(url);
|
||||
}
|
||||
// 兼容:在表单保存时取首张
|
||||
}
|
||||
}
|
||||
|
||||
// 如果用户通过本地选择图片(没有使用 up-upload 的自动上传),也支持手动选择并上传
|
||||
async function chooseAndUpload() {
|
||||
const res = await uni.chooseImage({ count: 1 });
|
||||
if (!res || !res.tempFilePaths || !res.tempFilePaths.length) return;
|
||||
const tempPath = res.tempFilePaths[0];
|
||||
const { data } = await FileApi.uploadFile(tempPath);
|
||||
if (data) {
|
||||
if (healthImages.value.length < 4) {
|
||||
healthImages.value.push(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
// 简单校验:至少填写编号或上传图片
|
||||
if (!healthNumber.value && !healthImages.value.length) {
|
||||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('请填写编号或上传健康证图片');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
healthCertNumber: healthNumber.value || undefined,
|
||||
healthCertCategory: healthCategory.value || undefined,
|
||||
healthCertValidStart: healthValidStart.value || undefined,
|
||||
healthCertValidEnd: healthValidEnd.value || undefined,
|
||||
// 为兼容后端,保留首张图片字段 healthCert(若后端支持数组可改为数组)
|
||||
healthCert: healthImages.value.length ? healthImages.value[0] : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await UserApi.updateUser(payload);
|
||||
if (res && res.code === 0) {
|
||||
// 更新本地用户缓存
|
||||
await sheep.$store('user').updateUserData();
|
||||
uni.navigateBack();
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('保存健康证失败', err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.health-cert-page .page-body {
|
||||
padding: 20rpx 30rpx;
|
||||
background: transparent;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 35rpx;
|
||||
}
|
||||
.label {
|
||||
width: 140rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.date-row .date-input {
|
||||
width: 260rpx;
|
||||
}
|
||||
.dash {
|
||||
margin: 0 10rpx;
|
||||
color: #999999;
|
||||
}
|
||||
.upload-section {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
.upload-title {
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.upload-item {
|
||||
position: relative;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.upload-thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.remove-btn {
|
||||
position: absolute;
|
||||
right: 6rpx;
|
||||
top: 6rpx;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
line-height: 30rpx;
|
||||
text-align: center;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 24rpx;
|
||||
z-index: 5;
|
||||
}
|
||||
.upload-add {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px dashed #e6e6e6;
|
||||
border-radius: 8rpx;
|
||||
background: #fafafa;
|
||||
}
|
||||
.save-row {
|
||||
margin-top: 80rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,7 @@
|
||||
labelPosition="left"
|
||||
border
|
||||
class="form-box"
|
||||
label-width="235"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<view class="ss-flex ss-row-center ss-col-center ss-p-t-60 ss-p-b-0 bg-white">
|
||||
@@ -67,7 +68,7 @@
|
||||
</view>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="mobile" label="手机号" @tap="onChangeMobile">
|
||||
<uni-forms-item name="mobile" label="常用手机号" @tap="onChangeMobile">
|
||||
<uni-easyinput
|
||||
v-model="userInfo.mobile"
|
||||
placeholder="请绑定手机号"
|
||||
@@ -87,91 +88,104 @@
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- <uni-forms-item name="password" label="登录密码" @tap="onSetPassword">
|
||||
<!-- 身份证号 -->
|
||||
<uni-forms-item name="idNumber" label="身份证号">
|
||||
<uni-easyinput
|
||||
v-model="userInfo.password"
|
||||
placeholder="点击修改登录密码"
|
||||
:value="displayIdNumber"
|
||||
:inputBorder="false"
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
disabled
|
||||
placeholderStyle="color:#BBBBBB;font-size:28rpx;line-height:normal"
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 健康证 -->
|
||||
<uni-forms-item name="healthCert" label="健康证" @tap="onTapHealthCert">
|
||||
<uni-easyinput
|
||||
:value="state.model.healthCertStatusText || '未设置'"
|
||||
:inputBorder="false"
|
||||
disabled
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<su-radio
|
||||
class="ss-flex"
|
||||
v-if="userInfo.verification?.password"
|
||||
:modelValue="true"
|
||||
/>
|
||||
<button v-else class="ss-reset-button ss-flex ss-col-center ss-row-center">
|
||||
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx" />
|
||||
<button class="ss-reset-button ss-flex ss-col-center ss-row-center">
|
||||
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 紧急联系人 -->
|
||||
<uni-forms-item name="emergencyContact" label="紧急联系人" @tap="sheep.$router.go('/pages/user/contact/index')">
|
||||
<uni-easyinput
|
||||
:value="state.model.emergencyContact ? '已设置' : '未设置'"
|
||||
:inputBorder="false"
|
||||
disabled
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<button class="ss-reset-button ss-flex ss-col-center ss-row-center">
|
||||
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 所属公司 -->
|
||||
<uni-forms-item name="company" label="所属公司">
|
||||
<uni-easyinput
|
||||
:value="state.model.companyName || state.model.company || '广东省XX有限公司'"
|
||||
:inputBorder="false"
|
||||
disabled
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 所属运营站点 -->
|
||||
<uni-forms-item name="site" label="所属运营站点">
|
||||
<uni-easyinput
|
||||
:value="state.model.stationName || state.model.siteName || 'XXXX学校XX校区'"
|
||||
:inputBorder="false"
|
||||
disabled
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<!-- 联系站长 -->
|
||||
<!-- <uni-forms-item name="stationContact" label="联系站长" @tap="onTapContactStation">
|
||||
<uni-easyinput
|
||||
:value="state.model.stationManagerName || state.model.stationContactName || ''"
|
||||
:inputBorder="false"
|
||||
disabled
|
||||
:styles="{ disableColor: '#fff' }"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:clearable="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<button class="ss-reset-button ss-flex ss-col-center ss-row-center">
|
||||
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item> -->
|
||||
</view>
|
||||
|
||||
<!-- <view class="bg-white ss-m-t-14">
|
||||
<uni-list>
|
||||
<uni-list-item
|
||||
clickable
|
||||
@tap="sheep.$router.go('/pages/user/address/list')"
|
||||
title="地址管理"
|
||||
showArrow
|
||||
:border="false"
|
||||
class="list-border"
|
||||
/>
|
||||
</uni-list>
|
||||
</view> -->
|
||||
</uni-forms>
|
||||
|
||||
<!-- 当前社交平台的绑定关系,只处理 wechat 微信场景 -->
|
||||
<!-- <view v-if="sheep.$platform.name !== 'H5'">
|
||||
<view class="title-box ss-p-l-30">第三方账号绑定</view>
|
||||
<view class="account-list ss-flex ss-row-between">
|
||||
<view v-if="'WechatOfficialAccount' === sheep.$platform.name" class="ss-flex ss-col-center">
|
||||
<image
|
||||
class="list-img"
|
||||
:src="sheep.$url.static('/static/img/shop/platform/WechatOfficialAccount.png')"
|
||||
/>
|
||||
<text class="list-name">微信公众号</text>
|
||||
</view>
|
||||
<view v-if="'WechatMiniProgram' === sheep.$platform.name" class="ss-flex ss-col-center">
|
||||
<image
|
||||
class="list-img"
|
||||
:src="sheep.$url.static('/static/img/shop/platform/WechatMiniProgram.png')"
|
||||
/>
|
||||
<text class="list-name">微信小程序</text>
|
||||
</view>
|
||||
<view v-if="'App' === sheep.$platform.name" class="ss-flex ss-col-center">
|
||||
<image
|
||||
class="list-img"
|
||||
:src="sheep.$url.static('/static/img/shop/platform/wechat.png')"
|
||||
/>
|
||||
<text class="list-name">微信开放平台</text>
|
||||
</view>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<view class="info ss-flex ss-col-center" v-if="state.thirdInfo">
|
||||
<image class="avatar ss-m-r-20" :src="sheep.$url.cdn(state.thirdInfo.avatar)" />
|
||||
<text class="name">{{ state.thirdInfo.nickname }}</text>
|
||||
</view>
|
||||
<view class="bind-box ss-m-l-20">
|
||||
<button
|
||||
v-if="state.thirdInfo.openid"
|
||||
class="ss-reset-button relieve-btn"
|
||||
@tap="unBindThirdOauth"
|
||||
>
|
||||
解绑
|
||||
</button>
|
||||
<button v-else class="ss-reset-button bind-btn" @tap="bindThirdOauth">绑定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<su-fixed bottom placeholder bg="none">
|
||||
<view class="footer-box ss-p-20">
|
||||
<button class="ss-rest-button logout-btn ui-Shadow-Main" @tap="onSubmit">保存</button>
|
||||
@@ -209,6 +223,20 @@
|
||||
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
// 身份证显示(对中间数字做掩码显示)
|
||||
const displayIdNumber = computed(() => {
|
||||
const id =
|
||||
state.model?.idNumber ||
|
||||
state.model?.id_card ||
|
||||
state.model?.idNo ||
|
||||
state.model?.id;
|
||||
if (!id) return '';
|
||||
if (typeof id === 'string' && id.length >= 8) {
|
||||
return id.replace(/(\d{3})\d+(\d{4})/, '$1********$2');
|
||||
}
|
||||
return id;
|
||||
});
|
||||
|
||||
// 选择性别
|
||||
function onChangeGender(e) {
|
||||
state.model.sex = e.detail.value;
|
||||
@@ -219,6 +247,16 @@
|
||||
showAuthModal('changeMobile');
|
||||
};
|
||||
|
||||
// 点击健康证(跳转到健康证或证件页)
|
||||
function onTapHealthCert() {
|
||||
uni.navigateTo({ url: '/pages/registered/accountInfo' });
|
||||
}
|
||||
|
||||
// 联系站长(跳转到我的账户或站点详情页)
|
||||
function onTapContactStation() {
|
||||
uni.navigateTo({ url: '/pages/user/account/index' });
|
||||
}
|
||||
|
||||
// 选择微信的头像,进行上传
|
||||
function onChooseAvatar(e) {
|
||||
const tempUrl = e.detail.avatarUrl || '';
|
||||
|
||||
@@ -94,7 +94,7 @@ function onConfirm(e) {
|
||||
|
||||
function toList() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/recordList'
|
||||
url: '/pages/user/order/recordList'
|
||||
})
|
||||
}
|
||||
|
||||
70
pages/user/privacyManage/index.vue
Normal file
70
pages/user/privacyManage/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<s-layout class="set-wrap" title="隐私管理" :bgStyle="{ color: '#fff' }">
|
||||
<view class="container-list">
|
||||
<uni-list :border="false">
|
||||
<uni-list-item
|
||||
title="授权管理"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/user/AuthorizaManage/index')
|
||||
"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="用户协议"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/public/richtext', {
|
||||
title: '用户协议'
|
||||
})
|
||||
"
|
||||
/>
|
||||
<uni-list-item
|
||||
title="隐私管理"
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@tap="
|
||||
sheep.$router.go('/pages/public/richtext', {
|
||||
title: '隐私协议'
|
||||
})
|
||||
"
|
||||
/>
|
||||
<!-- 为了过审 只有 iOS-App 有注销账号功能 && sheep.$platform.os === 'ios' && sheep.$platform.name === 'App' -->
|
||||
<uni-list-item
|
||||
v-if="isLogin"
|
||||
title="注销账号"
|
||||
rightText=""
|
||||
showArrow
|
||||
clickable
|
||||
:border="false"
|
||||
class="list-border"
|
||||
@click="onLogoff"
|
||||
/>
|
||||
</uni-list>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container-list {
|
||||
padding-top: 15rpx;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
184
pages/user/salary/salaryList.vue
Normal file
184
pages/user/salary/salaryList.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<s-layout :title="titleText" class="salary-list-page">
|
||||
<view class="page-wrap">
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view class="salary-item" v-for="item in items" :key="item.id">
|
||||
<view class="left">
|
||||
<text class="date">{{ item.date }}</text>
|
||||
<text class="desc" :class="{ 'muted': !item.desc }">{{ item.desc || '' }}</text>
|
||||
</view>
|
||||
<text
|
||||
class="amount"
|
||||
:class="{ positive: item.amount >= 0, negative: item.amount < 0 }"
|
||||
>
|
||||
{{ formatSigned(item.amount) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-if="!items.length" class="empty">没有更多了~</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import PayWalletApi from '@/sheep/api/pay/wallet';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const items = ref([]);
|
||||
const year = ref('');
|
||||
const month = ref('');
|
||||
const titleText = ref('薪资明细');
|
||||
|
||||
function pad(n) {
|
||||
if (!n && n !== 0) return n;
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
function formatSigned(val) {
|
||||
const n = Number(val) || 0;
|
||||
const sign = n > 0 ? '+' : n < 0 ? '' : '+';
|
||||
// 保留两位小数
|
||||
return `${sign}${Math.abs(n).toFixed(2)}`;
|
||||
}
|
||||
|
||||
function toDateString(t) {
|
||||
const d = t ? new Date(t) : null;
|
||||
if (!d || isNaN(d.getTime())) return '';
|
||||
const y = d.getFullYear();
|
||||
const m = pad(d.getMonth() + 1);
|
||||
const day = pad(d.getDate());
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
// 本地回退示例明细(供开发查看)
|
||||
const testList = [
|
||||
{ id: 't1', date: '2025-10-31', desc: '配送订单佣金收入23.9 时段考勤收入23.21', amount: 71.89 },
|
||||
{ id: 't2', date: '2025-10-30', desc: '配送订单佣金收入23.9 时段考勤收入23.21', amount: 71.89 },
|
||||
{ id: 't3', date: '2025-10-31', desc: '配送订单佣金收入23.9 时段考勤收入23.21', amount: 71.89 },
|
||||
{ id: 't4', date: '2025-10-30', desc: '配送订单佣金收入23.9 时段考勤收入23.21', amount: 71.89 },
|
||||
];
|
||||
|
||||
// 加载并过滤流水(按 year/month)
|
||||
async function loadList() {
|
||||
try {
|
||||
const res = await PayWalletApi.getWalletTransactionPage({ page: 1, size: 200 });
|
||||
let records = [];
|
||||
if (res && res.code === 0) {
|
||||
if (Array.isArray(res.data)) {
|
||||
records = res.data;
|
||||
} else if (res.data && Array.isArray(res.data.records)) {
|
||||
records = res.data.records;
|
||||
}
|
||||
}
|
||||
|
||||
// 若有返回,则客户端过滤指定年月
|
||||
if (records && records.length) {
|
||||
const filtered = records.filter((r) => {
|
||||
const t = r.createTime ?? r.createdAt ?? r.time ?? r.create_date ?? r.date;
|
||||
if (!t) return false;
|
||||
const d = new Date(t);
|
||||
if (isNaN(d.getTime())) return false;
|
||||
const y = d.getFullYear();
|
||||
const m = d.getMonth() + 1;
|
||||
return Number(y) === Number(year.value) && Number(m) === Number(month.value);
|
||||
});
|
||||
|
||||
items.value = filtered.map((r, idx) => {
|
||||
const t = r.createTime ?? r.createdAt ?? r.time ?? r.create_date ?? r.date;
|
||||
return {
|
||||
id: r.id ?? `r-${idx}`,
|
||||
date: toDateString(t),
|
||||
desc: r.remark ?? r.note ?? r.title ?? r.typeName ?? '',
|
||||
amount: Number(r.amount ?? r.price ?? r.income ?? r.value ?? r.money ?? 0),
|
||||
};
|
||||
});
|
||||
// 如果过滤后为空则回退到示例数据,便于开发查看
|
||||
if (!items.value.length) {
|
||||
items.value = testList;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// 无接口返回,使用回退示例
|
||||
items.value = testList;
|
||||
}
|
||||
|
||||
// 获取路由参数(uni-app 页面可通过 getCurrentPages 读取 options)
|
||||
function initParams() {
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
const cur = pages[pages.length - 1] || {};
|
||||
const opts = cur.options || {};
|
||||
year.value = opts.year || opts.y || String(new Date().getFullYear());
|
||||
month.value = opts.month || opts.m || String(new Date().getMonth() + 1);
|
||||
titleText.value = `${String(year.value).slice(-2)}年${pad(month.value)}月薪资`;
|
||||
} catch (e) {
|
||||
year.value = String(new Date().getFullYear());
|
||||
month.value = String(new Date().getMonth() + 1);
|
||||
titleText.value = `${String(year.value).slice(-2)}年${pad(month.value)}月薪资`;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面显示时初始化并加载
|
||||
onShow(() => {
|
||||
initParams();
|
||||
loadList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-wrap {
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.list {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.salary-item {
|
||||
background: #fff;
|
||||
padding: 14px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.salary-item .left {
|
||||
flex: 1;
|
||||
}
|
||||
.date {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.desc {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
}
|
||||
.desc.muted {
|
||||
opacity: 0.8;
|
||||
}
|
||||
.amount {
|
||||
width: 160rpx;
|
||||
text-align: right;
|
||||
font-weight: 700;
|
||||
}
|
||||
.amount.positive {
|
||||
color: #e74c3c;
|
||||
}
|
||||
.amount.negative {
|
||||
color: #2db7a3;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
252
pages/user/salary/salaryManage.vue
Normal file
252
pages/user/salary/salaryManage.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<s-layout title="账户余额" class="record-list-page">
|
||||
<view class="page-wrap">
|
||||
<!-- 余额卡片 -->
|
||||
<view class="balance-card">
|
||||
<view class="balance-top">
|
||||
<text class="symbol">¥</text>
|
||||
<text class="balance">{{ formatBalance(balance) }}</text>
|
||||
</view>
|
||||
<view class="balance-sub" @tap="openDetail()">
|
||||
本月已入账 <up-icon name="arrow-right" color="#949494" size="15"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表:按年分组的薪资账单 -->
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view v-for="section in sections" :key="section.year" class="year-section">
|
||||
<view class="year-header">{{ section.year }}年已出账薪资账单</view>
|
||||
<view
|
||||
class="month-row"
|
||||
v-for="item in section.items"
|
||||
:key="section.year + '-' + item.month"
|
||||
@tap="openDetail(section.year, item.month)"
|
||||
>
|
||||
<text class="month-label">{{ item.month }}月份薪资</text>
|
||||
<view class="month-right">
|
||||
<text class="month-amount">¥ {{ formatAmount(item.amount) }}</text>
|
||||
<up-icon name="arrow-right" color="#c7c7c7" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="no-more">没有更多了~</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</s-layout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import PayWalletApi from '@/sheep/api/pay/wallet';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const balance = ref(0);
|
||||
const sections = ref([]);
|
||||
|
||||
// 格式化显示余额(保留两位小数)
|
||||
function formatBalance(val) {
|
||||
if (val == null) return '0.00';
|
||||
return (Number(val) || 0).toFixed(2);
|
||||
}
|
||||
|
||||
// 列表金额格式(示例中是带一位小数,这里为兼容保留一位或两位)
|
||||
function formatAmount(val) {
|
||||
if (val == null) return '0.0';
|
||||
// 如果本身有小数位则保留一位,否则保留一位
|
||||
return (Number(val) || 0).toFixed(1);
|
||||
}
|
||||
|
||||
// 将流水按 年 -> 月 聚合(支持后端 records 数组,record.createTime 字段)
|
||||
function groupTransactions(records = []) {
|
||||
const map = {}; // { year: { month: amount } }
|
||||
records.forEach((r) => {
|
||||
// 兼容不同字段名,尝试 createTime 或 createdAt 或 time
|
||||
const t = r.createTime ?? r.createdAt ?? r.time ?? r.create_date ?? r.date;
|
||||
let d = t ? new Date(t) : null;
|
||||
if (!d || isNaN(d.getTime())) {
|
||||
// 如果没有时间则跳过
|
||||
return;
|
||||
}
|
||||
const year = d.getFullYear();
|
||||
const month = d.getMonth() + 1;
|
||||
// 取金额字段:amount / price / income / value / money
|
||||
const amt = Number(r.amount ?? r.price ?? r.income ?? r.value ?? r.money ?? 0) || 0;
|
||||
map[year] = map[year] || {};
|
||||
map[year][month] = (map[year][month] || 0) + amt;
|
||||
});
|
||||
|
||||
// 转换为数组并排序(年降序,月降序)
|
||||
const result = Object.keys(map)
|
||||
.map((y) => {
|
||||
const monthsObj = map[y];
|
||||
const items = Object.keys(monthsObj)
|
||||
.map((m) => ({
|
||||
month: Number(m),
|
||||
amount: monthsObj[m],
|
||||
}))
|
||||
.sort((a, b) => b.month - a.month);
|
||||
return {
|
||||
year: Number(y),
|
||||
items,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.year - a.year);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadWallet() {
|
||||
try {
|
||||
const res = await PayWalletApi.getPayWallet();
|
||||
if (res && res.code === 0 && res.data) {
|
||||
balance.value = res.data.balance ?? res.data?.wallet?.balance ?? 0;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// 回退占位数据
|
||||
balance.value = 1999.91;
|
||||
}
|
||||
|
||||
async function loadTransactions() {
|
||||
try {
|
||||
// 请求分页(取足够多条用于按月聚合)
|
||||
const res = await PayWalletApi.getWalletTransactionPage({ page: 1, size: 200 });
|
||||
// 兼容常见返回结构:{ code:0, data: { records: [] } } 或 { code:0, data: [] }
|
||||
let records = [];
|
||||
if (res && res.code === 0) {
|
||||
if (Array.isArray(res.data)) {
|
||||
records = res.data;
|
||||
} else if (res.data && Array.isArray(res.data.records)) {
|
||||
records = res.data.records;
|
||||
}
|
||||
}
|
||||
if (records.length) {
|
||||
sections.value = groupTransactions(records);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// 回退示例数据(与原型一致)
|
||||
sections.value = [
|
||||
{
|
||||
year: 2025,
|
||||
items: [
|
||||
{ month: 5, amount: 4563.5 },
|
||||
{ month: 4, amount: 4563.5 },
|
||||
{ month: 3, amount: 4563.5 },
|
||||
{ month: 2, amount: 4563.5 },
|
||||
{ month: 1, amount: 4563.5 },
|
||||
],
|
||||
},
|
||||
{
|
||||
year: 2024,
|
||||
items: [{ month: 12, amount: 4563.5 }],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function openDetail(year, month) {
|
||||
// 占位跳转:可以替换为真实的工资明细页
|
||||
uni.navigateTo({
|
||||
url: `/pages/user/salary/salaryList?year=${year}&month=${month}`,
|
||||
});
|
||||
}
|
||||
|
||||
function openMonthSummary() {
|
||||
// 占位跳转到本月流水汇总
|
||||
uni.navigateTo({
|
||||
url: '/pages/user/walletSummary',
|
||||
});
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
loadWallet();
|
||||
loadTransactions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page-wrap {
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.balance-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 30px 16px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||||
}
|
||||
.balance-top {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
.symbol {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.balance {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
.balance-sub {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.year-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.year-header {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
padding: 10px 12px;
|
||||
font-weight: 600;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.month-row {
|
||||
background: #fff;
|
||||
padding: 14px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.month-label {
|
||||
color: #333;
|
||||
font-size: 15px;
|
||||
}
|
||||
.month-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #999;
|
||||
}
|
||||
.month-amount {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.no-more {
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="head-box ss-m-b-60 text-center">
|
||||
<view class="head-title ss-m-b-20">
|
||||
{{ userInfo.mobile ? '更换手机号' : '绑定手机号' }}
|
||||
</view>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<reset-password v-if="authType === 'resetPassword'" />
|
||||
|
||||
<!-- 4. 绑定手机号 changeMobile -->
|
||||
<!-- <change-mobile v-if="authType === 'changeMobile'" /> -->
|
||||
<change-mobile v-if="authType === 'changeMobile'" />
|
||||
|
||||
<!-- 5. 修改密码 changePassword-->
|
||||
<changePassword v-if="authType === 'changePassword'" />
|
||||
|
||||
Reference in New Issue
Block a user