feat:退款,支付返回

This commit is contained in:
dzc committed 2026-07-11 17:29:55 +08:00
1 parent 41d3b01c24
commit 68349eb8c0
17 files changed
+1092 -491

No files matched your search

+4 -4
View File
@@ -25,17 +25,17 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
# VITE_ZZD_CORP_ID=
# --------------------------- 开发环境 api地址 ----------------------------------
VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
# VITE_APP_DEV_SERVICE = https://stgl.pj.gov.cn/api # 域名线上
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
# VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
# VITE_IMAGE_DEV_PATH = https://stgl.pj.gov.cn/api # 域名线上
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
#VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
# VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
# --------------------------- 生产环境 api地址 ----------------------------------
@@ -0,0 +1,79 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { individualOrderApi } from "@@/api/index"
const emit = defineEmits(["confirm"])
const rejectFormRef = ref<any>()
const state = reactive<any>({
show: false,
apptList: [],
refundDetail: [],
})
const rejectForm = reactive<any>({
id: '',
auditReason: "",
})
const rejectFormRules = reactive<any>({
auditReason: [{ required: true, message: "请输入拒绝原因" }],
})
const open = (refundDetail: any) => {
state.show = true
state.refundDetail = refundDetail
}
// 同意退款
const doAgreeRefund = async () => {
if (!state.refundDetail.id) {
return uni.showToast({ title: "订单不存在", icon: "none" })
}
rejectFormRef.value.submit()
const { isPass } = rejectFormRef.value.validate()
if (!isPass) return
state.show = false
uni.showLoading({ title: "操作中...", mask: true })
try {
const res: any = await individualOrderApi.refuseRefund({ id: state.refundDetail.id, auditReason: rejectForm.auditReason })
if (res.code == 0) {
uni.showToast({ title: "操作成功", icon: "success" })
state.show = false
emit("confirm")
} else {
uni.showToast({ title: res.msg || "退款失败", icon: "none" })
}
} catch (error) {
uni.showToast({ title: "错误", icon: "none" })
} finally {
uni.hideLoading()
}
}
defineExpose({
open
})
</script>
<template>
<tm-drawer v-model:show="state.show" showClose :showCancel="false" showFooter bgColor="#EAF2FE" title="确认预约"
size="50%" position="bottom" confirmText="拒绝退款">
<view class="h-20rpx" />
<tm-form ref="rejectFormRef" v-model="rejectForm" :rules="rejectFormRules" direction="vertical">
<tm-form-item name="auditReason" label="拒绝原因" :showBottom="false">
<tm-input v-model="rejectForm.auditReason" type="textarea" :showClear="false" placeholder="请输入拒绝原因.." />
</tm-form-item>
</tm-form>
<template #footer>
<view class="bg-red p-y-20rpx text-center rounded-20rpx mx-30rpx m-y-10rpx sub-text-30 color-white!"
@tap="doAgreeRefund">
拒绝退款
</view>
</template>
</tm-drawer>
</template>
<style lang="scss" scoped></style>
+87 -14
View File
@@ -1,18 +1,40 @@
<script setup lang="ts">
import { reactive } from "vue"
import { computed, reactive, ref } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { isEmpty } from "radash"
import { hooks, router } from "@@/index"
import { orderApi, refundApi } from "@@/api"
import { hooks, router, constant } from "@@/index"
import { orderApi, refundApi, individualOrderApi } from "@@/api"
import { filePath } from "@@/utils/file"
import { hasRoleCode, MCH_USER } from "@@/utils/user-role"
import RejectChechk from "./components/reject-chechk.vue"
const rejectChechkRef = ref<any>()
const state = reactive<any>({
detailId: null,
detail: {},
refundDetail: {},
shopCarList: [],
currentUser: {}
})
const applyPicUrlList = computed(() => {
return state.refundDetail?.applyPicUrls?.split(',') ?? []
})
const isCheckRefund = computed(() => {
const orderUserId = state.detail?.userId
const currentUserId = state.currentUser?.sysUser?.userId
return hasRoleCode(MCH_USER) && orderUserId != currentUserId && state.refundDetail.status == '10'
})
const isUserOperation = computed(() => {
const orderUserId = state.detail?.userId
const currentUserId = state.currentUser?.sysUser?.userId
return orderUserId == currentUserId
})
onLoad((options: any) => {
@@ -21,9 +43,14 @@ onLoad((options: any) => {
}
})
onShow(() => {
refresh()
})
const refresh = () => {
getCurrentUserDetail()
getDetail()
getShopCarList()
})
}
const getDetail = async () => {
if (isEmpty(state.detailId)) return
@@ -35,6 +62,13 @@ const getDetail = async () => {
}
}
const getCurrentUserDetail = () => {
const userInfo: any = uni.getStorageSync(constant.storageConstant.USERINFO) || {}
if (userInfo) {
state.currentUser = userInfo
}
}
const getShopCarList = async () => {
if (isEmpty(state.detailId)) return
const _pa = { orderId: state.detailId }
@@ -65,6 +99,23 @@ const doCancelRefund = async () => {
},
})
}
const doAgreeRefund = async () => {
uni.showModal({
title: "提示",
content: "是否确定同意退款",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return
uni.showLoading({ title: "操作中...", mask: false })
const _pa = { id: state.refundDetail.id }
await individualOrderApi.agreeRefund(_pa)
refresh()
uni.hideLoading()
uni.showToast({ title: "同意成功", icon: "none" })
},
})
}
</script>
<template>
@@ -170,6 +221,13 @@ const doCancelRefund = async () => {
<view>售后类型:</view>
<view>{{ state.refundDetail.applyReason }}</view>
</view>
<view v-if="applyPicUrlList && applyPicUrlList.length" class="flex-row items-center">
<view>补充图片:</view>
<view class="flex items-center gap-10rpx">
<image v-for="(v, i) in applyPicUrlList" :key="i" class="size-100rpx rounded-8rpx"
:src="filePath(v)" mode="scaleToFill" @tap="hooks.usePreviewImage(filePath(v))" />
</view>
</view>
<view class="flex-row items-center">
<view>相关描述:</view>
<view>{{ state.refundDetail.applyDescription }}</view>
@@ -194,18 +252,33 @@ const doCancelRefund = async () => {
<!-- end 退款相关 end -->
<view class="h-30rpx" />
<template #main-bottom>
<view v-if="state.detail.orderStatus == 10 && state.refundDetail.status == 10"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="doCancelRefund">
取消申请
</view>
<view v-else-if="state.refundDetail.status == 62 || state.refundDetail.status == 61"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="router.jump('/pages/refund/apply', { id: state.detailId }, 2)">
重新申请
</view>
<slot v-if="isCheckRefund">
<view class="flex items-center mx-30rpx gap-30rpx">
<view class="w-40% bg-red p-y-20rpx text-center rounded-40rpx m-y-30rpx sub-text-30 color-white!"
@tap="rejectChechkRef.open(state.refundDetail)">
拒绝退款
</view>
<view class="flex-1 bg-blue p-y-20rpx text-center rounded-40rpx m-y-30rpx sub-text-30 color-white!"
@tap="doAgreeRefund">
同意退款
</view>
</view>
</slot>
<slot v-else-if="isUserOperation">
<view v-if="state.detail.orderStatus == 10 && state.refundDetail.status == 10"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="doCancelRefund">
取消申请
</view>
<view v-else-if="state.refundDetail.status == 62 || state.refundDetail.status == 61"
class="bg-#999 p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="router.jump('/pages/refund/apply', { id: state.detailId }, 2)">
重新申请
</view>
</slot>
</template>
</crud-layout>
<reject-chechk ref="rejectChechkRef" @confirm="refresh" />
</template>
<style lang="scss" scoped>
+44 -20
View File
@@ -2,23 +2,12 @@
<view class="w-full h-full">
<view class="flex-col-center p-30rpx">
<view class="flex-col-center m-y-50rpx">
<svg
t="1742978804614"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1708"
width="64"
height="64"
>
<svg t="1742978804614" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1708" width="64" height="64">
<path
d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0z m288 371.2L460.8 710.4c-6.4 6.4-19.2 12.8-25.6 12.8-12.8 0-19.2-6.4-25.6-12.8L224 531.2c-12.8-12.8-12.8-38.4 0-57.6 12.8-12.8 38.4-12.8 57.6 0l147.2 147.2L736 313.6c12.8-12.8 38.4-12.8 57.6 0 19.2 19.2 19.2 44.8 6.4 57.6z"
fill="#2BCDA3"
p-id="1709"
data-spm-anchor-id="a313x.search_index.0.i2.beb13a81SVstw0"
class="selected"
></path>
fill="#2BCDA3" p-id="1709" data-spm-anchor-id="a313x.search_index.0.i2.beb13a81SVstw0"
class="selected"></path>
</svg>
<text class="text-36 mt-3 font-bold c-#333333!">支付成功</text>
</view>
@@ -46,19 +35,54 @@
</template>
<script setup lang="ts">
import { ref } from "vue"
import { reactive, ref } from "vue"
import { onShow } from "@dcloudio/uni-app"
import { usePayStore } from "@/sheep/stores/pay"
import { merchantApi } from "@/sheep/api"
const scanPayData = ref<any>({})
onShow(() => {
if (!usePayStore().scanPayData.mchId) return uni.showToast({ title: "支付信息不存在", icon: "none" })
scanPayData.value = usePayStore().scanPayData
const state = reactive<any>({
mchDetail: {}
})
onShow(async () => {
if (!usePayStore().scanPayData.mchId) return uni.showToast({ title: "支付信息不存在", icon: "none" })
scanPayData.value = usePayStore().scanPayData
await getMchDetail(usePayStore().scanPayData.mchId)
})
// 获取商户详情
const getMchDetail = async (mchId: string) => {
try {
uni.showLoading({ title: "加载中" })
const { data } = await merchantApi.detail({ id: mchId })
console.log("获取商户详情成功:", data)
state.mchDetail = data ?? {}
} catch (error) {
console.log("获取商户详情意外的错误error:", error);
} finally {
uni.hideLoading()
}
}
const goBack = () => {
const url: string = window.location.href.split("h5")[0] + "h5/?launchParam=coopMerchant#/pages/tabbar/order"
let launchParam = "coopMerchant"
if (state.mchDetail?.types) {
// 自营食堂
if (state.mchDetail.types == "1") {
launchParam = "canteen"
}
// 合作商家
else if (state.mchDetail.types == "2") {
launchParam = "coopMerchant"
}
// 自营商家
else if (state.mchDetail.types == "2") {
launchParam = "selfMerchant"
}
}
const url: string = window.location.href.split("h5")[0] + `h5/?launchParam=${launchParam}#/pages/tabbar/order`
window.location.replace(url)
}
</script>
+4 -6
View File
@@ -33,7 +33,8 @@
</view>
<!-- 金额限制提示 -->
<view class="h-30rpx">
<view v-if="storePrice(amount) > payStore.sourceWalletData?.balance" class="text-xs c-red px-4">注:转账金额不能超过钱包余额</view>
<view v-if="storePrice(amount) > payStore.sourceWalletData?.balance" class="text-xs c-red px-4">
注:转账金额不能超过钱包余额</view>
</view>
<view class="font-bold mt-6">
@@ -45,13 +46,10 @@
<!-- 选择转入方式 -->
<tm-drawer v-model:show="show" title="选择钱包" size="auto" bg-color="#efefef" :show-footer="false" :showClose="true">
<view
v-for="(v, index) in wallet.walletList"
:key="index"
<view v-for="(v, index) in wallet.walletList" :key="index"
class="card m-2 overflow-hidden relative border-4 border-solid border-white duration-200 flex justify-between items-center p-4"
:class="{ 'border-4 border-solid border-blue!': targetWalletType.id == v.walletTypeId }"
@click="selectTargetWalletType(v)"
>
@click="selectTargetWalletType(v)">
<view class="flex items-center">
<image class="w-70 h-70 mr-2" :src="filePath(v.walletTypeIcon)" />
<view class="flex flex-col">
+14 -26
View File
@@ -54,6 +54,8 @@ const getSetting = async () => {
title: v,
}
})
applyForm.reason = state.selectList[0].id
applyForm.reasonStr = state.selectList[0].title
}
const getDetail = async () => {
if (isEmpty(state.detailId)) return
@@ -120,12 +122,8 @@ const doSubmit = async () => {
<view class="h-30rpx" />
<view class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-20rpx">
<!-- <view class="m-t-15rpx"> -->
<view
class="flex-row items-start justify-between"
v-for="(v, i) in state.shopCarList"
:key="i"
@tap.stop="router.jump('/pages/canteen/product-detail', { id: v.productId })"
>
<view class="flex-row items-start justify-between" v-for="(v, i) in state.shopCarList" :key="i"
@tap.stop="router.jump('/pages/canteen/product-detail', { id: v.productId })">
<view class="flex-row items-center">
<image class="w-160rpx h-160rpx rounded-8rpx" :src="filePath(v.productPic)" mode="aspectFill" />
<view class="flex-col m-l-15rpx second-text-26">
@@ -152,7 +150,8 @@ const doSubmit = async () => {
<view class="sub-text-28">售后类型</view>
<view class="m-t-20rpx">
<tm-radio-group @change="radioChange" direction="column" gap="20" v-model="applyForm.way">
<tm-radio :disabled="v.disabled" v-for="(v, i) in data.payType" :key="i" v-show="v.visible" :value="v.type" :label="v.text" />
<tm-radio :disabled="v.disabled" v-for="(v, i) in data.payType" :key="i" v-show="v.visible"
:value="v.type" :label="v.text" />
</tm-radio-group>
</view>
</view>
@@ -161,13 +160,8 @@ const doSubmit = async () => {
<view class="sub-text-28 color-red!">¥{{ hooks.useMoney(state.detail.payPrice) }}</view>
</view>
<view class="card p-x-20rpx p-y-30rpx m-b-20rpx">
<tm-picker
rang-key="title"
rang-text="title"
v-model="applyForm.reason"
v-model:model-str="applyForm.reasonStr"
:list="state.selectList"
>
<tm-picker rang-key="title" rang-text="title" v-model="applyForm.reason"
v-model:model-str="applyForm.reasonStr" :list="state.selectList">
<view class="flex-row items-center justify-between">
<view class="sub-text-28">申请原因</view>
<view class="flex-row items-center second-text-24">
@@ -180,29 +174,23 @@ const doSubmit = async () => {
<view class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-20rpx">
<view class="sub-text-28">补充凭证图片</view>
<view class="m-t-20rpx">
<tm-upload-photo
v-model="uploadApplyPicUrls"
:maxFileSize="1024 * 1024 * 5"
:url="'/api/admin/sys-file/upload'"
:header="state.header"
:maxCount="6"
/>
<tm-upload-photo v-model="uploadApplyPicUrls" :maxFileSize="1024 * 1024 * 5"
:url="'/api/admin/sys-file/upload'" :header="state.header" :maxCount="6" />
</view>
<view class="sub-text-20 c-red!">注:图片大小不超过5M,最多上传6张</view>
</view>
<view class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-20rpx">
<view class="sub-text-28">相关描述</view>
<view class="m-t-20rpx">
<tm-input v-model="applyForm.remark" type="textarea" :showClear="false" placeholder="客官~请描述您遇到的问题"></tm-input>
<tm-input v-model="applyForm.remark" type="textarea" :showClear="false"
placeholder="客官~请描述您遇到的问题"></tm-input>
</view>
</view>
<view class="h-30rpx" />
<template #main-bottom>
<view
v-if="state.detailType != 'view'"
<view v-if="state.detailType != 'view'"
class="bg-#0094ff p-y-20rpx text-center rounded-40rpx m-x-30rpx m-y-30rpx sub-text-30 color-white!"
@tap="doSubmit"
>
@tap="doSubmit">
提交申请
</view>
</template>
+67 -292
View File
@@ -5,233 +5,67 @@
:auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<view class="h-30rpx" />
<view class="flex justify-between items-center bg-white rounded-16rpx px-20rpx">
<z-tabs :list="tabs" @change="tabChange" />
<tm-between-time start="2020-1-1" end="2050-1-1" v-model="state.date"
v-model:model-str="state.formatVal" @confirm="datePickerConfirm" format="YYYY-MM-DD"
:quickDate="[]" drawerSize="900">
<view class="mb-6rpx flex items-center gap-1">
<image class="w-[30rpx] h-[30rpx]" src="@/static/page/filter.svg" />
<view class="text-center second-text-28 text-30 whitespace-nowrap">筛选</view>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<view class="flex justify-between items-center">
<z-tabs :list="tabs" @change="tabChange" />
<tm-between-time start="2020-1-1" end="2050-1-1" v-model="state.date"
v-model:model-str="state.formatVal" @confirm="datePickerConfirm" format="YYYY-MM-DD"
:quickDate="[]" drawerSize="900">
<view class="mb-6rpx flex items-center gap-1">
<image class="w-[30rpx] h-[30rpx]" src="@/static/page/filter.svg" />
<view class="text-center second-text-28 text-30 whitespace-nowrap">筛选</view>
</view>
</tm-between-time>
</view>
<view v-if="state.pa?.orderStatusCodes?.includes(10)"
class="flex flex-items-center justify-between py-10rpx second-text-24 font-400!">
<text>可筛选售后状态</text>
<view class="flex-row-center flex-wrap-center">
<view class="flex-row-center" v-for="(v, i) in refundStatusList" :key="v.id"
@tap.stop="toggleRefundStatus(v.id)">
<text :style="{ color: isRefundStatusActive(v.id) ? '#1677ff' : '' }">
{{ v.name }}
</text>
<text class="w-1rpx h-22rpx bg-[#999] mx-8rpx" v-if="i < refundStatusList.length - 1" />
</view>
</view>
</tm-between-time>
</view>
</view>
</template>
<view class="h-20rpx" />
<view class="flex-col justify-center card p-3 m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i">
<view class="flex-row items-center">
<image class="w-110rpx h-110rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 m-l-20rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row items-center">
<view class="main-text-28">{{ v.merchantName }}</view>
<view v-if="false"
class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" />
</view>
<tm-tag v-if="v.orderStatus == 1" color="info" size="xs">未支付</tm-tag>
<tm-tag v-if="v.orderStatus == 5" color="success" size="xs">已支付</tm-tag>
<tm-tag v-if="v.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="v.orderStatus == 20" size="xs">已核销</tm-tag>
<tm-tag v-if="v.orderStatus == 30" color="info" size="xs">已取消(智能柜)</tm-tag>
</view>
<view class="flex justify-between items-center m-t-5rpx">
<view class="second-text-22">
<text>{{ v.username }}</text>
<text class="m-l-10rpx">{{ v.userMobile }}</text>
</view>
<view class="second-text-22 h-30rpx">{{ hooks.useEnum("hardwareRefundStatusEnum",
v.refundStatus) }}
</view>
</view>
<view class="flex justify-between items-center">
<view class="second-text-22">
<text>下单时间</text>
<text class="m-l-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
}}</text>
</view>
<view class="main-text-30 color-red! min-w-140rpx text-right">¥ {{
hooks.useMoney(v.totalPrice) }}
</view>
<!-- <view class="second-text-24">共 {{ v.carList.length }} 件</view> -->
</view>
<view v-if="v.payType == '9'" class="sub-text-24">{{ v.productDesc }}</view>
</view>
</view>
<view class="flex justify-end items-center mt-1">
<view v-if="v.orderType == '1' && v.refundStatus" class="ml-1 botder_btn"
@tap.stop="refundDetail(v)">售后详情
</view>
<view v-if="v.orderStatus == '5' && v.orderType == '1' && v.payType != '9'" class="ml-1 botder_btn"
@tap.stop="doIndividualRefund(v)">
申请退款
</view>
</view>
<view class="card rounded-24rpx bg-white p-20rpx pb-0rpx mb-20rpx" v-for="(v, index) in pageHook.list"
:key="v.id">
<order-car visibleUser :visibleMch="false" :cardetail="v" :footerOperate="['refund', 'qrCodeDetail']" />
</view>
</z-paging>
<tm-drawer title="退款" :showClose="true" size="90%" bgColor="#EBF6FF" v-model:show="state.showDrawer"
zIndex="999">
<view class="bg-white rounded-16rpx px-10rpx m-b-10rpx animate-both">
<z-tabs :list="['同意退款', '拒绝退款']" bar-width="300" bar-height="0" :active-style="{
width: '300rpx',
height: '70rpx',
backgroundColor: '#0779FDdd',
color: '#000000',
textAlign: 'center',
lineHeight: '70rpx',
fontWeight: 'bold',
fontSize: '30rpx',
borderRadius: '10rpx',
marginTop: '10rpx',
}" :inactive-style="{
width: '300rpx',
height: '70rpx',
color: '#000000',
textAlign: 'center',
lineHeight: '70rpx',
fontWeight: 'bold',
fontSize: '30rpx',
borderRadius: '10rpx',
marginTop: '10rpx',
}" @change="refundTabChange" />
</view>
<view class="flex-col justify-center card p-x-15rpx p-y-25rpx m-b-10rpx">
<view class="flex-row items-center">
<image class="w-100rpx h-100rpx rounded-8rpx" :src="filePath(state.curOrder.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="flex-row items-center justify-between">
<view class="flex-row items-center">
<view class="main-text-28">{{ state.curOrder.merchantName }}</view>
<view v-if="false"
class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" />
</view>
<tm-tag v-if="state.curOrder.orderStatus == 1" color="info" size="xs">未支付</tm-tag>
<tm-tag v-if="state.curOrder.orderStatus == 5" color="success" size="xs">已支付</tm-tag>
<tm-tag v-if="state.curOrder.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="state.curOrder.orderStatus == 20" size="xs">已核销</tm-tag>
</view>
<view class="flex justify-between items-center">
<view class="flex flex-col">
<view class="second-text-20">
<text>{{ state.curOrder.username }}</text>
<text class="m-l-10rpx">{{ state.curOrder.userMobile }}</text>
</view>
<view class="second-text-20">
<text>下单时间</text>
<text class="m-l-10rpx">
{{ hooks.useTime(state.curOrder.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}
</text>
</view>
</view>
<view class="flex flex-col items-end justify-center m-t-5rpx">
<view class="second-text-22">{{ hooks.useEnum("hardwareRefundStatusEnum",
state.curOrder.refundStatus) }}</view>
<view class="flex">
<view v-if="state.curOrder.carList?.length"
class="m-t-4rpx m-r-10rpx second-text-26">
共 {{ state.curOrder.carList.length }} 件
</view>
<view class="main-text-30 color-red!">¥ {{ hooks.useMoney(state.curOrder.totalPrice)
}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-10rpx">
<view class="flex-row items-center justify-between">
<view class="sub-text-28">实付款</view>
<view class="flex-row items-end">
<view class="sub-text-28">¥</view>
<view class="m-l-5rpx sub-text-28">{{ hooks.useMoney(state.curOrder.payPrice) }}</view>
</view>
</view>
</view>
<view class="flex-row items-center justify-between card p-x-20rpx p-y-30rpx m-b-10rpx">
<view class="sub-text-28">退款金额</view>
<view class="sub-text-28 color-red!">¥{{ hooks.useMoney(state.curOrder.payPrice) }}</view>
</view>
<view class="card p-x-20rpx p-y-30rpx space-y-10rpx m-b-10rpx second-text-24">
<view class="flex-row items-center">
<view>服务单号:</view>
<view>{{ state.refundDetail.no }}</view>
</view>
<view class="flex-row items-center">
<view>申请时间:</view>
<view>{{ hooks.useTime(state.refundDetail.createTime, "") }}</view>
</view>
<view class="flex-row items-center">
<view>售后类型:</view>
<view>{{ state.refundDetail.applyReason }}</view>
</view>
<view class="flex-row items-center">
<view>相关描述:</view>
<view>{{ state.refundDetail.applyDescription }}</view>
</view>
</view>
<view v-if="state.refundDetail.status == 62"
class="card p-x-20rpx p-y-30rpx space-y-10rpx m-b-10rpx second-text-24">
<view class="flex-row items-center">
<view>审核人:</view>
<view>{{ state.refundDetail.auditUsername }}</view>
</view>
<view class="flex-row items-center">
<view>审核时间:</view>
<view>{{ hooks.useTime(state.refundDetail.auditTime, "") }}</view>
</view>
<view class="flex-row items-center">
<view>拒绝原因:</view>
<view>{{ state.refundDetail.auditReason }}</view>
</view>
</view>
<view v-if="state.refundScene === 1" class="flex-col justify-center card p-x-20rpx p-y-30rpx m-b-10rpx">
<view class="sub-text-28">拒绝原因</view>
<view class="m-t-20rpx">
<tm-input v-model="state.remark" type="textarea" :showClear="false" placeholder="请描述拒绝原因" />
</view>
</view>
<view v-else class="h-270rpx" />
<view class="flex">
<tm-modal v-if="state.refundScene === 0" style="flex: 1" height="250" title="确定同意退款吗?"
:show-footer="true" @confirm="doAgreeRefund()">
<template v-slot:trigger><tm-button block>同意退款</tm-button></template>
</tm-modal>
<tm-modal v-if="state.refundScene === 1" style="flex: 1" height="250" title="确定拒绝退款吗?"
:show-footer="true" @confirm="doRefuseRefund()">
<template v-slot:trigger><tm-button block color="danger">拒绝退款</tm-button></template>
</tm-modal>
</view>
</tm-drawer>
</crud-layout>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { hooks, router, constant } from "@@/index"
import { filePath } from "@@/utils/file"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import { individualOrderApi, refundApi } from "@@/api"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import OrderCar from "@/sheep/components/order/order-car.vue"
const pageRef = ref<any>(null)
// 选项卡
const tabs = [
{ id: null, name: "全部", disabled: false },
{ id: 5, name: "已支付", disabled: false },
{ id: 10, name: "售后", disabled: false },
{ pa: { orderStatusCodes: null }, name: "全部", disabled: false },
{ pa: { orderStatusCodes: [5, 20, 200, 210, 220] }, name: "已支付", disabled: false },
{ pa: { orderStatusCodes: [10] }, name: "售后", disabled: false },
]
const state = reactive<any>({
pa: {
orderStatus: tabs[0].id,
...tabs[0].pa,
// 售后状态筛选使用逗号拼接后的字符串,便于直接透传给分页接口。
refundStatusCodes: [],
createTimeList: [],
},
activeRefundStatusList: [],
showDrawer: false,
refundScene: 0,
curOrder: {},
@@ -240,6 +74,13 @@ const state = reactive<any>({
date: [],
formatVal: "",
})
const refundStatusList = computed(() => {
const list = Object.keys(constant.serviceEnum.hardwareRefundStatusEnum).map((k) => ({
id: k,
name: constant.serviceEnum.hardwareRefundStatusEnum[k],
})).filter((item) => item.id !== '0')
return list
})
const formatVal = computed(() => (state.date.length === 2 ? state.formatVal : ""))
const pageHook = hooks.usePage(pageRef, individualOrderApi.page, null, "data.records")
@@ -258,13 +99,17 @@ onShow(() => {
// 切换选项卡
const tabChange = (e: number) => {
state.pa.orderStatus = tabs[e].id
const target = tabs[e]
state.pa = target.pa
// 只有“售后”tab 才需要售后状态筛选,切走时清空,避免影响其它状态查询。
if (!target.pa?.orderStatusCodes?.includes(10)) {
state.activeRefundStatusList = []
state.pa.refundStatusCodes = []
}
pageRef.value.reload(true)
}
const query = (page: number, pagesize: number) => {
// const isLogin = hooks.useStaticCheckLogin()
// if (!isLogin) return
pageHook.startPage(page, pagesize, false, state.pa)
}
@@ -278,92 +123,22 @@ const datePickerConfirm = (date: any) => {
pageRef.value.reload(false)
}
// 重置状态
const resetState = () => {
state.refundScene = 0
state.curOrder = {}
state.refundDetail = {}
state.remark = ""
// 售后状态支持多选:
// - 已选中时再次点击可取消;
// - 未选中时点击加入筛选;
// - 最终同步成逗号分隔字符串给订单列表接口使用。
const toggleRefundStatus = (statusId: string) => {
const targetId = String(statusId)
if (isRefundStatusActive(targetId)) {
state.activeRefundStatusList = state.activeRefundStatusList.filter((id: string) => id !== targetId)
} else {
state.activeRefundStatusList = [...state.activeRefundStatusList, targetId]
}
state.pa.refundStatusCodes = state.activeRefundStatusList
pageRef.value?.reload(true)
}
// 点击售后详情
const refundDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
}
// 点击退款
const doIndividualRefund = async (v: any) => {
return router.jump("/pages/refund/apply", { id: v.id })
if (!v.id) return uni.showToast({ title: "订单id不存在", icon: "none" })
resetState()
state.curOrder = v
state.showDrawer = true
uni.showLoading({ title: "加载中...", mask: true })
try {
const res: any = await refundApi.get({ id: v.id })
if (res.code == 0) {
state.refundDetail = res.data
} else {
uni.showToast({ title: res.msg || "加载退款详情失败", icon: "none" })
}
} catch (error) {
uni.showToast({ title: "错误", icon: "none" })
} finally {
uni.hideLoading()
}
}
// 退款切换选项卡
const refundTabChange = (e: number) => {
state.refundScene = e
}
// 同意退款
const doAgreeRefund = async () => {
if (!state.refundDetail.id) {
return uni.showToast({ title: "订单不存在", icon: "none" })
}
uni.showLoading({ title: "操作中...", mask: true })
try {
const res: any = await individualOrderApi.agreeRefund({ id: state.refundDetail.id })
if (res.code == 0) {
uni.showToast({ title: "操作成功", icon: "success" })
state.showDrawer = false
pageRef.value.reload(true)
} else {
uni.showToast({ title: res.msg || "退款失败", icon: "none" })
}
} catch (error) {
uni.showToast({ title: "错误", icon: "none" })
} finally {
uni.hideLoading()
}
}
// 拒绝退款
const doRefuseRefund = async () => {
if (!state.refundDetail.id) {
return uni.showToast({ title: "订单不存在", icon: "none" })
}
uni.showLoading({ title: "操作中...", mask: true })
try {
const res: any = await individualOrderApi.refuseRefund({ id: state.refundDetail.id, auditReason: state.remark })
if (res.code == 0) {
uni.showToast({ title: "操作成功", icon: "success" })
state.showDrawer = false
pageRef.value.reload(true)
} else {
uni.showToast({ title: res.msg || "拒绝退款失败", icon: "none" })
}
} catch (error) {
uni.showToast({ title: "错误", icon: "none" })
} finally {
uni.hideLoading()
}
const isRefundStatusActive = (statusId: string) => {
return state.activeRefundStatusList?.includes(String(statusId))
}
</script>
+63 -96
View File
@@ -1,28 +1,41 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { ref, reactive, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { hooks, router, constant } from "@@/index"
import { orderApi } from "@@/api"
import { filePath } from "@@/utils/file"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import OrderCar from "@/sheep/components/order/order-car.vue"
import { getLaunchParam } from "@/sheep/utils/app"
const data = {
tabs: [
{ id: null, name: "全部", disabled: false },
{ id: 1, name: "未支付", disabled: false },
{ id: 5, name: "已支付", disabled: false },
{ id: 10, name: "售后", disabled: false },
{ id: 20, name: "已核销", disabled: false },
{ pa: { orderStatusCodes: null }, name: "全部", disabled: false },
{ pa: { orderStatusCodes: [200] }, name: "待发货", disabled: false },
{ pa: { orderStatusCodes: [5, 210] }, name: "待使用", disabled: false },
{ pa: { orderStatusCodes: [20, 220] }, name: "已核销", disabled: false },
{ pa: { orderStatusCodes: [10] }, name: "售后", disabled: false },
{ pa: { orderStatusCodes: [1] }, name: "待支付", disabled: false },
{ pa: { orderStatusCodes: [30] }, name: "已关闭", disabled: false },
],
}
const state = reactive<any>({
pa: {
orderStatus: data.tabs[0].id,
...data.tabs[0].pa,
// 售后状态筛选使用逗号拼接后的字符串,便于直接透传给分页接口。
refundStatusCodes: [],
},
// 维护当前多选的售后状态,用于文字高亮和切换。
activeRefundStatusList: [] as string[],
})
const refundStatusList = computed(() => {
const list = Object.keys(constant.serviceEnum.hardwareRefundStatusEnum).map((k) => ({
id: k,
name: constant.serviceEnum.hardwareRefundStatusEnum[k],
})).filter((item) => item.id !== '0')
return list
})
onLoad(() => {
@@ -36,9 +49,15 @@ onShow(async () => {
pageRef.value.reload(false)
}
})
const doTab = (e: any) => {
const target = data.tabs[e]
state.pa.orderStatus = target.id
state.pa = target.pa
// 只有“售后”tab 才需要售后状态筛选,切走时清空,避免影响其它状态查询。
if (!target.pa.orderStatusCodes?.includes(10)) {
state.activeRefundStatusList = []
state.pa.refundStatusCodes = []
}
pageRef.value.reload(true)
}
@@ -47,9 +66,9 @@ const pageHook = hooks.usePage(
pageRef,
getLaunchParam() == "canteen" // 食堂
? orderApi.caneenPage
: getLaunchParam() == "selfMerchant"
: getLaunchParam() == "selfMerchant" // 自营商户
? orderApi.supPage
: getLaunchParam() == "coopMerchant"
: getLaunchParam() == "coopMerchant" // 合作商户
? orderApi.supPage
: getLaunchParam() == "onlineShop"
? orderApi.supPage
@@ -61,20 +80,27 @@ const pageHook = hooks.usePage(
)
const query = (page: number, pagesize: number) => {
const isLogin = hooks.useStaticCheckLogin()
if (!isLogin) return
pageHook.startPage(page, pagesize, false, state.pa)
}
// 查看订单和核销码
const doToDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
// 售后状态支持多选:
// - 已选中时再次点击可取消;
// - 未选中时点击加入筛选;
// - 最终同步成逗号分隔字符串给订单列表接口使用。
const toggleRefundStatus = (statusId: string) => {
const targetId = String(statusId)
if (isRefundStatusActive(targetId)) {
state.activeRefundStatusList = state.activeRefundStatusList.filter((id: string) => id !== targetId)
} else {
state.activeRefundStatusList = [...state.activeRefundStatusList, targetId]
}
state.pa.refundStatusCodes = state.activeRefundStatusList
pageRef.value?.reload(true)
}
const doAppraise = () => { }
const doRefund = (v: any) => {
router.jump("/pages/refund/apply", { id: v.id })
const isRefundStatusActive = (statusId: string) => {
return state.activeRefundStatusList.includes(String(statusId))
}
</script>
@@ -86,83 +112,24 @@ const doRefund = (v: any) => {
<template #top>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
<view v-if="state.pa.orderStatusCodes?.includes(10)"
class="flex flex-items-center justify-between py-10rpx second-text-24 font-400!">
<text>可筛选售后状态</text>
<view class="flex-row-center flex-wrap-center">
<view class="flex-row-center" v-for="(v, i) in refundStatusList" :key="v.id"
@tap.stop="toggleRefundStatus(v.id)">
<text :style="{ color: isRefundStatusActive(v.id) ? '#1677ff' : '' }">
{{ v.name }}
</text>
<text class="w-1rpx h-22rpx bg-[#999] mx-8rpx" v-if="i < refundStatusList.length - 1" />
</view>
</view>
</view>
</view>
</template>
<view class="flex-col justify-center card p-3 m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i">
<view class="flex-row">
<image class="size-130rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 ml-20rpx">
<view class="flex-row items-center justify-between">
<view class="sub-text-30">{{ v.merchantName }}</view>
<!-- <view class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" /> -->
<tm-tag v-if="v.orderStatus == 1" color="info" size="s">未支付</tm-tag>
<tm-tag v-if="v.orderStatus == 5" color="success" size="s">已支付</tm-tag>
<tm-tag v-if="v.orderStatus == 10" color="error" size="s">售后</tm-tag>
<tm-tag v-if="v.orderStatus == 20" size="s">已核销</tm-tag>
<tm-tag v-if="v.orderStatus == 30" color="info" size="s">已取消(智能柜)</tm-tag>
</view>
<view class="flex-row items-center justify-between mt-5rpx second-text-24">
<view>
<text>下单时间</text>
<text class="ml-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
}}</text>
</view>
<view>{{ hooks.useEnum("hardwareRefundStatusEnum", v.refundStatus) }}</view>
</view>
<view class="flex-row items-center justify-end mt-1">
<text
v-if="v.carList?.length && ['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)"
class="second-text-30 mr-2">
共 {{ v.carList.length }} 件
</text>
<view class="flex-col items-center">
<text v-if="v.couponPrice || v.extraPrice" class="main-text-26 mr-10rpx line-through">
¥{{ hooks.useMoney(v.totalPrice) }}
</text>
<text class="main-text-30 color-red!">
¥{{ hooks.useMoney(v.payPrice) }}
</text>
</view>
</view>
</view>
</view>
<view v-if="['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)">
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
<tm-collapse>
<tm-collapse-item :name="i" title="详细购买" titleFontSize="28" activeColor="#000" titleHeight="80"
:showBottomLine="false">
<view v-if="v.carList.length" class="flex justify-between items-center mb-2 px-3"
v-for="(pV, pI) in v.carList" :key="pI">
<view class="flex items-center w-6/10">
<view class="size-100rpx">
<image v-if="v.payType == '9'" class="size-100rpx rounded-8rpx"
:src="pV.productPic" mode="aspectFill" />
<image v-else class="size-100rpx rounded-8rpx" :src="filePath(pV.productPic)"
mode="aspectFill" />
</view>
<view class="ml-2 overflow-hidden">
<view class="sub-text-26 truncate">{{ pV.productName }}</view>
<view class="sub-text-26">¥ {{ hooks.useMoney(pV.productPrice) }}</view>
<view class="sub-text-26">x {{ pV.productCount }}</view>
</view>
</view>
<view class="sub-text-30 w-3/10">¥ {{ hooks.useMoney(pV.totalPrice) }}</view>
</view>
<view v-else class="second-text-26 text-center">无数据</view>
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
</tm-collapse-item>
</tm-collapse>
</view>
<view v-if="['5', '10'].includes(v.orderStatus)" class="w-full flex-row items-center justify-end my-1">
<!-- <view v-if="v.orderStatus == 5 && v.orderType == 2" class="botder_btn" @tap.stop="doRefund(v)">退款</view> -->
<view v-if="v.orderStatus === '5' && ['2', '7'].includes(v.orderType)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(v)">
核销码
</view>
</view>
<view class="card rounded-24rpx bg-white p-20rpx pb-0rpx mb-20rpx" v-for="(v, index) in pageHook.list"
:key="v.id">
<order-car :cardetail="v" :footerOperate="['refund', 'qrCodeDetail']" />
</view>
</z-paging>
</crud-layout>
+169
View File
@@ -0,0 +1,169 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { hooks, router } from "@@/index"
import { orderApi } from "@@/api"
import { filePath } from "@@/utils/file"
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
import { getLaunchParam } from "@/sheep/utils/app"
const data = {
tabs: [
{ id: null, name: "全部", disabled: false },
{ id: 1, name: "未支付", disabled: false },
{ id: 5, name: "已支付", disabled: false },
{ id: 10, name: "售后", disabled: false },
{ id: 20, name: "已核销", disabled: false },
],
}
const state = reactive<any>({
pa: {
orderStatus: data.tabs[0].id,
},
})
onLoad(() => {
const app = getApp()
app._initTabbar()
})
onShow(async () => {
const isLogin = await hooks.useCheckLogin()
if (!isLogin) return
if (pageRef.value) {
pageRef.value.reload(false)
}
})
const doTab = (e: any) => {
const target = data.tabs[e]
state.pa.orderStatus = target.id
pageRef.value.reload(true)
}
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(
pageRef,
getLaunchParam() == "canteen" // 食堂
? orderApi.caneenPage
: getLaunchParam() == "selfMerchant" // 自营商户
? orderApi.supPage
: getLaunchParam() == "coopMerchant" // 合作商户
? orderApi.supPage
: getLaunchParam() == "onlineShop"
? orderApi.supPage
: getLaunchParam() == "prosperityShop"
? orderApi.farmerSpuPage
: async () => [],
null,
"data.records",
)
const query = (page: number, pagesize: number) => {
const isLogin = hooks.useStaticCheckLogin()
if (!isLogin) return
pageHook.startPage(page, pagesize, false, state.pa)
}
// 查看订单和核销码
const doToDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
}
const doAppraise = () => { }
const doRefund = (v: any) => {
router.jump("/pages/refund/apply", { id: v.id })
}
</script>
<template>
<crud-layout bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
<template #top>
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
<z-tabs :list="data.tabs" @change="doTab" />
</view>
</template>
<view class="flex-col justify-center card p-3 m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i">
<view class="flex-row">
<image class="size-130rpx rounded-8rpx" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 ml-20rpx">
<view class="flex-row items-center justify-between">
<view class="sub-text-30">{{ v.merchantName }}</view>
<!-- <view class="m-l-15rpx color-#999 font-size-26rpx! iconfont icon-youjiantou" /> -->
<tm-tag v-if="v.orderStatus == 1" color="info" size="s">未支付</tm-tag>
<tm-tag v-if="v.orderStatus == 5" color="success" size="s">已支付</tm-tag>
<tm-tag v-if="v.orderStatus == 10" color="error" size="s">售后</tm-tag>
<tm-tag v-if="v.orderStatus == 20" size="s">已核销</tm-tag>
<tm-tag v-if="v.orderStatus == 30" color="info" size="s">已取消(智能柜)</tm-tag>
</view>
<view class="flex-row items-center justify-between mt-5rpx second-text-24">
<view>
<text>下单时间</text>
<text class="ml-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
}}</text>
</view>
<view>{{ hooks.useEnum("hardwareRefundStatusEnum", v.refundStatus) }}</view>
</view>
<view class="flex-row items-center justify-end mt-1">
<text
v-if="v.carList?.length && ['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)"
class="second-text-30 mr-2">
共 {{ v.carList.length }} 件
</text>
<view class="flex-col items-center">
<text v-if="v.couponPrice || v.extraPrice" class="main-text-26 mr-10rpx line-through">
¥{{ hooks.useMoney(v.totalPrice) }}
</text>
<text class="main-text-30 color-red!">
¥{{ hooks.useMoney(v.payPrice) }}
</text>
</view>
</view>
</view>
</view>
<view v-if="['5', '20'].includes(v.orderStatus) && ['1', '2'].includes(v.orderType)">
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
<tm-collapse>
<tm-collapse-item :name="i" title="详细购买" titleFontSize="28" activeColor="#000" titleHeight="80"
:showBottomLine="false">
<view v-if="v.carList.length" class="flex justify-between items-center mb-2 px-3"
v-for="(pV, pI) in v.carList" :key="pI">
<view class="flex items-center w-6/10">
<view class="size-100rpx">
<image v-if="v.payType == '9'" class="size-100rpx rounded-8rpx"
:src="pV.productPic" mode="aspectFill" />
<image v-else class="size-100rpx rounded-8rpx" :src="filePath(pV.productPic)"
mode="aspectFill" />
</view>
<view class="ml-2 overflow-hidden">
<view class="sub-text-26 truncate">{{ pV.productName }}</view>
<view class="sub-text-26">¥ {{ hooks.useMoney(pV.productPrice) }}</view>
<view class="sub-text-26">x {{ pV.productCount }}</view>
</view>
</view>
<view class="sub-text-30 w-3/10">¥ {{ hooks.useMoney(pV.totalPrice) }}</view>
</view>
<view v-else class="second-text-26 text-center">无数据</view>
<view class="border-b-2rpx border-dashed border-#eeeeee mt-2"></view>
</tm-collapse-item>
</tm-collapse>
</view>
<view v-if="['5', '10'].includes(v.orderStatus)" class="w-full flex-row items-center justify-end my-1">
<!-- <view v-if="v.orderStatus == 5 && v.orderType == 2" class="botder_btn" @tap.stop="doRefund(v)">退款</view> -->
<view v-if="v.orderStatus === '5' && ['2', '7'].includes(v.orderType)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(v)">
核销码
</view>
</view>
</view>
</z-paging>
</crud-layout>
</template>
+12 -12
View File
@@ -83,7 +83,7 @@
<view class="second-text-22">
<text>下单时间</text>
<text class="m-l-10rpx">{{ hooks.useTime(v.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
}}</text>
}}</text>
</view>
<view class="main-text-30 color-red! min-w-140rpx text-right">¥ {{
hooks.useMoney(v.totalPrice) }}
@@ -135,16 +135,16 @@
borderRadius: '10rpx',
marginTop: '10rpx',
}" :inactive-style="{
width: '300rpx',
height: '70rpx',
color: '#000000',
textAlign: 'center',
lineHeight: '70rpx',
fontWeight: 'bold',
fontSize: '30rpx',
borderRadius: '10rpx',
marginTop: '10rpx',
}" @change="refundTabChange" />
width: '300rpx',
height: '70rpx',
color: '#000000',
textAlign: 'center',
lineHeight: '70rpx',
fontWeight: 'bold',
fontSize: '30rpx',
borderRadius: '10rpx',
marginTop: '10rpx',
}" @change="refundTabChange" />
</view>
<view class="flex-col justify-center card p-x-15rpx p-y-25rpx m-b-10rpx">
@@ -185,7 +185,7 @@
共 {{ state.curOrder.carList.length }} 件
</view>
<view class="main-text-30 color-red!">¥ {{ hooks.useMoney(state.curOrder.totalPrice)
}}
}}
</view>
</view>
</view>
+6 -17
View File
@@ -3,13 +3,9 @@
<view class="text-center py-12">
<view class="main-text-40 mb-2">请选择您的身份</view>
</view>
<view class="px-6">
<view
class="card mb-6 overflow-hidden relative border-8 border-solid border-white duration-200"
:class="{ 'border-8 border-solid border-blue!': userRole === 'user' }"
@click="selectRole('user')"
>
<view class="card mb-6 overflow-hidden relative border-8 border-solid border-white duration-200"
:class="{ 'border-8 border-solid border-blue!': userRole === 'user' }" @click="selectRole('user')">
<view class="flex-row items-center p-6">
<view class="w-64 h-64 rounded-full flex-col-center mr-8">
<image src="@/static/page/userRole.svg" />
@@ -17,12 +13,9 @@
<view class="main-text-32 mb-1">我是用户</view>
</view>
</view>
<view
class="card mb-6 overflow-hidden relative border-8 border-solid border-white duration-200"
<view class="card mb-6 overflow-hidden relative border-8 border-solid border-white duration-200"
:class="{ 'border-8 border-solid border-blue!': userRole === 'merchant' }"
@click="selectRole('merchant')"
>
@click="selectRole('merchant')">
<view class="flex-row items-center p-6">
<view class="w-64 h-64 rounded-full flex-col-center mr-8">
<image src="@/static/page/merchantRole.svg" />
@@ -30,12 +23,9 @@
<view class="main-text-32 mb-1">我是合作商户</view>
</view>
</view>
<view
class="card overflow-hidden relative border-8 border-solid border-white duration-200"
<view class="card overflow-hidden relative border-8 border-solid border-white duration-200"
:class="{ 'border-8 border-solid border-blue!': userRole === 'doorAdmin' }"
@click="selectRole('doorAdmin')"
>
@click="selectRole('doorAdmin')">
<view class="flex-row items-center p-6">
<view class="w-64 h-64 rounded-full flex-col-center mr-8">
<image src="@/static/page/doorAdminRole.svg" />
@@ -44,7 +34,6 @@
</view>
</view>
</view>
<view class="flex-row-center px-10 mt-auto pb-10 font-bold">
<tm-button block @click="confirmRole">确 定</tm-button>
</view>
+314
View File
@@ -0,0 +1,314 @@
<script setup lang="ts">
import { ref } from "vue"
import { hooks, router } from "@@/index"
import {
hasRoleCode,
MCH_USER,
} from "@@/utils/user-role"
import { filePath } from "@@/utils/file"
import OrderStatusTag from "./order-status-tag.vue"
const emit = defineEmits(['cancel', 'confirmReceipt'])
export type Props = {
// 订单详情
cardetail: any
// 商品信息是否折叠
goodsCollapsed?: boolean
// 是否显示商户信息
visibleMch?: boolean
// 是否显示用户信息
visibleUser?: boolean
// 是否显示底部边框
visibleBottomBorder?: boolean
// 是否是最后一个订单
isLast?: boolean
// 底部操作区域控制。
// 传 true 时,等价于 ['refund', 'qrCodeDetail'],表示两个按钮都允许显示。
// 传数组时,可按需精确控制具体显示哪些按钮。
footerOperate?: boolean | Array<'refund' | 'qrCodeDetail' | 'cancel' | 'expressTracking'>
}
const props = withDefaults(defineProps<Props>(), {
cardetail: {},
goodsCollapsed: true,
visibleMch: true,
visibleUser: false,
visibleBottomBorder: false,
isLast: false,
footerOperate: false,
})
const collapseState = ref<Record<string, boolean>>({})
const getOrderKey = (order: any, index: number) => String(order?.id ?? index)
const hasMultipleGoods = (order: any) => (order?.carList?.length || 0) > 1
const isGoodsCollapsed = (order: any, index: number) => {
if (!props.goodsCollapsed || !hasMultipleGoods(order)) return false
return collapseState.value[getOrderKey(order, index)] ?? true
}
const toggleGoodsCollapse = (order: any, index: number) => {
if (!props.goodsCollapsed || !hasMultipleGoods(order)) return
const key = getOrderKey(order, index)
collapseState.value[key] = !isGoodsCollapsed(order, index)
}
// 判断某个底部操作按钮是否允许显示。
// `footerOperate === true` 时,视为开启全部按钮;
// 未传或传 false 时,不显示任何底部操作按钮;
// 传数组时,仅显示数组中包含的操作项。
const hasFooterOperate = (operate: 'refund' | 'qrCodeDetail' | 'cancel' | 'expressTracking') => {
if (props.footerOperate === true) return true
if (!Array.isArray(props.footerOperate)) return false
return props.footerOperate.includes(operate)
}
// 退款按钮显示条件:
// 1. footerOperate 允许 refund;
// 2. 订单状态为 5、210
// 3. 订单类型为 2、7
const showRefundButton = (order: any) => {
return hasFooterOperate('refund') && ['5', '210'].includes(String(order?.orderStatus)) && ['1', '2', '7'].includes(String(order?.orderType)) && ['2', '3', '4', '5'].includes(String(order?.merchantType))
}
// 显示订单详情按钮条件:
// 1. footerOperate 允许 refund;
// 2. 订单状态为 10、20;
// 3. 订单类型为 2。
// 4. 订单退款为 10,20,30。
const showRefundDetailButton = (order: any) => {
return (
hasFooterOperate('refund') &&
['10', '20'].includes(String(order?.orderStatus)) &&
['1', '2', '7'].includes(String(order?.orderType)) &&
['10', '20', '30'].includes(order?.refundStatus || '')
)
}
// 核销码按钮显示条件:
// 1. footerOperate 允许 qrCodeDetail;
// 2. 订单状态为 5;
// 3. 订单类型为 2 或 7。
const showQrCodeDetailButton = (order: any) => {
return (
hasFooterOperate('qrCodeDetail') &&
String(order?.orderStatus) === '5' &&
['2'].includes(String(order?.orderType))
)
}
// 手动核销按钮显示条件:
// 1. footerOperate 允许 cancel;
// 2. 订单状态为 5;
// 3. 订单类型为 2。
// 4. 用户有'移动端商户角色'
const showCancelButton = (order: any) => {
return (
hasFooterOperate('cancel') &&
String(order?.orderStatus) === '5' &&
['2'].includes(String(order?.orderType)) && hasRoleCode(MCH_USER)
)
}
// 查看订单和核销码
const doToDetail = (v: any) => {
router.jump("/pages/order/detail", { id: v.id })
}
// 申请售后
const doRefund = (v: any) => {
router.jump("/pages/refund/apply", { id: v.id })
}
const doCancel = (v: any) => {
emit('cancel', v)
}
</script>
<template>
<view class="flex-col justify-center pb-20rpx gap-20rpx"
:class="!props.isLast && props.visibleBottomBorder ? 'car-border' : ''">
<!-- start 用户信息 start -->
<view v-if="props.visibleUser" class="flex items-start justify-between gap-16rpx">
<view class="min-w-0 flex-1">
<view class="flex items-center min-w-0">
<text class="sub-text-26 truncate">{{ props.cardetail.username || "未知用户" }}</text>
<text class="mx-10rpx text-18rpx text-[#d0d5dd]">|</text>
<text class="second-text-24 truncate">{{ props.cardetail.userMobile || "-" }}</text>
</view>
<view class="mt-10rpx inline-flex max-w-full items-center rounded-24rpx bg-[#f8fafc] px-14rpx py-8rpx">
<text class="text-20rpx text-[#98a2b3]">下单时间</text>
<text v-if="props.cardetail.createTime" class="ml-10rpx text-20rpx text-[#475467] truncate">
{{ hooks.useTime(props.cardetail.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}
</text>
<text v-else class="ml-10rpx text-20rpx text-[#1570ef]">正在下单</text>
</view>
</view>
<view class="flex-shrink-0">
<view class="w-100rpx flex-row-center">
<order-status-tag :cardetail="props.cardetail" />
</view>
<view class="w-100rpx flex-row-center second-text-22 mt-10rpx" v-if="props.cardetail.refundStatus">
{{ hooks.useEnum("hardwareRefundStatusEnum", props.cardetail.refundStatus) }}
</view>
</view>
</view>
<!-- end 用户信息 end -->
<!-- start 商户信息 start -->
<view v-if="props.visibleMch" class="flex-row">
<image class="size-90rpx rounded-8rpx bg-[#999] shadow-main overflow-hidden"
:src="filePath(props.cardetail.logoPic) || '/static/logo/zax.png'" mode="aspectFill" />
<view class="flex-1 ml-20rpx">
<view class="flex-row items-center justify-between">
<view class="sub-text-26">{{ props.cardetail.merchantName }}</view>
<view class="w-100rpx flex-row-center">
<order-status-tag :cardetail="props.cardetail" />
</view>
</view>
<view class="flex-row items-center justify-between second-text-22">
<view>
<text>下单时间:</text>
<text v-if="props.cardetail.createTime" class="ml-10rpx">
{{ hooks.useTime(props.cardetail.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}
</text>
<text v-else class="ml-10rpx color-blue">正在下单</text>
</view>
<view class="w-100rpx flex-row-center mt-10rpx" v-if="props.cardetail.refundStatus">
{{ hooks.useEnum("hardwareRefundStatusEnum", props.cardetail.refundStatus) }}
</view>
</view>
</view>
</view>
<!-- end 商户信息 end -->
<!-- start 商品信息 start -->
<view v-if="props.cardetail?.carList?.length"
class="flex items-center rounded-12rpx bg-[#fafafa] px-20rpx py-16rpx transition-all duration-200"
@tap="toggleGoodsCollapse(props.cardetail, 0)">
<view class="min-w-0 flex-1">
<view v-if="isGoodsCollapsed(props.cardetail, 0)" class="flex items-center">
<image class="h-90rpx w-120rpx flex-shrink-0 rounded-8rpx bg-[#999] shadow-main overflow-hidden"
:src="filePath(props.cardetail.carList?.[0]?.productPic) || '/static/logo/zax.png'"
mode="aspectFill" />
<view class="ml-16rpx min-w-0 flex-1">
<view class="second-text-22 truncate">
{{ props.cardetail.carList?.[0]?.productName || "暂无商品" }}
</view>
<view v-if="hasMultipleGoods(props.cardetail)" class="mt-8rpx text-20rpx text-[#98a2b3]">
共 {{ props.cardetail.carList?.length || 0 }} 件,点击展开
</view>
<view v-else class="mt-8rpx text-20rpx text-[#98a2b3]">共 1 件商品</view>
</view>
</view>
<scroll-view v-if="!isGoodsCollapsed(props.cardetail, 0)" scroll-x
class="overflow-hidden whitespace-nowrap">
<view class="flex items-start">
<view v-for="item in props.cardetail.carList || []" :key="item.id"
class="mr-20rpx w-120rpx flex-shrink-0">
<image class="h-90rpx w-120rpx rounded-8rpx bg-[#999] shadow-main overflow-hidden"
:src="filePath(item.productPic) || '/static/logo/zax.png'" mode="aspectFill" />
<view class="second-text-22 mt-8rpx truncate">{{ item.productName }}</view>
</view>
</view>
</scroll-view>
</view>
<view class="order-car__summary w-120rpx flex-col-center">
<view class="sub-text-28">¥{{ hooks.useMoney(props.cardetail.payPrice) }}</view>
<view class="second-text-20 font-400! line-height-1em!">共 {{ props.cardetail.carList?.length || 0 }} 件
</view>
</view>
<view v-if="hasMultipleGoods(props.cardetail)"
class="ml-12rpx flex-col-center text-[#98a2b3] transition-transform duration-200">
<text class="text-20rpx">{{ isGoodsCollapsed(props.cardetail, 0) ? "展开" : "收起" }}</text>
<text class="iconfont icon-youjiantou mt-8rpx text-20rpx"
:class="isGoodsCollapsed(props.cardetail, 0) ? 'rotate-90' : '-rotate-90'" />
</view>
</view>
<!-- end 商品信息 end -->
<!-- start 简单购买信息(付款方式+支付金额) start -->
<view v-if="!props.cardetail?.carList?.length"
class="flex items-center justify-between rounded-12rpx border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-22rpx py-12rpx">
<view class="flex-row-center sub-text-24 gap-8rpx">
<text class="iconfont icon-zhifufangshi1 text-24rpx!" />
<text> {{ hooks.useEnum('hardwarePayTypeEnum', props.cardetail.payType) }}</text>
</view>
<view class="order-car__summary w-120rpx flex-col-center">
<view class="sub-text-28">¥{{ hooks.useMoney(props.cardetail.payPrice) }}</view>
</view>
</view>
<!-- end 简单购买信息(付款方式+支付金额) end -->
<!-- start 预约信息 start -->
<view v-if="props.cardetail.apptId && props.cardetail.apptStartTime && props.cardetail.apptEndTime"
class="flex items-center justify-between rounded-12rpx border border-[#eef3f8] bg-[linear-gradient(90deg,#ffffff_0%,#fafafa_100%)] px-22rpx py-12rpx shadow-[0_8rpx_24rpx_rgba(15,23,42,0.04)]">
<view class="min-w-0 flex-1">
<view class="second-text-22 text-[#98a2b3]!">预约时间段</view>
<view class="mt-10rpx sub-text-24">
{{ props.cardetail.apptStartTime }} - {{ props.cardetail.apptEndTime }}
</view>
</view>
<view v-if="props.cardetail.apptTitle"
class="flex items-center gap-4rpx ml-20rpx flex-shrink-0 rounded-full border border-[#d1e9ff] bg-[#eff8ff] px-18rpx py-16rpx text-22rpx line-height-1 text-[#1570ef]">
<text> {{ props.cardetail.apptUseDate }}</text>
<text>{{ props.cardetail.apptTitle }}</text>
</view>
</view>
<!-- end 预约信息 end -->
<!-- start 底部操作区域 start -->
<view
v-if="showRefundButton(props.cardetail) || showRefundDetailButton(props.cardetail) || showQrCodeDetailButton(props.cardetail) || showCancelButton(props.cardetail)"
class="w-full flex-row items-center justify-end my-1">
<view v-if="showRefundButton(props.cardetail)" class="botder_btn" @tap.stop="doRefund(props.cardetail)">
退款
</view>
<view v-if="showRefundDetailButton(props.cardetail)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(props.cardetail)">
售后详情
</view>
<view v-if="showQrCodeDetailButton(props.cardetail)" class="m-l-20rpx botder_btn"
@tap.stop="doToDetail(props.cardetail)">
核销码
</view>
<view v-if="showCancelButton(props.cardetail)" class="m-l-20rpx botder_btn"
@tap.stop="doCancel(props.cardetail)">
手动核销
</view>
</view>
<!-- end 底部操作区域 end -->
</view>
</template>
<style scoped lang="scss">
.car-border {
border-bottom: 1px solid #fafafa;
}
.order-car__summary {
position: relative;
padding-left: 18rpx;
}
.order-car__summary::before {
content: "";
position: absolute;
left: -8rpx;
top: 8rpx;
bottom: 8rpx;
width: 20rpx;
pointer-events: none;
background: linear-gradient(90deg, rgba(15, 23, 42, 0.08) 0%, rgba(15, 23, 42, 0.03) 38%, rgba(15, 23, 42, 0) 100%);
filter: blur(8rpx);
}
</style>
@@ -0,0 +1,26 @@
<script setup lang="ts">
export type Props = {
// 订单详情
cardetail: any
}
const props = withDefaults(defineProps<Props>(), {
cardetail: {},
})
</script>
<template>
<slot v-if="props.cardetail.orderStatus == 5">
<tm-tag v-if="props.cardetail.orderType == 2" color="success" size="xs">待使用</tm-tag>
<tm-tag v-else-if="props.cardetail.orderType == 7" color="success" size="xs">待发货</tm-tag>
<tm-tag v-else color="success" size="xs">已支付</tm-tag>
</slot>
<tm-tag v-if="props.cardetail.orderStatus == 200" color="success" size="xs">待发货</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 210" color="success" size="xs">待收货</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 220" size="xs">已收货</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 230" color="success" size="xs">已评价</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 10" color="error" size="xs">售后</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 20" size="xs">已核销</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 30" color="info" size="xs">已取消(智能柜)</tm-tag>
<tm-tag v-if="props.cardetail.orderStatus == 110" color="success" size="xs">配送中</tm-tag>
</template>
+8
View File
@@ -36,6 +36,13 @@ const hardwareRefundStatusEnum = {
30: "售后失败",
}
const hardSaleStatusEnum = {
0: "售后中",
50: "售后完成",
61: "买家取消",
62: "卖家拒绝",
}
const carTypeEnum = {
1: "私家车",
2: "公车",
@@ -98,4 +105,5 @@ export default {
actCouponLimitTypeEnum,
actCouponTypeEnum,
actUserReceiveEnum,
hardSaleStatusEnum,
}
+8 -1
View File
@@ -7,6 +7,7 @@ export function useScanCode(actModalShow) {
const appInfo = await getAppInfo()
if (isDevEnv()) {
let testQrCode = ""
// 开发环境模拟扫码
// const testQrCode = "eyJpZCI6IjE5MDU4ODg4NjQ0NTQ1MzMxMjEiLCJuYW1lIjoi6ICB5Y6/5bqc6aOf5aCCIn0=" // 测试线上-老县府食堂-收款二维码
// const testQrCode = "eyJoYXJkRG9vcklkIjoiMTkxNDIxNTYwODQzNzQxMTg0MSIsImNhYmluZXRObyI6IjEifQ==" // 测试线上-智能柜-收款二维码
@@ -15,7 +16,13 @@ export function useScanCode(actModalShow) {
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?id=1905816751043452930&name=测试新县府超市" // 邓本地-测试环境-新县府超市收款二维码
// const testQrCode = "http://192.168.101.45:8889/h5/#/pages/pay/scanPay?hardDoorId=1913157080418295809&cabinetNo=1" // 测试智能柜柜门二维码
// const testQrCode = "eyJjYW5jZWxJZCI6IjE5ODQyNDA4MzM5NDMyMDc5MzcifQ==" // 测试商户活动核销
const testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1931916121241079810"
// const testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1931916121241079810"
// 测试环境(自营食堂)-新县府食堂
// testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1905816666859577346"
// 测试环境(合作商家)-测试部门超市
testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1931916121241079810"
// 测试环境(自营商家)-老县府超市
// testQrCode = "https://stgl.pj.gov.cn/h5/#/pages/pay/scanPay?id=1905816898011865090"
uni.showModal({ content: "开发环境不是浙政钉和诗画环境,扫码仅供测试" })
return Promise.resolve(testQrCode)
}
+111 -3
View File
@@ -1,9 +1,9 @@
@font-face {
font-family: "iconfont";
/* Project id 4740692 */
src: url('//at.alicdn.com/t/c/font_4740692_uwg9mtj0d0p.woff2?t=1770172172940') format('woff2'),
url('//at.alicdn.com/t/c/font_4740692_uwg9mtj0d0p.woff?t=1770172172940') format('woff'),
url('//at.alicdn.com/t/c/font_4740692_uwg9mtj0d0p.ttf?t=1770172172940') format('truetype');
src: url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.woff2?t=1782372215492') format('woff2'),
url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.woff?t=1782372215492') format('woff'),
url('https://at.alicdn.com/t/c/font_4740692_ol93veips6n.ttf?t=1782372215492') format('truetype');
}
.iconfont {
@@ -14,6 +14,114 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-jisuwuliu:before {
content: "\e61a";
}
.icon-add:before {
content: "\e79e";
}
.icon-add1:before {
content: "\e6aa";
}
.icon-bianji:before {
content: "\e763";
}
.icon-address1:before {
content: "\e605";
}
.icon-address:before {
content: "\e659";
}
.icon-address-f:before {
content: "\e604";
}
.icon-zhifufangshi:before {
content: "\e759";
}
.icon-zhifufangshi-yuezhifu:before {
content: "\e68c";
}
.icon-zhifufangshi1:before {
content: "\e603";
}
.icon-rili:before {
content: "\e60e";
}
.icon-hexiaoicon:before {
content: "\e602";
}
.icon-hexiao1:before {
content: "\e7fe";
}
.icon-hexiao:before {
content: "\e63f";
}
.icon-fanhui:before {
content: "\e8fb";
}
.icon-tongzhi:before {
content: "\e61b";
}
.icon-dangqianweizhi:before {
content: "\e678";
}
.icon-sousuo:before {
content: "\e619";
}
.icon-yizuofei1:before {
content: "\e60d";
}
.icon-yizuofei:before {
content: "\f2d8";
}
.icon-jilu1:before {
content: "\e631";
}
.icon-jilu:before {
content: "\e664";
}
.icon-kefu:before {
content: "\e677";
}
.icon-kefu1:before {
content: "\e612";
}
.icon-info:before {
content: "\e64a";
}
.icon-setting:before {
content: "\e8d8";
}
.icon-shezhi:before {
content: "\e601";
}
.icon-yiguoqi:before {
content: "\e642";
}
+76
View File
@@ -0,0 +1,76 @@
import { constant } from "@@/index"
import { userApi } from "@@/api"
export const ROLE_ONLINE_CANTEEN_MCH = "ONLINE_CANTEEN_MCH"
export const ROLE_CANTEEN_RIDER = "CANTEEN_RIDER"
// 移动端商户角色(核销订餐二维码)
export const MCH_USER = "MCH_USER"
/** 移动端后台管理入口角色 */
export const ROLE_MOBILE_ADMIN_PC = "MOBILE_ADMIN_PC"
/** 新增用户默认角色 */
export const ROLE_GENERAL_USER = "GENERAL_USER"
export const ROLE_WALLET_AUTO_CREATE = "WALLET_AUTO_CREATE"
const DEFAULT_NEW_USER_ROLES = [
{ roleCode: ROLE_GENERAL_USER, roleId: "2" },
{ roleCode: ROLE_WALLET_AUTO_CREATE, roleId: "1873557615006810113" },
] as const
/** 新增用户时默认勾选的角色 ID(优先按 roleCode 从列表解析) */
export const resolveDefaultNewUserRoleIds = (roleList: { roleId?: string | number; roleCode?: string }[]): string[] =>
DEFAULT_NEW_USER_ROLES.map(({ roleCode, roleId }) => {
const hit = roleList.find((r) => r.roleCode === roleCode)
return hit ? String(hit.roleId) : roleId
})
export const getUserRoleCodes = (): string[] => {
const userInfo: any = uni.getStorageSync(constant.storageConstant.USERINFO) || {}
const codes = userInfo?.roleCodes
if (Array.isArray(codes)) return codes.filter(Boolean)
return []
}
export const hasRoleCode = (code: string) => getUserRoleCodes().includes(code)
/** 刷新用户信息中的 roleCodes(我的页菜单、统计页鉴权用) */
export const refreshUserRoleCodes = async (): Promise<string[]> => {
try {
const res: any = await userApi.userInfo()
if (res?.code === 0 && res.data) {
uni.setStorageSync(constant.storageConstant.USERINFO, res.data)
return Array.isArray(res.data.roleCodes) ? res.data.roleCodes : []
}
} catch (e) {
console.warn("refreshUserRoleCodes failed", e)
}
return getUserRoleCodes()
}
/** 移动端后台管理 - 权限码(与 PC 角色菜单分配一致) */
export const PERM_SYS_USER_ADD = "sys_user_add"
export const PERM_SYS_USER_EDIT = "sys_user_edit"
export const PERM_SYS_USER_DEL = "sys_user_del"
export const PERM_WALLET_MANAGE_VIEW = "wallet_walletManage_view"
export const PERM_WALLET_MANAGE_EDIT = "wallet_walletManage_edit"
export const PERM_WALLET_UPDATE_BALANCE = "wallet_walletTransaction_update_balance"
export const ADMIN_USER_PERMS = [PERM_SYS_USER_ADD, PERM_SYS_USER_EDIT, PERM_SYS_USER_DEL]
export const ADMIN_WALLET_PERMS = [PERM_WALLET_MANAGE_VIEW, PERM_WALLET_MANAGE_EDIT, PERM_WALLET_UPDATE_BALANCE]
export const ADMIN_HUB_PERMS = [...ADMIN_USER_PERMS, ...ADMIN_WALLET_PERMS]
export const getUserPermissions = (): string[] => {
const userInfo: any = uni.getStorageSync(constant.storageConstant.USERINFO) || {}
const perms = userInfo?.permissions
if (Array.isArray(perms)) return perms.filter(Boolean)
return []
}
export const hasPermission = (code: string) => getUserPermissions().includes(code)
export const hasAnyPermission = (codes: string[]) => codes.some((code) => hasPermission(code))
/** 刷新 permissions(顺带刷新 roleCodes) */
export const refreshUserPermissions = async (): Promise<string[]> => {
await refreshUserRoleCodes()
return getUserPermissions()
}