Files
yxe-mp/pages/audit/components/AuditMovementItem.vue
T
2026-09-29 15:17:13 +08:00

345 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<view class="record">
<view>
<view class="record-meta" @click="goDetail" v-if="item.taskTitle">任务:{{ item.taskTitle }}</view>
<view class="record-meta" @click="goDetail" v-if="item.chainName && item.chainName !== '-'">连锁:{{ item.chainName }}</view>
<view class="record-meta" @click="goDetail" v-if="item.merchantName">门店:{{ item.merchantName }}</view>
<view class="record-meta" @click="goDetail">提交人:{{ item.owner }} · {{ item.submitTime }}</view>
<view v-if="item.imageList && item.imageList.length" class="record-media">
<view v-if="item.imageList && item.imageList.length" class="media-images">
<image
v-for="(img, idx) in item.imageList"
:key="idx"
class="media-image"
:src="resolveMediaUrl(img)"
mode="aspectFill"
@click="emitPreview(resolveMediaUrl(img), buildPreviewList(item.imageList))"
/>
</view>
</view>
</view>
<view class="record-actions" v-if="item.status === 1">
<view class="flex-1 mx-15">
<view class="chip w-90p" @tap="$emit('approve', item)">通过</view>
</view>
<view class="flex-1 mx-15">
<view class="chip w-90p danger-chip" @tap="$emit('reject', item)">驳回</view>
</view>
<view class="flex-1 mx-15">
<view class="chip w-90p" @tap="openCopyPopup">抄送</view>
</view>
<view class="flex-1 mx-15">
<view class="chip w-90p" @tap="goDetail">查看详情</view>
</view>
</view>
<view class="record-actions" v-if="item.status === 3">
<view class="flex-1 mx-15">
<view class="chip w-90p" @tap="openCopyPopup">抄送</view>
</view>
<view class="flex-1 mx-15">
<view class="chip w-90p" @tap="goDetail">查看详情</view>
</view>
</view>
</view>
<view v-if="showCopyPopup" class="copy-popup-mask" @tap="closeCopyPopup" @touchmove.stop.prevent></view>
<view v-if="showCopyPopup" class="copy-popup card" @tap.stop>
<view class="copy-popup-head">
<view class="section-title">抄送</view>
<view class="close-btn" @tap="closeCopyPopup">关闭</view>
</view>
<view class="copy-search-row">
<input
v-model="copyKeyword"
class="copy-search-input"
type="text"
placeholder="输入姓名/手机号搜索用户"
@confirm="searchCopyUsers"
/>
<view class="copy-search-btn" @tap="searchCopyUsers">搜索</view>
</view>
<view v-if="copySearching" class="copy-loading">搜索中...</view>
<scroll-view v-else class="copy-user-list" scroll-y>
<view
v-for="user in copyUsers"
:key="`copy-user-${user.id}`"
:class="['copy-user-item', selectedCopyUserId === user.id ? 'copy-user-item-active' : '']"
@tap="selectedCopyUserId = user.id"
>
<text class="copy-user-name">{{ user.label }}</text>
<text class="copy-user-phone">{{ user.phone || '' }}</text>
</view>
<view v-if="copyUsers.length === 0" class="copy-empty">暂无用户</view>
</scroll-view>
<view class="primary-btn copy-submit" @tap="submitCopy">确认抄送</view>
</view>
</view>
</template>
<script>
import {userSearchReq} from "@/api/user";
import {copyUserTaskReq} from "@/api/task";
export default {
name: 'AuditMovementItem',
props: {
item: {
type: Object,
required: true
}
},
data() {
return {
showCopyPopup: false,
copyKeyword: '',
copyUsers: [],
copySearching: false,
selectedCopyUserId: ''
}
},
methods: {
emitPreview(url, list) {
this.$emit('preview', url, list)
},
resolveMediaUrl(media) {
if (!media) return ''
if (typeof media === 'string') return media
return media.url || media.path || media.image || ''
},
buildPreviewList(list) {
const items = Array.isArray(list) ? list : []
return items.map(item => this.resolveMediaUrl(item)).filter(Boolean)
},
goDetail() {
const taskId = this.item.taskId
if (taskId === '' || taskId === null || taskId === undefined) {
uni.showToast({
title: '任务ID缺失',
icon: 'none'
})
return
}
const title = this.item.merchantName || this.item.taskTitle || ''
const reward = this.item.taskReward !== undefined && this.item.taskReward !== null ? this.item.taskReward : ''
const target = this.item.taskTarget || ''
let url = `/pages/task/detail?id=${taskId}`
url += `&fromAudit=1`
if (this.item.id !== undefined && this.item.id !== null && this.item.id !== '') {
url += `&auditId=${encodeURIComponent(String(this.item.id))}`
}
if (this.item.taskId !== undefined && this.item.taskId !== null && this.item.taskId !== '') {
url += `&auditTaskId=${encodeURIComponent(String(this.item.taskId))}`
}
if (this.item.userId !== undefined && this.item.userId !== null && this.item.userId !== '') {
url += `&auditUserId=${encodeURIComponent(String(this.item.userId))}`
}
if (this.item.status !== undefined && this.item.status !== null && this.item.status !== '') {
url += `&auditStatus=${encodeURIComponent(String(this.item.status))}`
}
if (title) url += `&title=${encodeURIComponent(title)}`
if (reward !== '') url += `&reward=${encodeURIComponent(String(reward))}`
if (target) url += `&target=${encodeURIComponent(target)}`
uni.navigateTo({url})
},
unwrapList(data) {
if (Array.isArray(data)) return data
if (!data || typeof data !== 'object') return []
const keys = ['list', 'records', 'rows', 'items', 'data', 'result']
for (let i = 0; i < keys.length; i++) {
if (Array.isArray(data[keys[i]])) return data[keys[i]]
}
return []
},
normalizeUser(item) {
const id = item.id ?? item.userId ?? item.uid
const nickname = item.nickname || item.nickName || ''
const realName = item.realName || item.real_name || item.name || ''
const phone = item.phone || item.mobile || item.tel || ''
const label = realName || nickname || phone || `用户${id || ''}`
return {
id,
label,
phone
}
},
openCopyPopup() {
this.copyKeyword = ''
this.copyUsers = []
this.selectedCopyUserId = ''
this.showCopyPopup = true
},
closeCopyPopup() {
this.showCopyPopup = false
},
async searchCopyUsers() {
const keyword = (this.copyKeyword || '').trim()
if (!keyword) {
uni.showToast({
title: '请输入keyword',
icon: 'none'
})
return
}
this.copySearching = true
try {
const {data} = await userSearchReq({
keywords: keyword
})
const list = this.unwrapList(data)
this.copyUsers = list
.map(item => this.normalizeUser(item))
.filter(item => item.id !== undefined && item.id !== null && String(item.id).trim() !== '')
this.selectedCopyUserId = this.copyUsers[0] ? this.copyUsers[0].id : ''
} catch (error) {
this.copyUsers = []
uni.showToast({
title: '搜索失败',
icon: 'none'
})
} finally {
this.copySearching = false
}
},
async submitCopy() {
const targetUserId = this.selectedCopyUserId
if (targetUserId === '' || targetUserId === null || targetUserId === undefined) {
uni.showToast({
title: '请选择用户',
icon: 'none'
})
return
}
try {
await copyUserTaskReq({
id: this.item.id,
taskId: this.item.taskId,
sourceUserId: this.item.userId,
userId: targetUserId,
isCopy: 1
})
uni.showToast({
title: '抄送成功',
icon: 'success'
})
this.closeCopyPopup()
this.$emit('copied')
} catch (error) {
uni.showToast({
title: '抄送失败',
icon: 'none'
})
}
}
}
}
</script>
<style lang="scss" scoped>
@import './record.scss';
.copy-popup-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 30;
}
.copy-popup {
position: fixed;
left: 50%;
bottom: 40rpx;
transform: translateX(-50%);
width: 88%;
max-height: 70vh;
overflow: auto;
z-index: 40;
}
.copy-popup-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.close-btn {
color: var(--text-sub);
font-size: 24rpx;
}
.copy-search-row {
display: flex;
gap: 10rpx;
align-items: center;
}
.copy-search-input {
flex: 1;
height: 70rpx;
line-height: 70rpx;
border-radius: 12rpx;
background: #f3f4f6;
padding: 0 16rpx;
font-size: 26rpx;
}
.copy-search-btn {
min-width: 110rpx;
height: 70rpx;
border-radius: 12rpx;
background: var(--brand-primary);
color: #ffffff;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
.copy-loading,
.copy-empty {
margin-top: 14rpx;
font-size: 24rpx;
color: var(--text-sub);
}
.copy-user-list {
margin-top: 14rpx;
max-height: 420rpx;
}
.copy-user-item {
padding: 12rpx;
border-radius: 12rpx;
background: #f9fafb;
margin-bottom: 10rpx;
border: 1rpx solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
}
.copy-user-item-active {
border-color: var(--brand-primary);
background: #ecfdf5;
}
.copy-user-name {
font-size: 26rpx;
color: var(--text-main);
}
.copy-user-phone {
font-size: 22rpx;
color: var(--text-sub);
}
.copy-submit {
margin-top: 14rpx;
}
</style>