web工单处理

This commit is contained in:
2025-12-28 13:18:28 +08:00
parent 1148e3368d
commit 7de30b1b36
12 changed files with 1209 additions and 95 deletions

View File

@@ -164,6 +164,12 @@
<view class="title-bar"></view>
<text class="title-text">处理记录</text>
</view>
<!-- 处理人记录处理过程 -->
<view v-if="isProcessor" class="add-process-section">
<button class="add-process-btn" type="primary" size="mini" @tap="navigateToAddProcess">
添加处理记录
</button>
</view>
<view class="timeline">
<view class="timeline-item" v-for="(item, index) in processList" :key="index">
<view class="timeline-dot" :class="getTimelineDotClass(item.status)"></view>
@@ -198,13 +204,139 @@
<view class="action-button primary" v-if="mode === 'create'" @tap="submitWorkcase">
<text class="button-text">提交工单</text>
</view>
<view class="action-button primary" v-if="mode === 'view' && workcase.status === 'pending'" @tap="handleAssign">
<!-- 来客视角:只能撤销自己创建的工单 -->
<view
class="action-button danger"
v-if="mode !== 'create' && isCreator && workcase.status !== 'done'"
@tap="handleRevoke"
>
<text class="button-text">撤销工单</text>
</view>
<!-- 员工视角:可以指派、转派、完成工单 -->
<view
class="action-button warning"
v-if="mode !== 'create' && !isCreator && workcase.status === 'pending'"
@tap="handleAssign"
>
<text class="button-text">指派工程师</text>
</view>
<view class="action-button success" v-if="mode === 'view' && workcase.status === 'processing'" @tap="handleComplete">
<view
class="action-button warning"
v-if="mode !== 'create' && !isCreator && workcase.status === 'processing'"
@tap="handleRedeployBtn"
>
<text class="button-text">转派工程师</text>
</view>
<view
class="action-button success"
v-if="mode !== 'create' && !isCreator && workcase.status === 'processing'"
@tap="handleComplete"
>
<text class="button-text">完成工单</text>
</view>
</view>
<!-- 添加处理记录弹窗(使用 v-if 控制显示) -->
<view v-if="showAddProcessDialog" class="process-modal-overlay" @tap="closeAddProcessDialog">
<view class="process-modal" @tap.stop>
<view class="modal-header">
<text class="modal-title">添加处理记录</text>
<view class="modal-close" @tap="closeAddProcessDialog">
<text>×</text>
</view>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">处理内容</text>
<textarea
class="form-textarea"
v-model="processForm.message"
placeholder="请详细描述处理过程、发现的问题或解决方案..."
maxlength="500"
/>
<text class="char-count">{{ processForm.message.length }}/500</text>
</view>
<view class="form-item">
<text class="form-label">附件(可选)</text>
<view class="file-upload-section">
<view class="file-list">
<view class="file-item" v-for="(file, index) in processForm.files" :key="index">
<text class="file-name">{{ file.name }}</text>
<view class="file-delete" @tap="deleteProcessFile(index)">
<text>×</text>
</view>
</view>
</view>
<button
class="upload-btn"
size="mini"
@tap="chooseProcessFile"
v-if="processForm.files.length < 5"
>
选择文件
</button>
<text class="upload-tip">最多上传5个文件</text>
</view>
</view>
</view>
<view class="modal-footer">
<button class="modal-btn" @tap="closeAddProcessDialog">取消</button>
<button class="modal-btn primary" type="primary" @tap="submitProcessRecord" :loading="submittingProcess">
提交
</button>
</view>
</view>
</view>
<!-- 指派/转派工程师弹窗 -->
<view v-if="showAssignDialog" class="process-modal-overlay" @tap="closeAssignDialog">
<view class="process-modal" @tap.stop>
<view class="modal-header">
<text class="modal-title">{{ assignDialogTitle }}</text>
<view class="modal-close" @tap="closeAssignDialog">
<text>×</text>
</view>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">选择工程师</text>
<picker
class="form-picker"
:value="selectedEngineerIndex"
:range="availableEngineers"
range-key="username"
@change="onEngineerChange"
>
<view class="picker-content">
<text class="picker-text" v-if="assignForm.processorName">{{ assignForm.processorName }}</text>
<text class="picker-text placeholder" v-else>请选择工程师</text>
<text class="picker-arrow">></text>
</view>
</picker>
<view v-if="loadingEngineers" class="loading-tip">
<text>加载中...</text>
</view>
</view>
<view class="form-item">
<text class="form-label">备注说明(可选)</text>
<textarea
class="form-textarea"
v-model="assignForm.message"
placeholder="请输入指派/转派说明..."
maxlength="200"
/>
</view>
</view>
<view class="modal-footer">
<button class="modal-btn" @tap="closeAssignDialog">取消</button>
<button class="modal-btn primary" type="primary" @tap="submitAssign" :loading="submittingAssign">
确定
</button>
</view>
</view>
</view>
</view>
<!-- #ifdef APP -->
</scroll-view>
@@ -212,10 +344,11 @@
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'
import { ref, onMounted, reactive, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import type { TbWorkcaseDTO, TbWorkcaseProcessDTO } from '@/types/workcase'
import { workcaseAPI, fileAPI } from '@/api'
import type { CustomerServiceVO } from '@/types/workcase/chatRoom'
import { workcaseAPI, fileAPI, workcaseChatAPI } from '@/api'
// 响应式数据
const headerPaddingTop = ref<number>(44)
@@ -228,6 +361,7 @@ const faultTypes = ref<string[]>(['电气系统故障', '机械故障', '控制
const typeIndex = ref<number>(0)
const emergencies = ref<string[]>(['普通', '紧急'])
const emergencyIndex = ref<number>(0)
const userId = JSON.parse(uni.getStorageSync('loginDomain')).userInfo.userId
// 工单数据
const workcase = reactive<TbWorkcaseDTO>({})
@@ -235,6 +369,38 @@ const workcase = reactive<TbWorkcaseDTO>({})
// 处理记录
const processList = reactive<TbWorkcaseProcessDTO[]>([])
// 判断是否是处理人
const isProcessor = computed(() => {
return workcase.processor === userId
})
// 判断是否是创建人
const isCreator = computed(() => {
return workcase.creator === userId
})
// 添加处理记录相关
const showAddProcessDialog = ref(false)
const submittingProcess = ref(false)
const processForm = reactive({
message: '',
files: [] as Array<{ name: string; fileId: string }>
})
// 指派/转派相关
const showAssignDialog = ref(false)
const assignDialogTitle = ref('指派工程师')
const assignAction = ref<'assign' | 'redeploy'>('assign')
const submittingAssign = ref(false)
const loadingEngineers = ref(false)
const availableEngineers = ref<CustomerServiceVO[]>([])
const selectedEngineerIndex = ref(-1)
const assignForm = reactive({
processor: '',
processorName: '',
message: ''
})
// 获取图片 URL通过 fileId
function getImageUrl(fileId: string): string {
// 如果已经是完整 URL直接返回
@@ -431,11 +597,9 @@ function handleViewChat() {
// 指派工程师
function handleAssign() {
uni.showToast({
title: '指派工程师',
icon: 'none'
})
// TODO: 实现指派逻辑
assignAction.value = 'assign'
assignDialogTitle.value = '指派工程师'
openAssignDialog()
}
// 完成工单
@@ -443,18 +607,131 @@ function handleComplete() {
uni.showModal({
title: '完成确认',
content: '确认完成该工单?',
success: (res) => {
success: async (res) => {
if (res.confirm) {
// TODO: 调用 API 完成工单
uni.showToast({
title: '工单已完成',
icon: 'success'
})
try {
const params: TbWorkcaseProcessDTO = {
workcaseId: workcase.workcaseId,
action: 'finish',
message: '工单完成'
}
const result = await workcaseAPI.createWorkcaseProcess(params)
if (result.success) {
uni.showToast({ title: '工单已完成', icon: 'success' })
// 重新加载工单详情
if (workcase.workcaseId) {
await loadWorkcaseDetail(workcase.workcaseId)
}
} else {
uni.showToast({ title: result.message || '操作失败', icon: 'none' })
}
} catch (error) {
console.error('完成工单失败:', error)
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
}
})
}
// 转派工程师
function handleRedeploy() {
assignAction.value = 'redeploy'
assignDialogTitle.value = '转派工程师'
openAssignDialog()
}
// 打开指派/转派弹窗
async function openAssignDialog() {
assignForm.processor = ''
assignForm.processorName = ''
assignForm.message = ''
selectedEngineerIndex.value = -1
showAssignDialog.value = true
await loadAvailableEngineers()
}
// 关闭指派/转派弹窗
function closeAssignDialog() {
showAssignDialog.value = false
}
// 加载可用工程师列表(排除当前处理人)
async function loadAvailableEngineers() {
loadingEngineers.value = true
try {
const res = await workcaseChatAPI.getCustomerServicePage({
filter: {},
pageParam: { page: 1, pageSize: 100 }
})
if (res.success && res.dataList) {
// 排除当前处理人
availableEngineers.value = res.dataList.filter(
(engineer: CustomerServiceVO) => engineer.userId !== workcase.processor
)
}
} catch (error) {
console.error('加载工程师列表失败:', error)
uni.showToast({ title: '加载工程师列表失败', icon: 'none' })
} finally {
loadingEngineers.value = false
}
}
// 选择工程师
function onEngineerChange(e: any) {
const index = e.detail.value
selectedEngineerIndex.value = index
if (index >= 0 && index < availableEngineers.value.length) {
const engineer = availableEngineers.value[index]
assignForm.processor = engineer.userId || ''
assignForm.processorName = engineer.username || ''
}
}
// 提交指派/转派
async function submitAssign() {
if (!assignForm.processor) {
uni.showToast({ title: '请选择工程师', icon: 'none' })
return
}
if (!workcase.workcaseId) {
uni.showToast({ title: '工单ID不存在', icon: 'none' })
return
}
submittingAssign.value = true
try {
const params: TbWorkcaseProcessDTO = {
workcaseId: workcase.workcaseId,
action: assignAction.value,
processor: assignForm.processor,
message: assignForm.message || (assignAction.value === 'assign' ? '工单指派' : '工单转派')
}
const res = await workcaseAPI.createWorkcaseProcess(params)
if (res.success) {
uni.showToast({
title: assignAction.value === 'assign' ? '指派成功' : '转派成功',
icon: 'success'
})
closeAssignDialog()
// 重新加载工单详情
if (workcase.workcaseId) {
await loadWorkcaseDetail(workcase.workcaseId)
}
} else {
uni.showToast({ title: res.message || '操作失败', icon: 'none' })
}
} catch (error) {
console.error('指派/转派失败:', error)
uni.showToast({ title: '操作失败', icon: 'none' })
} finally {
submittingAssign.value = false
}
}
// 表单选择器事件
function onTypeChange(e: any) {
typeIndex.value = e.detail.value
@@ -649,6 +926,127 @@ async function submitWorkcase() {
}
}
// 撤销工单
function handleRevoke() {
uni.showModal({
title: '撤销确认',
content: '确认撤销该工单?撤销后无法恢复',
success: (res) => {
if (res.confirm) {
// TODO: 调用 API 撤销工单
uni.showToast({
title: '工单已撤销',
icon: 'success'
})
}
}
})
}
// 转派工程师
function handleRedeployBtn() {
handleRedeploy()
}
// 导航到添加处理记录(或直接显示弹窗)
function navigateToAddProcess() {
showAddProcessDialog.value = true
}
// 关闭添加处理记录弹窗
function closeAddProcessDialog() {
showAddProcessDialog.value = false
}
// 选择处理记录附件
async function chooseProcessFile() {
uni.chooseFile({
count: 5 - processForm.files.length,
extension: ['.jpg', '.jpeg', '.png', '.pdf', '.doc', '.docx', '.xls', '.xlsx'],
success: async (res) => {
uni.showLoading({ title: '上传中...' })
try {
const uploadPromises = res.tempFiles.map(async (file) => {
const result = await fileAPI.uploadFile(file.path, {
module: 'workcase',
optsn: workcase.workcaseId || 'temp'
})
if (result.success && result.data?.fileId) {
return {
name: file.name,
fileId: result.data.fileId
}
}
return null
})
const results = await Promise.all(uploadPromises)
results.forEach(result => {
if (result) {
processForm.files.push(result)
}
})
uni.hideLoading()
uni.showToast({ title: '上传成功', icon: 'success' })
} catch (error) {
uni.hideLoading()
console.error('上传文件失败:', error)
uni.showToast({ title: '上传失败', icon: 'none' })
}
}
})
}
// 删除处理记录附件
function deleteProcessFile(index: number) {
processForm.files.splice(index, 1)
}
// 提交处理记录
async function submitProcessRecord() {
if (!processForm.message.trim()) {
uni.showToast({ title: '请输入处理内容', icon: 'none' })
return
}
if (!workcase.workcaseId) {
uni.showToast({ title: '工单ID不存在', icon: 'none' })
return
}
submittingProcess.value = true
try {
const fileIds = processForm.files.map(f => f.fileId).join(',')
const params: TbWorkcaseProcessDTO = {
workcaseId: workcase.workcaseId,
action: 'info',
message: processForm.message,
files: fileIds || undefined
}
const res = await workcaseAPI.addWorkcaseProcess(params)
if (res.success) {
uni.showToast({ title: '处理记录添加成功', icon: 'success' })
closeAddProcessDialog()
// 重置表单
processForm.message = ''
processForm.files = []
// 重新加载处理记录
if (workcase.workcaseId) {
await loadProcessList(workcase.workcaseId)
}
} else {
uni.showToast({ title: res.message || '添加失败', icon: 'none' })
}
} catch (error) {
console.error('添加处理记录失败:', error)
uni.showToast({ title: '添加失败', icon: 'none' })
} finally {
submittingProcess.value = false
}
}
// 返回上一页
function goBack() {
uni.navigateBack()