Compare commits
7 Commits
42eae0b1cd
...
1582ca6f2f
Author | SHA1 | Date |
---|---|---|
|
1582ca6f2f | |
|
4891d9376c | |
|
7a9847bb25 | |
|
533a94ca9b | |
|
adf5bc4d20 | |
|
ac5d9292ca | |
|
9c81c228ee |
|
@ -0,0 +1,55 @@
|
||||||
|
import { request } from '@/http/axios'
|
||||||
|
import {
|
||||||
|
GetTokenParams,
|
||||||
|
LoginData,
|
||||||
|
LogoutResponse,
|
||||||
|
SmsSendResponse,
|
||||||
|
TokenResponse,
|
||||||
|
VerifySmsParams,
|
||||||
|
WechatQrCodeParams
|
||||||
|
} from './type'
|
||||||
|
|
||||||
|
/** 获取临时令牌 */
|
||||||
|
export function getTokenApi(appName: string) {
|
||||||
|
return request<ApiResponseData<TokenResponse>>({
|
||||||
|
url: '/wx/login/getToken',
|
||||||
|
method: 'get',
|
||||||
|
params: { appName }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取微信登录二维码 */
|
||||||
|
export function getWechatQrCodeApi(token: string) {
|
||||||
|
return request<ApiResponseData<string>>({
|
||||||
|
url: '/wx/login/wechat/qrcode',
|
||||||
|
method: 'get',
|
||||||
|
params: { token }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发送短信验证码 */
|
||||||
|
export function sendSmsApi(token: string, tel: string) {
|
||||||
|
return request<ApiResponseData<SmsSendResponse>>({
|
||||||
|
url: '/wx/login/sendSms',
|
||||||
|
method: 'post',
|
||||||
|
params: { token, tel }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 验证短信验证码 */
|
||||||
|
export function verifySmsApi(params: VerifySmsParams) {
|
||||||
|
return request<ApiResponseData<LoginData>>({
|
||||||
|
url: '/wx/login/verifySms',
|
||||||
|
method: 'post',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用户退出登录 */
|
||||||
|
export function logoutApi(token: string) {
|
||||||
|
return request<ApiResponseData<LogoutResponse>>({
|
||||||
|
url: '/wx/login/logout',
|
||||||
|
method: 'post',
|
||||||
|
params: { token }
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/** 令牌响应 */
|
||||||
|
export interface TokenResponse {
|
||||||
|
/** 认证令牌 */
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 退出登录响应 */
|
||||||
|
export interface LogoutResponse {
|
||||||
|
/** 是否成功 */
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 短信发送响应 */
|
||||||
|
export interface SmsSendResponse {
|
||||||
|
/** 发送状态 */
|
||||||
|
success: boolean
|
||||||
|
/** 错误信息 */
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 登录数据 */
|
||||||
|
export interface LoginData {
|
||||||
|
/** 用户头像 */
|
||||||
|
face_img: string
|
||||||
|
/** 登录状态 */
|
||||||
|
success: boolean
|
||||||
|
/** 用户性别 */
|
||||||
|
sex: string
|
||||||
|
/** 用户姓名 */
|
||||||
|
name: string
|
||||||
|
/** 用户ID */
|
||||||
|
userid: string
|
||||||
|
/** 是否已注册 */
|
||||||
|
registered: boolean
|
||||||
|
/** 联系电话 */
|
||||||
|
tel: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取令牌参数 */
|
||||||
|
export type GetTokenParams = {
|
||||||
|
/** 应用名称 */
|
||||||
|
appName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 微信二维码参数 */
|
||||||
|
export type WechatQrCodeParams = {
|
||||||
|
/** 认证令牌 */
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 短信验证参数 */
|
||||||
|
export type VerifySmsParams = {
|
||||||
|
/** 认证令牌 */
|
||||||
|
token: string
|
||||||
|
/** 手机号码 */
|
||||||
|
tel: string
|
||||||
|
/** 验证码 */
|
||||||
|
vcode: string
|
||||||
|
}
|
|
@ -1,5 +1,15 @@
|
||||||
import { request } from '@/http/axios'
|
import { request } from '@/http/axios'
|
||||||
import { SubmitApprovalRequestData, SubmitApprovalResponseData } from './type'
|
import { SubmitApprovalRequestData, SubmitApprovalResponseData, SearchApiReturnApprovalQuery, ApiResponsePageData, ReturnApprovalEntity, HandleApprovalRequestData } from './type'
|
||||||
|
|
||||||
|
export const getApprovalListApi = (params: SearchApiReturnApprovalQuery) => {
|
||||||
|
return request<ApiResponsePageData<ReturnApprovalEntity>>({
|
||||||
|
url: 'approval/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const submitApprovalApi = (data: SubmitApprovalRequestData) => {
|
export const submitApprovalApi = (data: SubmitApprovalRequestData) => {
|
||||||
return request<SubmitApprovalResponseData>({
|
return request<SubmitApprovalResponseData>({
|
||||||
|
@ -7,4 +17,12 @@ export const submitApprovalApi = (data: SubmitApprovalRequestData) => {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleApprovalApi = (data: HandleApprovalRequestData) => {
|
||||||
|
return request<ApiResponseMsgData<string>>({
|
||||||
|
url: 'approval/handle',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,65 @@ export interface SubmitApprovalRequestData {
|
||||||
orderGoodsId: number
|
orderGoodsId: number
|
||||||
returnQuantity: number
|
returnQuantity: number
|
||||||
returnImages: string
|
returnImages: string
|
||||||
|
returnRemark: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HandleApprovalRequestData {
|
||||||
|
/** 审批ID */
|
||||||
|
approvalId: number
|
||||||
|
/** 审批状态 */
|
||||||
|
status: number
|
||||||
|
returnAmount: number
|
||||||
|
auditImages: string
|
||||||
|
auditRemark: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchApiReturnApprovalQuery {
|
||||||
|
pageNum: number
|
||||||
|
pageSize: number
|
||||||
|
approvalId?: number
|
||||||
|
orderId?: number
|
||||||
|
goodsId?: number
|
||||||
|
status?: number
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponsePageData<T> {
|
||||||
|
code: number
|
||||||
|
msg: string
|
||||||
|
data: {
|
||||||
|
total: number
|
||||||
|
rows: T[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReturnApprovalEntity {
|
||||||
|
approvalId: number
|
||||||
|
orderId: number
|
||||||
|
goodsId: number
|
||||||
|
/** 关联订单商品ID */
|
||||||
|
orderGoodsId: number
|
||||||
|
/** 归还数量 */
|
||||||
|
returnQuantity: number
|
||||||
|
/** 商品单价 */
|
||||||
|
goodsPrice: number
|
||||||
|
status: number
|
||||||
|
returnAmount: number
|
||||||
|
/** 归还图片路径数组 */
|
||||||
|
returnImages: string
|
||||||
|
/** 审核图片路径数组 */
|
||||||
|
auditImages: string
|
||||||
|
/** 归还说明 */
|
||||||
|
returnRemark: string
|
||||||
|
/** 审核说明 */
|
||||||
|
auditRemark: string
|
||||||
|
createTime: string
|
||||||
|
updateTime: string
|
||||||
|
/** 商品名称 */
|
||||||
|
goodsName: string
|
||||||
|
/** 封面图URL */
|
||||||
|
coverImg: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SubmitApprovalResponseData = ApiResponseMsgData<{
|
export type SubmitApprovalResponseData = ApiResponseMsgData<{
|
||||||
|
|
|
@ -16,10 +16,21 @@ export type category = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubmitOrderRequestData {
|
export interface SubmitOrderRequestData {
|
||||||
|
/** 微信用户唯一标识 */
|
||||||
openid: string;
|
openid: string;
|
||||||
|
/** 系统用户ID */
|
||||||
userid: string;
|
userid: string;
|
||||||
|
/** 企业ID */
|
||||||
corpid: string;
|
corpid: string;
|
||||||
|
/** 支付类型 wechat:微信 balance:余额 */
|
||||||
paymentType: 'wechat' | 'balance';
|
paymentType: 'wechat' | 'balance';
|
||||||
|
/** 联系电话 */
|
||||||
|
mobile: string;
|
||||||
|
/** 企业微信用户ID或汇邦云用户ID */
|
||||||
|
qyUserid: string;
|
||||||
|
/** 是否内部订单 0否 1汇邦云用户 2企业微信用户 */
|
||||||
|
isInternal: number;
|
||||||
|
/** 订单商品明细列表 */
|
||||||
goodsList: Array<{
|
goodsList: Array<{
|
||||||
goodsId: number
|
goodsId: number
|
||||||
quantity: number
|
quantity: number
|
||||||
|
|
|
@ -3,7 +3,7 @@ const router = useRouter()
|
||||||
|
|
||||||
const tabbarItemList = computed(() => {
|
const tabbarItemList = computed(() => {
|
||||||
const routes = router.getRoutes()
|
const routes = router.getRoutes()
|
||||||
return routes.filter(route => route.meta.layout?.tabbar?.showTabbar)
|
return routes.filter(route => route.meta.layout?.tabbar?.showTabbar && route.path !== '/cabinet')
|
||||||
.map(route => ({
|
.map(route => ({
|
||||||
title: route.meta.title,
|
title: route.meta.title,
|
||||||
icon: route.meta.layout?.tabbar?.icon,
|
icon: route.meta.layout?.tabbar?.icon,
|
||||||
|
|
|
@ -0,0 +1,358 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { showConfirmDialog, showSuccessToast, showToast, UploaderFileListItem, Popup, Picker } from 'vant'
|
||||||
|
|
||||||
|
const showStatusPicker = ref(false)
|
||||||
|
const showPreview = ref(false)
|
||||||
|
const currentPreviewImage = ref('')
|
||||||
|
const statusMap: { [key: number]: string } = {
|
||||||
|
1: '待审核',
|
||||||
|
2: '已通过',
|
||||||
|
3: '已驳回'
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ text: '待审核', value: 1 },
|
||||||
|
{ text: '通过', value: 2 },
|
||||||
|
{ text: '驳回', value: 3 }
|
||||||
|
]
|
||||||
|
import axios from "axios"
|
||||||
|
import { handleApprovalApi } from '@/common/apis/approval'
|
||||||
|
import type { HandleApprovalRequestData } from '@/common/apis/approval/type'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { useApprovalStore } from '@/pinia/stores/approval'
|
||||||
|
|
||||||
|
const { VITE_APP_BASE_API } = import.meta.env;
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const approvalStore = useApprovalStore()
|
||||||
|
|
||||||
|
const formData = ref<HandleApprovalRequestData>({
|
||||||
|
approvalId: approvalStore.currentApproval?.approvalId || 0,
|
||||||
|
status: 1,
|
||||||
|
returnAmount: 0,
|
||||||
|
auditImages: '',
|
||||||
|
auditRemark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitting = ref(false)
|
||||||
|
const fileList = ref<UploaderFileListItem[]>([])
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
if (!formData.value.approvalId || isNaN(formData.value.approvalId)) {
|
||||||
|
showConfirmDialog({ title: '错误', message: '审批单ID参数错误' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (formData.value.returnAmount <= 0) {
|
||||||
|
showConfirmDialog({ title: '提示', message: '退款金额需大于0' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (null != approvalStore.currentApproval &&
|
||||||
|
formData.value.returnAmount > approvalStore.currentApproval.goodsPrice) {
|
||||||
|
showConfirmDialog({ title: '提示', message: '退款金额不能超过商品价格' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.value.auditImages) {
|
||||||
|
showConfirmDialog({ title: '提示', message: '请上传审核图片' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (formData.value.status === 3 && !formData.value.auditRemark) {
|
||||||
|
showConfirmDialog({ title: '提示', message: '驳回时必须填写审核说明' })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListItem[]) => {
|
||||||
|
const files = Array.isArray(items) ? items : [items]
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const uploadPromises = files.map(async (item) => {
|
||||||
|
item.status = 'uploading'
|
||||||
|
item.message = '上传中...'
|
||||||
|
const file = item.file as File
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
|
||||||
|
const { data } = await axios.post<{
|
||||||
|
code: number
|
||||||
|
data: { url: string }
|
||||||
|
message?: string
|
||||||
|
}>(VITE_APP_BASE_API + '/file/upload', formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(data.message || '文件上传失败')
|
||||||
|
}
|
||||||
|
return { url: data.data.url }
|
||||||
|
})
|
||||||
|
|
||||||
|
const urls = await Promise.all(uploadPromises)
|
||||||
|
files.forEach((item, index) => {
|
||||||
|
item.status = 'done'
|
||||||
|
item.message = '上传成功'
|
||||||
|
item.url = urls[index].url
|
||||||
|
})
|
||||||
|
formData.value.auditImages = fileList.value.map(item => item.url).join(',')
|
||||||
|
} catch (error) {
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '上传失败',
|
||||||
|
message: error instanceof Error ? error.message : '未知错误'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStatusConfirm = ({ selectedOptions }: { selectedOptions: { value: number }[] }) => {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
if (!selectedOptions?.[0]?.value) {
|
||||||
|
throw new Error('请选择有效的审批状态')
|
||||||
|
}
|
||||||
|
formData.value.status = selectedOptions[0].value
|
||||||
|
showStatusPicker.value = false
|
||||||
|
} catch (error) {
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '操作失败',
|
||||||
|
message: error instanceof Error ? error.message : '状态选择异常'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewImage = (url: string) => {
|
||||||
|
currentPreviewImage.value = url
|
||||||
|
showPreview.value = true
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
if (!approvalStore.currentApproval) {
|
||||||
|
router.push('/approval/list')
|
||||||
|
} else if (approvalStore.currentApproval.status !== 1) {
|
||||||
|
// 填充历史审批数据
|
||||||
|
formData.value = {
|
||||||
|
...formData.value,
|
||||||
|
status: approvalStore.currentApproval.status,
|
||||||
|
returnAmount: approvalStore.currentApproval.returnAmount,
|
||||||
|
auditRemark: approvalStore.currentApproval.auditRemark,
|
||||||
|
auditImages: approvalStore.currentApproval.auditImages
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件回显
|
||||||
|
if (approvalStore.currentApproval.auditImages) {
|
||||||
|
fileList.value = approvalStore.currentApproval.auditImages.split(',').map(url => ({
|
||||||
|
url,
|
||||||
|
status: 'done',
|
||||||
|
message: '已上传'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!validateForm()) return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const { code, msg } = await handleApprovalApi(formData.value)
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
showSuccessToast('操作成功')
|
||||||
|
await showConfirmDialog({
|
||||||
|
title: "操作成功",
|
||||||
|
message: `审批处理已完成`
|
||||||
|
})
|
||||||
|
router.push('/approval/list')
|
||||||
|
} else {
|
||||||
|
console.error('操作失败code:', code, 'msg:', msg)
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '操作失败',
|
||||||
|
message: msg || '操作失败'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
showConfirmDialog({
|
||||||
|
title: '提交失败',
|
||||||
|
message: error instanceof Error ? error.message : '网络请求异常'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="approval-container">
|
||||||
|
<van-nav-bar title="审批处理" left-text="返回" left-arrow fixed @click-left="() => router.go(-1)" />
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<van-cell-group>
|
||||||
|
<van-cell title="商品名称" :value="approvalStore.currentApproval?.goodsName" />
|
||||||
|
<van-cell title="商品封面" v-if="approvalStore.currentApproval?.coverImg">
|
||||||
|
<van-image
|
||||||
|
:src="approvalStore.currentApproval.coverImg"
|
||||||
|
fit="cover"
|
||||||
|
width="80"
|
||||||
|
height="80"
|
||||||
|
@click="previewImage(approvalStore.currentApproval.coverImg)"
|
||||||
|
style="margin-top: 8px"
|
||||||
|
/>
|
||||||
|
</van-cell>
|
||||||
|
<van-cell title="退还数量" :value="approvalStore.currentApproval?.returnQuantity" />
|
||||||
|
<van-cell title="商品单价" :value="`¥${approvalStore.currentApproval?.goodsPrice}`" />
|
||||||
|
<van-cell title="当前状态" :value="statusMap[approvalStore.currentApproval?.status || 1]" />
|
||||||
|
<van-cell title="退还说明" :value="approvalStore.currentApproval?.returnRemark" />
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-cell-group class="image-section">
|
||||||
|
<van-cell title="退还凭证">
|
||||||
|
<van-grid :column-num="3" gutter="10">
|
||||||
|
<van-grid-item v-for="(img, index) in approvalStore.currentApproval?.returnImages.split(',')"
|
||||||
|
:key="index">
|
||||||
|
<van-image :src="img" fit="cover" @click="previewImage(img)" />
|
||||||
|
</van-grid-item>
|
||||||
|
</van-grid>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-cell-group>
|
||||||
|
<!-- 原有表单字段保持不变 -->
|
||||||
|
<van-field
|
||||||
|
v-model="formData.returnAmount"
|
||||||
|
:readonly="approvalStore.currentApproval?.status !== 1"
|
||||||
|
label="退款金额"
|
||||||
|
type="number"
|
||||||
|
class="audit-remark-field"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
<van-field
|
||||||
|
:model-value="statusOptions.find(opt => opt.value === formData.status)?.text || ''"
|
||||||
|
label="审批结果"
|
||||||
|
readonly
|
||||||
|
@click="showStatusPicker = true"
|
||||||
|
:disabled="approvalStore.currentApproval?.status !== 1"
|
||||||
|
class="clickable-status-field"
|
||||||
|
/>
|
||||||
|
<van-field
|
||||||
|
v-model="formData.auditRemark"
|
||||||
|
label="审核说明"
|
||||||
|
type="textarea"
|
||||||
|
rows="2"
|
||||||
|
autosize
|
||||||
|
class="audit-remark-field"
|
||||||
|
/>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<van-popup v-model:show="showStatusPicker" position="bottom">
|
||||||
|
<van-picker :columns="statusOptions" @confirm="onStatusConfirm" @cancel="showStatusPicker = false" />
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<van-popup v-model:show="showPreview" position="center" :style="{ width: '90%', height: '80%' }" round>
|
||||||
|
<div class="preview-wrapper">
|
||||||
|
<van-icon name="cross" class="close-icon" @click="showPreview = false" />
|
||||||
|
<van-image :src="currentPreviewImage" fit="contain" class="preview-image" />
|
||||||
|
</div>
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<van-cell-group class="upload-section">
|
||||||
|
<van-cell title="审核凭证">
|
||||||
|
<template #extra>
|
||||||
|
<van-uploader v-model="fileList" multiple max-count="3" :after-read="handleFileUpload" />
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="submit-bar"
|
||||||
|
v-if="approvalStore.currentApproval?.status !== 2">
|
||||||
|
<van-button
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
:loading="submitting"
|
||||||
|
loading-text="提交中..."
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
提交审批
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.approval-container {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding-top: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-bar {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 0 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audit-remark-field::v-deep .van-field__control {
|
||||||
|
background-color: #fffbe6;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-status-field:not(:disabled)::v-deep .van-field__control {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-status-field:not(:disabled):hover::v-deep .van-field__control {
|
||||||
|
background-color: #f2f3f5;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-status-field:not(:disabled):active::v-deep .van-field__control {
|
||||||
|
background-color: #ebedf0;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,192 @@
|
||||||
|
<template>
|
||||||
|
<div class="approval-list-container">
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<van-form @submit="handleSearch">
|
||||||
|
<van-field v-model="searchParams.approvalId" label="审批单号" type="number" placeholder="请输入审批单号" />
|
||||||
|
|
||||||
|
<van-field v-model="searchParams.orderId" label="订单编号" type="number" placeholder="请输入订单编号" />
|
||||||
|
|
||||||
|
<van-field v-model="searchParams.goodsId" label="商品ID" type="number" placeholder="请输入商品ID" />
|
||||||
|
|
||||||
|
<van-field name="status" label="审批状态" readonly clickable v-model="statusText" placeholder="请选择状态"
|
||||||
|
@click="showStatusPicker = true" />
|
||||||
|
<van-popup v-model:show="showStatusPicker" position="bottom">
|
||||||
|
<van-picker :columns="statusOptions" @confirm="onStatusConfirm" @cancel="showStatusPicker = false" />
|
||||||
|
</van-popup>
|
||||||
|
|
||||||
|
<!-- <van-field
|
||||||
|
readonly
|
||||||
|
clickable
|
||||||
|
name="date"
|
||||||
|
:value="dateRangeText"
|
||||||
|
label="申请时间"
|
||||||
|
placeholder="选择时间范围"
|
||||||
|
@click="showDatePicker = true"
|
||||||
|
/>
|
||||||
|
<van-popup v-model:show="showDatePicker" position="bottom">
|
||||||
|
<van-datetime-picker
|
||||||
|
type="daterange"
|
||||||
|
@confirm="onDateConfirm"
|
||||||
|
@cancel="showDatePicker = false"
|
||||||
|
/>
|
||||||
|
</van-popup> -->
|
||||||
|
|
||||||
|
<div style="margin: 16px;">
|
||||||
|
<van-button block type="primary" native-type="submit">搜索</van-button>
|
||||||
|
<van-button block plain type="primary" style="margin-top: 10px;" @click="handleReset">重置</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
|
||||||
|
<!-- 审批列表 -->
|
||||||
|
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||||
|
<van-cell v-for="item in list" :key="item.approvalId" :title="`审批单号:${item.approvalId}`" clickable
|
||||||
|
@click="handleCellClick(item.approvalId)">
|
||||||
|
<template #label>
|
||||||
|
<div>商品名称:{{ item.goodsName }}</div>
|
||||||
|
<div>申请时间:{{ item.createTime }}</div>
|
||||||
|
<van-tag :type="statusTagType(item.status)">
|
||||||
|
{{ statusMap[item.status] }}
|
||||||
|
</van-tag>
|
||||||
|
</template>
|
||||||
|
</van-cell>
|
||||||
|
</van-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { getApprovalListApi } from '@/common/apis/approval'
|
||||||
|
import type { SearchApiReturnApprovalQuery, ReturnApprovalEntity } from '@/common/apis/approval/type'
|
||||||
|
import type { PickerConfirmEventParams } from 'vant/es';
|
||||||
|
import { useApprovalStore } from '@/pinia/stores/approval';
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 搜索参数
|
||||||
|
const searchParams = reactive<SearchApiReturnApprovalQuery>({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 状态选择相关
|
||||||
|
const showStatusPicker = ref(false)
|
||||||
|
const statusOptions = [
|
||||||
|
{ text: '全部', value: undefined },
|
||||||
|
{ text: '待审核', value: 1 },
|
||||||
|
{ text: '已通过', value: 2 },
|
||||||
|
{ text: '已驳回', value: 3 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const statusMap: { [key: number]: string } = {
|
||||||
|
1: '待审核',
|
||||||
|
2: '已通过',
|
||||||
|
3: '已驳回'
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusText = ref('')
|
||||||
|
|
||||||
|
// 时间选择相关
|
||||||
|
const showDatePicker = ref(false)
|
||||||
|
const dateRangeText = ref('')
|
||||||
|
|
||||||
|
// 列表相关
|
||||||
|
const list = ref<ReturnApprovalEntity[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const finished = ref(false)
|
||||||
|
|
||||||
|
const handleCellClick = (approvalId: number) => {
|
||||||
|
const approvalStore = useApprovalStore()
|
||||||
|
const currentItem = list.value.find(item => item.approvalId === approvalId)
|
||||||
|
if (currentItem) {
|
||||||
|
approvalStore.setCurrentApproval(currentItem)
|
||||||
|
}
|
||||||
|
router.push(`/approval/handle/${approvalId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态标签类型
|
||||||
|
const statusTagType = (status: number) => {
|
||||||
|
switch (status) {
|
||||||
|
case 1: return 'warning'
|
||||||
|
case 2: return 'success'
|
||||||
|
case 3: return 'danger'
|
||||||
|
default: return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理状态选择
|
||||||
|
const onStatusConfirm = (e: PickerConfirmEventParams) => {
|
||||||
|
const { selectedOptions } = e;
|
||||||
|
searchParams.status = selectedOptions[0]?.value ? Number(selectedOptions[0]?.value) : undefined;
|
||||||
|
showStatusPicker.value = false;
|
||||||
|
// 确保赋值给 statusText 的是字符串类型
|
||||||
|
statusText.value = String(selectedOptions[0]?.text || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理时间选择
|
||||||
|
const onDateConfirm = (values: Date[]) => {
|
||||||
|
const [start, end] = values
|
||||||
|
searchParams.startTime = start.toISOString().split('T')[0]
|
||||||
|
searchParams.endTime = end.toISOString().split('T')[0]
|
||||||
|
dateRangeText.value = `${searchParams.startTime} 至 ${searchParams.endTime}`
|
||||||
|
showDatePicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
list.value = []
|
||||||
|
searchParams.pageNum = 1
|
||||||
|
onLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const handleReset = () => {
|
||||||
|
Object.assign(searchParams, {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
approvalId: undefined,
|
||||||
|
orderId: undefined,
|
||||||
|
goodsId: undefined,
|
||||||
|
status: undefined,
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined
|
||||||
|
})
|
||||||
|
statusText.value = ''
|
||||||
|
dateRangeText.value = ''
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const onLoad = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await getApprovalListApi(searchParams)
|
||||||
|
list.value.push(...data.rows)
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
|
if (list.value.length >= data.total) {
|
||||||
|
finished.value = true
|
||||||
|
} else {
|
||||||
|
searchParams.pageNum++
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取审批列表失败', error)
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.approval-list-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-cell__title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-cell__label {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -13,7 +13,8 @@ const route = useRoute()
|
||||||
const formData = ref<SubmitApprovalRequestData>({
|
const formData = ref<SubmitApprovalRequestData>({
|
||||||
orderGoodsId: Number(route.query.orderGoodsId),
|
orderGoodsId: Number(route.query.orderGoodsId),
|
||||||
returnQuantity: 1,
|
returnQuantity: 1,
|
||||||
returnImages: ''
|
returnImages: '',
|
||||||
|
returnRemark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => route.query.orderGoodsId, (newVal) => {
|
watch(() => route.query.orderGoodsId, (newVal) => {
|
||||||
|
@ -71,10 +72,9 @@ const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListIt
|
||||||
const urls = await Promise.all(uploadPromises)
|
const urls = await Promise.all(uploadPromises)
|
||||||
files.forEach((item, index) => {
|
files.forEach((item, index) => {
|
||||||
item.status = 'done'
|
item.status = 'done'
|
||||||
item.message = ''
|
item.message = '上传成功'
|
||||||
item.url = urls[index].url
|
item.url = urls[index].url
|
||||||
})
|
})
|
||||||
showToast('上传成功')
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showConfirmDialog({
|
showConfirmDialog({
|
||||||
title: '上传失败',
|
title: '上传失败',
|
||||||
|
@ -96,7 +96,13 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
showSuccessToast('提交成功')
|
showSuccessToast('提交成功')
|
||||||
router.push('/order/' + orderId)
|
try {
|
||||||
|
await showConfirmDialog({
|
||||||
|
title: "提交成功",
|
||||||
|
message: `退货申请已提交,等待管理员审核`
|
||||||
|
})
|
||||||
|
} catch (error) { }
|
||||||
|
router.push('/order/' + orderId.value)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交失败:', error)
|
console.error('提交失败:', error)
|
||||||
|
@ -118,6 +124,7 @@ const handleSubmit = async () => {
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
<!-- 移除订单ID和商品ID的输入框 -->
|
<!-- 移除订单ID和商品ID的输入框 -->
|
||||||
<van-field v-model="formData.returnQuantity" label="退还数量" type="number" :min="1" />
|
<van-field v-model="formData.returnQuantity" label="退还数量" type="number" :min="1" />
|
||||||
|
<van-field v-model="formData.returnRemark" label="退货备注" type="textarea" rows="2" autosize />
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<van-cell-group class="upload-section">
|
<van-cell-group class="upload-section">
|
||||||
|
|
|
@ -251,7 +251,6 @@ loadCabinetDetail()
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 100px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 3px;
|
padding-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<div class="login-container">
|
||||||
|
<van-form @submit="handleSubmit">
|
||||||
|
<van-field
|
||||||
|
v-model="form.tel"
|
||||||
|
name="手机号"
|
||||||
|
label="手机号"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
:rules="[{ required: true, message: '请填写手机号' }, { pattern: /^1[3-9]\d{9}$/, message: '手机号格式错误' }]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="form.vcode"
|
||||||
|
center
|
||||||
|
clearable
|
||||||
|
name="验证码"
|
||||||
|
label="验证码"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
:rules="[{ required: true, message: '请填写验证码' }]"
|
||||||
|
>
|
||||||
|
<template #button>
|
||||||
|
<van-button
|
||||||
|
size="small"
|
||||||
|
:disabled="countdown > 0"
|
||||||
|
@click="sendSms"
|
||||||
|
native-type="button"
|
||||||
|
>
|
||||||
|
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
|
||||||
|
</van-button>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<div style="margin: 16px;">
|
||||||
|
<van-button round block type="primary" native-type="submit">
|
||||||
|
立即登录
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { showSuccessToast, showFailToast } from 'vant'
|
||||||
|
import { getTokenApi, sendSmsApi, verifySmsApi } from '@/common/apis/ab98'
|
||||||
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
||||||
|
|
||||||
|
const userStore = useAb98UserStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
tel: '',
|
||||||
|
vcode: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const countdown = ref(0)
|
||||||
|
const loading = ref(true)
|
||||||
|
let timer: number | null = null
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await getTokenApi('ab98_app')
|
||||||
|
if (data.token) {
|
||||||
|
userStore.setToken(data.token)
|
||||||
|
} else {
|
||||||
|
showFailToast('令牌获取失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showFailToast('网络异常,请重试')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sendSms = async () => {
|
||||||
|
try {
|
||||||
|
if (!/^1[3-9]\d{9}$/.test(form.value.tel)) {
|
||||||
|
showFailToast('手机号格式错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await sendSmsApi(userStore.token, form.value.tel)
|
||||||
|
if (data.success) {
|
||||||
|
startCountdown()
|
||||||
|
showSuccessToast('验证码已发送')
|
||||||
|
} else {
|
||||||
|
showFailToast(data.message || '发送失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showFailToast('请求异常,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startCountdown = () => {
|
||||||
|
countdown.value = 60
|
||||||
|
timer = window.setInterval(() => {
|
||||||
|
if (countdown.value <= 0 && timer) {
|
||||||
|
window.clearInterval(timer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
countdown.value--
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await verifySmsApi({
|
||||||
|
token: userStore.token,
|
||||||
|
tel: form.value.tel,
|
||||||
|
vcode: form.value.vcode
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
userStore.setTel(form.value.tel)
|
||||||
|
userStore.setUserInfo(data)
|
||||||
|
userStore.setIsLogin(true)
|
||||||
|
showSuccessToast('登录成功')
|
||||||
|
router.push('/')
|
||||||
|
} else {
|
||||||
|
showFailToast('验证码错误')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
showFailToast('登录失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 30%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,17 +1,22 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useWxStore } from '@/pinia/stores/wx'
|
import { useWxStore } from '@/pinia/stores/wx'
|
||||||
import { computed } from 'vue'
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
import { publicPath } from "@/common/utils/path"
|
import { publicPath } from "@/common/utils/path"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const wxStore = useWxStore()
|
const wxStore = useWxStore()
|
||||||
const balance = computed(() => wxStore.balance)
|
const ab98UserStore = useAb98UserStore()
|
||||||
|
|
||||||
|
const { balance } = storeToRefs(wxStore)
|
||||||
|
const { name: userName, sex: userSex, face_img } = storeToRefs(ab98UserStore)
|
||||||
|
|
||||||
|
const userAvatar = face_img.value ? face_img.value : `${publicPath}img/1.jpg`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div un-py-16px>
|
<div un-py-16px>
|
||||||
<!-- 用户信息区域 -->
|
|
||||||
<van-cell-group class="user-card">
|
<van-cell-group class="user-card">
|
||||||
<van-cell :border="false">
|
<van-cell :border="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
|
@ -20,13 +25,12 @@ const balance = computed(() => wxStore.balance)
|
||||||
round
|
round
|
||||||
width="80"
|
width="80"
|
||||||
height="80"
|
height="80"
|
||||||
:src="`${publicPath}img/1.jpg`"
|
:src="userAvatar"
|
||||||
class="mr-4"
|
class="mr-4"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-lg font-bold mb-2">{{ '' }}</div>
|
<div class="text-lg font-bold mb-2">{{ userName }}</div>
|
||||||
<van-tag type="primary" class="mr-2">{{ 20 }}岁</van-tag>
|
<van-tag type="primary" class="mr-2">{{ userSex }}</van-tag>
|
||||||
<van-tag type="success">{{ '男' }}</van-tag>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -75,6 +79,7 @@ const balance = computed(() => wxStore.balance)
|
||||||
<van-cell-group>
|
<van-cell-group>
|
||||||
<van-cell title="订单列表" is-link @click="router.push('/order-list')" />
|
<van-cell title="订单列表" is-link @click="router.push('/order-list')" />
|
||||||
<van-cell title="柜机管理" is-link @click="router.push('/cabinet')" v-if="wxStore.isCabinetAdmin"/>
|
<van-cell title="柜机管理" is-link @click="router.push('/cabinet')" v-if="wxStore.isCabinetAdmin"/>
|
||||||
|
<van-cell title="审批中心" is-link @click="router.push('/approval/list')" v-if="wxStore.isCabinetAdmin"/>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -20,7 +20,7 @@ async function handleOpenCabinet(orderId: number, orderGoodsId: number) {
|
||||||
try {
|
try {
|
||||||
const result = await openCabinetApi(orderId, orderGoodsId)
|
const result = await openCabinetApi(orderId, orderGoodsId)
|
||||||
if (result.code !== 0) {
|
if (result.code !== 0) {
|
||||||
showFailToast(result.message)
|
showFailToast(result.msg || '开启失败,请稍后重试')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showSuccessToast('柜口已成功开启')
|
showSuccessToast('柜口已成功开启')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCartStore } from "@/pinia/stores/cart"
|
import { useCartStore } from "@/pinia/stores/cart"
|
||||||
import { useWxStore } from "@/pinia/stores/wx"
|
import { useWxStore } from "@/pinia/stores/wx"
|
||||||
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
||||||
import { storeToRefs } from "pinia"
|
import { storeToRefs } from "pinia"
|
||||||
import { showConfirmDialog } from "vant"
|
import { showConfirmDialog } from "vant"
|
||||||
import { submitOrderApi } from "@/common/apis/shop"
|
import { submitOrderApi } from "@/common/apis/shop"
|
||||||
|
@ -13,7 +14,10 @@ const cartStore = useCartStore()
|
||||||
const { cartItems, totalPrice } = storeToRefs(cartStore)
|
const { cartItems, totalPrice } = storeToRefs(cartStore)
|
||||||
|
|
||||||
const wxStore = useWxStore()
|
const wxStore = useWxStore()
|
||||||
const { openid, balance } = storeToRefs(wxStore)
|
const { openid, balance, corpid, userid: qyUserid } = storeToRefs(wxStore)
|
||||||
|
|
||||||
|
const ab98UserStore = useAb98UserStore()
|
||||||
|
const { tel, userid: ab98Userid } = storeToRefs(ab98UserStore)
|
||||||
|
|
||||||
const selectedPayment = ref<'wechat' | 'balance'>('wechat')
|
const selectedPayment = ref<'wechat' | 'balance'>('wechat')
|
||||||
const contact = ref("")
|
const contact = ref("")
|
||||||
|
@ -72,6 +76,11 @@ async function handleSubmit() {
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
|
// 判断用户类型:
|
||||||
|
// 2 - 企业微信用户(corpid存在)
|
||||||
|
// 1 - 汇邦云用户(qyUserid存在)
|
||||||
|
// 0 - 外部用户
|
||||||
|
const isInternal = corpid.value ? 2 : qyUserid.value ? 1 : 0;
|
||||||
const requestData: SubmitOrderRequestData = {
|
const requestData: SubmitOrderRequestData = {
|
||||||
openid: openid.value,
|
openid: openid.value,
|
||||||
userid: wxStore.userid,
|
userid: wxStore.userid,
|
||||||
|
@ -80,7 +89,10 @@ async function handleSubmit() {
|
||||||
goodsId: item.product.id,
|
goodsId: item.product.id,
|
||||||
quantity: item.quantity
|
quantity: item.quantity
|
||||||
})),
|
})),
|
||||||
paymentType: selectedPayment.value
|
paymentType: selectedPayment.value,
|
||||||
|
mobile: tel.value,
|
||||||
|
qyUserid: isInternal === 2 ? qyUserid.value : ab98Userid.value,
|
||||||
|
isInternal: isInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
const { code, data } = await submitOrderApi(requestData)
|
const { code, data } = await submitOrderApi(requestData)
|
||||||
|
@ -155,7 +167,7 @@ async function handleSubmit() {
|
||||||
<van-field label="支付方式" :model-value="selectedPayment" readonly>
|
<van-field label="支付方式" :model-value="selectedPayment" readonly>
|
||||||
<template #input>
|
<template #input>
|
||||||
<van-radio-group v-model="selectedPayment" direction="horizontal">
|
<van-radio-group v-model="selectedPayment" direction="horizontal">
|
||||||
<van-radio name="wechat">
|
<van-radio name="wechat" v-if="!wxStore.corpid">
|
||||||
<van-icon name="wechat" class="method-icon" />
|
<van-icon name="wechat" class="method-icon" />
|
||||||
微信支付
|
微信支付
|
||||||
</van-radio>
|
</van-radio>
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
import { pinia } from "@/pinia"
|
||||||
|
import { LoginData } from "@/common/apis/ab98/type"
|
||||||
|
|
||||||
|
// 本地存储键名常量
|
||||||
|
const STORAGE_KEYS = {
|
||||||
|
FACE: 'ab98_face',
|
||||||
|
SEX: 'ab98_sex',
|
||||||
|
NAME: 'ab98_name',
|
||||||
|
USERID: 'ab98_userid',
|
||||||
|
REGISTERED: 'ab98_registered',
|
||||||
|
TEL: 'ab98_tel',
|
||||||
|
TOKEN: 'ab98_token'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AB98用户信息存储
|
||||||
|
* @description 管理AB98系统用户相关状态信息
|
||||||
|
*/
|
||||||
|
export const useAb98UserStore = defineStore("ab98User", () => {
|
||||||
|
// 用户面部图像URL
|
||||||
|
const storedFace = localStorage.getItem(STORAGE_KEYS.FACE)
|
||||||
|
const face_img = ref<string>(storedFace ? decodeURIComponent(storedFace) : '')
|
||||||
|
// 用户性别(男/女)
|
||||||
|
const storedSex = localStorage.getItem(STORAGE_KEYS.SEX)
|
||||||
|
const sex = ref<string>(storedSex ? decodeURIComponent(storedSex) : '')
|
||||||
|
// 用户真实姓名
|
||||||
|
const storedName = localStorage.getItem(STORAGE_KEYS.NAME)
|
||||||
|
const name = ref<string>(storedName ? decodeURIComponent(storedName) : '')
|
||||||
|
// AB98系统用户唯一标识
|
||||||
|
const storedUserId = localStorage.getItem(STORAGE_KEYS.USERID)
|
||||||
|
const userid = ref<string>(storedUserId ? decodeURIComponent(storedUserId) : "")
|
||||||
|
// 是否已完成注册流程
|
||||||
|
const registered = ref<boolean>(JSON.parse(localStorage.getItem(STORAGE_KEYS.REGISTERED) || "false"))
|
||||||
|
// 用户绑定手机号
|
||||||
|
const storedTel = localStorage.getItem(STORAGE_KEYS.TEL)
|
||||||
|
const tel = ref<string>(storedTel ? decodeURIComponent(storedTel) : "")
|
||||||
|
// 用户认证令牌
|
||||||
|
const storedToken = localStorage.getItem(STORAGE_KEYS.TOKEN)
|
||||||
|
const token = ref<string>(storedToken ? decodeURIComponent(storedToken) : "")
|
||||||
|
// 用户登录状态
|
||||||
|
const isLogin = ref<boolean>(false);
|
||||||
|
isLogin.value = tel.value ? true : false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户基本信息
|
||||||
|
* @param data - 登录接口返回的用户数据
|
||||||
|
*/
|
||||||
|
const setUserInfo = (data: LoginData) => {
|
||||||
|
face_img.value = data.face_img
|
||||||
|
localStorage.setItem(STORAGE_KEYS.FACE, encodeURIComponent(data.face_img))
|
||||||
|
sex.value = data.sex
|
||||||
|
localStorage.setItem(STORAGE_KEYS.SEX, encodeURIComponent(data.sex))
|
||||||
|
name.value = data.name
|
||||||
|
localStorage.setItem(STORAGE_KEYS.NAME, encodeURIComponent(data.name))
|
||||||
|
userid.value = data.userid
|
||||||
|
localStorage.setItem(STORAGE_KEYS.USERID, encodeURIComponent(data.userid))
|
||||||
|
registered.value = data.registered
|
||||||
|
localStorage.setItem(STORAGE_KEYS.REGISTERED, JSON.stringify(data.registered))
|
||||||
|
tel.value = data.tel
|
||||||
|
localStorage.setItem(STORAGE_KEYS.TEL, encodeURIComponent(data.tel))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空用户敏感信息
|
||||||
|
* @description 用于用户登出或会话过期时
|
||||||
|
*/
|
||||||
|
const clearUserInfo = () => {
|
||||||
|
face_img.value = ""
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.FACE)
|
||||||
|
sex.value = ""
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.SEX)
|
||||||
|
name.value = ""
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.NAME)
|
||||||
|
userid.value = ""
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.USERID)
|
||||||
|
registered.value = false
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.REGISTERED)
|
||||||
|
tel.value = ""
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.TEL)
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.TOKEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置认证令牌
|
||||||
|
* @param value - JWT格式的认证令牌
|
||||||
|
*/
|
||||||
|
const setToken = (value: string) => {
|
||||||
|
localStorage.setItem(STORAGE_KEYS.TOKEN, encodeURIComponent(value))
|
||||||
|
token.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTel = (value: string) => {
|
||||||
|
localStorage.setItem(STORAGE_KEYS.TEL, btoa(value))
|
||||||
|
tel.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const setIsLogin = (value: boolean) => {
|
||||||
|
isLogin.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
face_img,
|
||||||
|
sex,
|
||||||
|
name,
|
||||||
|
userid,
|
||||||
|
registered,
|
||||||
|
tel,
|
||||||
|
token,
|
||||||
|
isLogin,
|
||||||
|
setUserInfo,
|
||||||
|
setToken,
|
||||||
|
setTel,
|
||||||
|
setIsLogin,
|
||||||
|
clearUserInfo
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 在非setup上下文或SSR场景中使用store
|
||||||
|
*/
|
||||||
|
export function useAb98UserStoreOutside() {
|
||||||
|
return useAb98UserStore(pinia)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { ReturnApprovalEntity } from '@/common/apis/approval/type'
|
||||||
|
|
||||||
|
export interface ApprovalDetail extends ReturnApprovalEntity {
|
||||||
|
goodsName: string
|
||||||
|
coverImg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useApprovalStore = defineStore('approval', () => {
|
||||||
|
const currentApproval = ref<ApprovalDetail | null>(null)
|
||||||
|
|
||||||
|
const setCurrentApproval = (approval: ApprovalDetail) => {
|
||||||
|
currentApproval.value = approval
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentApproval,
|
||||||
|
setCurrentApproval
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useApprovalStoreOutside() {
|
||||||
|
return useApprovalStore()
|
||||||
|
}
|
|
@ -5,18 +5,34 @@ import { isWhiteList } from "@/router/whitelist"
|
||||||
import { useTitle } from "@@/composables/useTitle"
|
import { useTitle } from "@@/composables/useTitle"
|
||||||
import { getToken } from "@@/utils/cache/cookies"
|
import { getToken } from "@@/utils/cache/cookies"
|
||||||
import NProgress from "nprogress"
|
import NProgress from "nprogress"
|
||||||
|
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
|
||||||
|
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
const { setTitle } = useTitle()
|
const { setTitle } = useTitle()
|
||||||
|
|
||||||
const LOGIN_PATH = "/login"
|
const LOGIN_PATH = "/ab98"
|
||||||
|
|
||||||
export function registerNavigationGuard(router: Router) {
|
export function registerNavigationGuard(router: Router) {
|
||||||
// 全局前置守卫
|
// 全局前置守卫
|
||||||
router.beforeEach((to, _from) => {
|
router.beforeEach((to, _from) => {
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
|
|
||||||
|
// 企业微信登录
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const corpid = urlParams.get('corpid') || undefined;
|
||||||
|
if (corpid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = useAb98UserStore()
|
||||||
|
if (!userStore.isLogin) {
|
||||||
|
// 如果在免登录的白名单中,则直接进入
|
||||||
|
if (isWhiteList(to)) return true
|
||||||
|
// 其他没有访问权限的页面将被重定向到登录页面
|
||||||
|
return LOGIN_PATH
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
// const userStore = useUserStore()
|
// const userStore = useUserStore()
|
||||||
// // 如果没有登录
|
// // 如果没有登录
|
||||||
|
|
|
@ -34,6 +34,11 @@ export const routes: RouteRecordRaw[] = [
|
||||||
component: () => import('@/pages/approval/submit.vue'),
|
component: () => import('@/pages/approval/submit.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/approval/handle/:approvalId',
|
||||||
|
component: () => import('@/pages/approval/handle.vue'),
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/order-success',
|
path: '/order-success',
|
||||||
name: 'OrderSuccess',
|
name: 'OrderSuccess',
|
||||||
|
@ -75,6 +80,25 @@ export const routes: RouteRecordRaw[] = [
|
||||||
meta: {
|
meta: {
|
||||||
title: '柜机管理',
|
title: '柜机管理',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
|
layout: {
|
||||||
|
navBar: {
|
||||||
|
showNavBar: false,
|
||||||
|
showLeftArrow: false
|
||||||
|
},
|
||||||
|
tabbar: {
|
||||||
|
showTabbar: true,
|
||||||
|
icon: "home-o"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/approval/list',
|
||||||
|
component: () => import('@/pages/approval/list.vue'),
|
||||||
|
name: "Approval",
|
||||||
|
meta: {
|
||||||
|
title: '审批中心',
|
||||||
|
keepAlive: true,
|
||||||
layout: {
|
layout: {
|
||||||
navBar: {
|
navBar: {
|
||||||
showNavBar: false,
|
showNavBar: false,
|
||||||
|
@ -123,6 +147,14 @@ export const routes: RouteRecordRaw[] = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/ab98",
|
||||||
|
component: () => import("@/pages/login/Ab98Login.vue"),
|
||||||
|
name: "Ab98Login",
|
||||||
|
meta: {
|
||||||
|
title: "登录"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -132,6 +164,11 @@ export const routes: RouteRecordRaw[] = [
|
||||||
component: () => import('@/pages/approval/submit.vue'),
|
component: () => import('@/pages/approval/submit.vue'),
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/approval/handle/:approvalId',
|
||||||
|
component: () => import('@/pages/approval/handle.vue'),
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/order-success',
|
path: '/order-success',
|
||||||
name: 'OrderSuccess',
|
name: 'OrderSuccess',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { RouteLocationNormalizedGeneric, RouteRecordNameGeneric } from "vue-router"
|
import type { RouteLocationNormalizedGeneric, RouteRecordNameGeneric } from "vue-router"
|
||||||
|
|
||||||
/** 免登录白名单(匹配路由 path) */
|
/** 免登录白名单(匹配路由 path) */
|
||||||
const whiteListByPath: string[] = ["/login"]
|
const whiteListByPath: string[] = ["/login", "/ab98"]
|
||||||
|
|
||||||
/** 免登录白名单(匹配路由 name) */
|
/** 免登录白名单(匹配路由 name) */
|
||||||
const whiteListByName: RouteRecordNameGeneric[] = []
|
const whiteListByName: RouteRecordNameGeneric[] = []
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
interface ApiResponseData<T> {
|
interface ApiResponseData<T> {
|
||||||
code: number
|
code: number
|
||||||
data: T
|
data: T
|
||||||
message: string
|
msg: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiResponseMsgData<T> {
|
interface ApiResponseMsgData<T> {
|
||||||
code: number
|
code: number
|
||||||
data: T
|
data: T
|
||||||
message: string
|
msg: string
|
||||||
}
|
}
|
|
@ -17,12 +17,16 @@ declare module 'vue' {
|
||||||
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
|
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
|
||||||
VanDivider: typeof import('vant/es')['Divider']
|
VanDivider: typeof import('vant/es')['Divider']
|
||||||
VanField: typeof import('vant/es')['Field']
|
VanField: typeof import('vant/es')['Field']
|
||||||
|
VanForm: typeof import('vant/es')['Form']
|
||||||
VanGrid: typeof import('vant/es')['Grid']
|
VanGrid: typeof import('vant/es')['Grid']
|
||||||
VanGridItem: typeof import('vant/es')['GridItem']
|
VanGridItem: typeof import('vant/es')['GridItem']
|
||||||
VanIcon: typeof import('vant/es')['Icon']
|
VanIcon: typeof import('vant/es')['Icon']
|
||||||
VanImage: typeof import('vant/es')['Image']
|
VanImage: typeof import('vant/es')['Image']
|
||||||
|
VanList: typeof import('vant/es')['List']
|
||||||
VanLoading: typeof import('vant/es')['Loading']
|
VanLoading: typeof import('vant/es')['Loading']
|
||||||
VanNavBar: typeof import('vant/es')['NavBar']
|
VanNavBar: typeof import('vant/es')['NavBar']
|
||||||
|
VanPicker: typeof import('vant/es')['Picker']
|
||||||
|
VanPopup: typeof import('vant/es')['Popup']
|
||||||
VanRadio: typeof import('vant/es')['Radio']
|
VanRadio: typeof import('vant/es')['Radio']
|
||||||
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
||||||
VanSidebar: typeof import('vant/es')['Sidebar']
|
VanSidebar: typeof import('vant/es')['Sidebar']
|
||||||
|
|
Loading…
Reference in New Issue