聊天室url修正

This commit is contained in:
2026-01-09 16:40:28 +08:00
parent bfd06dd8f6
commit f4b7337210
18 changed files with 360 additions and 124 deletions

View File

@@ -263,8 +263,8 @@ CREATE TABLE workcase.tb_workcase(
type VARCHAR(50) NOT NULL, -- 故障类型
device VARCHAR(50) DEFAULT NULL, -- 设备名称
device_code VARCHAR(50) DEFAULT NULL, -- 设备代码
device_name_plate VARCHAR(50) DEFAULT NULL, -- 设备名称牌
device_name_plate_img VARCHAR(50) NOT NULL, -- 设备名称牌图片
device_name_plate VARCHAR(50) NOT NULL, -- 设备名称牌
device_name_plate_img VARCHAR(50) DEFAULT NULL, -- 设备名称牌图片
address VARCHAR(1000) DEFAULT NULL, -- 现场地址
description VARCHAR(1000) DEFAULT NULL, -- 故障描述
imgs VARCHAR(50)[] DEFAULT '{}', -- 工单图片id

View File

@@ -120,4 +120,6 @@ public class LoginParam implements Serializable {
*/
private String iv;
private Boolean mockMode;
}

View File

@@ -224,7 +224,7 @@ public class AesEncryptUtil {
public static void main(String[] args) {
AesEncryptUtil aesEncryptUtil = new AesEncryptUtil("MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=");
String phone = "17857100376";
String phone = "15170037929";
// 测试加密(每次都不同,不能用于查询)
String encryptedPhone1 = aesEncryptUtil.encryptPhone(phone);

View File

@@ -127,64 +127,94 @@ public class GuestController {
// ========================= 微信小程序用户识别登录 =========================
/**
* 模拟用户数据用于测试没有手机号校验appid时使用
* 格式:手机号 -> [姓名, 微信ID, 角色]
*/
private static final java.util.Map<String, String[]> MOCK_USERS = new java.util.HashMap<String, String[]>() {{
put("17857100375", new String[]{"超级管理员", "17857100375", "admin"});
put("13870055185", new String[]{"魏瑶", "75719954", "engineer"});
put("15170466624", new String[]{"刘杰", "liujie1370984851", "engineer"});
put("15170037929", new String[]{"万家明", "WJM15170037929", "engineer"});
put("19100185270", new String[]{"戴斌", "BaiBin0714", "guest"});
put("15797790517", new String[]{"余其跃", "a540378218", "guest"});
put("15879126468", new String[]{"李小华", "wxid_vgmmzfdcwx9021", "guest"});
}};
@Operation(summary = "微信小程序用户识别登录")
@PostMapping("/identify")
public ResultDomain<LoginDomain> identifyUser(@RequestBody LoginParam loginParam, HttpServletRequest request) {
logger.info("微信小程序登录请求: wechatId={}, code={}, phoneCode={}",
logger.info("微信小程序登录请求: wechatId={}, phone={}, mockMode={}",
loginParam.getWechatId(),
loginParam.getCode() != null ? loginParam.getCode().substring(0, Math.min(10, loginParam.getCode().length())) + "..." : null,
loginParam.getPhoneCode() != null ? "" : "");
loginParam.getPhone(),
loginParam.getMockMode() != null ? loginParam.getMockMode() : false);
// 1. 处理微信登录code获取openid
String openid = null;
String sessionKey = null;
if (loginParam.getCode() != null && !loginParam.getCode().trim().isEmpty()) {
ResultDomain<WeChatSessionResult> sessionResult = weChatMiniProgramService.code2Session(loginParam.getCode());
if (sessionResult.getSuccess() && sessionResult.getData() != null) {
openid = sessionResult.getData().getOpenid();
sessionKey = sessionResult.getData().getSessionKey();
logger.info("获取openid成功: {}", openid);
// 使用openid作为wechatId
loginParam.setWechatId(openid);
// ========== 模拟模式:直接使用传入的手机号匹配用户 ==========
if (Boolean.TRUE.equals(loginParam.getMockMode()) && loginParam.getPhone() != null) {
String phone = loginParam.getPhone().trim();
String[] mockUser = MOCK_USERS.get(phone);
if (mockUser != null) {
// 设置模拟用户信息
loginParam.setUsername(mockUser[0]);
loginParam.setWechatId(mockUser[1]);
logger.info("模拟登录: phone={}, name={}, wechatId={}, role={}",
phone, mockUser[0], mockUser[1], mockUser[2]);
} else {
logger.warn("获取openid失败: {}", sessionResult.getMessage());
return ResultDomain.failure("未找到该手机号对应的测试用户");
}
}
// 2. 处理手机号授权
String phoneNumber = null;
// 方式1使用phoneCode获取手机号新版API推荐
if (loginParam.getPhoneCode() != null && !loginParam.getPhoneCode().trim().isEmpty()) {
ResultDomain<WeChatPhoneResult> phoneResult = weChatMiniProgramService.getPhoneNumber(loginParam.getPhoneCode());
if (phoneResult.getSuccess() && phoneResult.getData() != null && phoneResult.getData().getPhoneInfo() != null) {
phoneNumber = phoneResult.getData().getPhoneInfo().getPurePhoneNumber();
if (phoneNumber == null) {
phoneNumber = phoneResult.getData().getPhoneInfo().getPhoneNumber();
} else {
// ========== 正常模式通过微信API获取手机号 ==========
// 1. 处理微信登录code获取openid
String openid = null;
String sessionKey = null;
if (loginParam.getCode() != null && !loginParam.getCode().trim().isEmpty()) {
ResultDomain<WeChatSessionResult> sessionResult = weChatMiniProgramService.code2Session(loginParam.getCode());
if (sessionResult.getSuccess() && sessionResult.getData() != null) {
openid = sessionResult.getData().getOpenid();
sessionKey = sessionResult.getData().getSessionKey();
logger.info("获取openid成功: {}", openid);
loginParam.setWechatId(openid);
} else {
logger.warn("获取openid失败: {}", sessionResult.getMessage());
}
logger.info("通过phoneCode获取手机号成功: {}", phoneNumber);
} else {
logger.warn("通过phoneCode获取手机号失败: {}", phoneResult.getMessage());
}
}
// 方式2使用encryptedData和iv解密手机号旧版API
if (phoneNumber == null && sessionKey != null
&& loginParam.getEncryptedData() != null && !loginParam.getEncryptedData().trim().isEmpty()
&& loginParam.getIv() != null && !loginParam.getIv().trim().isEmpty()) {
ResultDomain<String> decryptResult = weChatMiniProgramService.decryptPhoneNumber(
sessionKey, loginParam.getEncryptedData(), loginParam.getIv());
if (decryptResult.getSuccess() && decryptResult.getData() != null) {
phoneNumber = decryptResult.getData();
logger.info("通过解密获取手机号成功: {}", phoneNumber);
} else {
logger.warn("解密手机号失败: {}", decryptResult.getMessage());
// 2. 处理手机号授权
String phoneNumber = null;
// 方式1使用phoneCode获取手机号新版API推荐
if (loginParam.getPhoneCode() != null && !loginParam.getPhoneCode().trim().isEmpty()) {
ResultDomain<WeChatPhoneResult> phoneResult = weChatMiniProgramService.getPhoneNumber(loginParam.getPhoneCode());
if (phoneResult.getSuccess() && phoneResult.getData() != null && phoneResult.getData().getPhoneInfo() != null) {
phoneNumber = phoneResult.getData().getPhoneInfo().getPurePhoneNumber();
if (phoneNumber == null) {
phoneNumber = phoneResult.getData().getPhoneInfo().getPhoneNumber();
}
logger.info("通过phoneCode获取手机号成功: {}", phoneNumber);
} else {
logger.warn("通过phoneCode获取手机号失败: {}", phoneResult.getMessage());
}
}
// 方式2使用encryptedData和iv解密手机号旧版API
if (phoneNumber == null && sessionKey != null
&& loginParam.getEncryptedData() != null && !loginParam.getEncryptedData().trim().isEmpty()
&& loginParam.getIv() != null && !loginParam.getIv().trim().isEmpty()) {
ResultDomain<String> decryptResult = weChatMiniProgramService.decryptPhoneNumber(
sessionKey, loginParam.getEncryptedData(), loginParam.getIv());
if (decryptResult.getSuccess() && decryptResult.getData() != null) {
phoneNumber = decryptResult.getData();
logger.info("通过解密获取手机号成功: {}", phoneNumber);
} else {
logger.warn("解密手机号失败: {}", decryptResult.getMessage());
}
}
// 设置手机号
if (phoneNumber != null) {
loginParam.setPhone(phoneNumber);
}
}
// 设置手机号
if (phoneNumber != null) {
loginParam.setPhone(phoneNumber);
}
// 验证参数必须有wechatId或phone

View File

@@ -87,6 +87,18 @@ public class WorkcaseController {
return workcaseService.deleteWorkcase(workcase);
}
@Operation(summary = "撤销工单")
@PreAuthorize("hasAuthority('workcase:ticket:update')")
@PostMapping("/revoke/{workcaseId}")
public ResultDomain<TbWorkcaseProcessDTO> revokeWorkcase(@PathVariable(value = "workcaseId") String workcaseId) {
// 创建撤销处理过程
TbWorkcaseProcessDTO process = new TbWorkcaseProcessDTO();
process.setWorkcaseId(workcaseId);
process.setAction("repeal");
process.setMessage("用户撤销工单");
return workcaseService.createWorkcaseProcess(process);
}
@Operation(summary = "获取工单详情")
@PreAuthorize("hasAuthority('workcase:ticket:view')")
@GetMapping("/{workcaseId}")

View File

@@ -376,6 +376,7 @@ public class VideoMeetingServiceImpl implements VideoMeetingService {
meeting.setJwtToken(userJwtToken);
meeting.setJitsiIframeUrl(jitsiIframeUrl); // 真正的Jitsi URL
meeting.setIframeUrl(meetingPageUrl); // 会议页面URL用于router跳转
meeting.setJitsiServerUrl(jitsiProperties.getServer().getUrl()); // 使用当前配置的服务器URL
logger.info("生成用户专属会议URL成功: meetingId={}, userId={}, status={}",
meetingId, userId, meeting.getStatus());

View File

@@ -416,10 +416,38 @@ public class WorkcaseServiceImpl implements WorkcaseService {
// 不影响工单完成流程,只记录错误日志
}
} else if (WorkcaseProcessAction.REPEAL.getName().equals(action)) {
// 1. 更新工单状态为已撤销
TbWorkcaseDTO workcase = new TbWorkcaseDTO();
workcase.setWorkcaseId(workcaseProcess.getWorkcaseId());
workcase.setStatus("cancelled");
workcaseMapper.updateWorkcase(workcase);
// 2. 发送系统评分消息到聊天室
try {
TbWorkcaseDTO workcaseData = workcaseMapper.selectWorkcaseById(workcaseProcess.getWorkcaseId());
if (workcaseData != null && workcaseData.getRoomId() != null) {
// 创建系统评分消息
org.xyzh.api.workcase.dto.TbChatRoomMessageDTO commentMessage = new org.xyzh.api.workcase.dto.TbChatRoomMessageDTO();
commentMessage.setMessageId(IdUtil.generateUUID());
commentMessage.setOptsn(IdUtil.getOptsn());
commentMessage.setRoomId(workcaseData.getRoomId());
commentMessage.setSenderId("system");
commentMessage.setSenderType("system"); // 系统消息
commentMessage.setSenderName("系统");
commentMessage.setMessageType("comment"); // 评分消息
commentMessage.setContent("工单已撤销,请为本次服务评分");
commentMessage.setStatus("sent");
commentMessage.setCreator("system");
// 发送消息到聊天室
chatRoomService.sendMessage(commentMessage);
logger.info("工单撤销,已发送系统评分消息: workcaseId={}, roomId={}",
workcaseProcess.getWorkcaseId(), workcaseData.getRoomId());
}
} catch (Exception e) {
logger.error("发送系统评分消息失败: workcaseId={}", workcaseProcess.getWorkcaseId(), e);
// 不影响工单撤销流程,只记录错误日志
}
}
workcaseProcess.setCreator(LoginUtil.getCurrentUserId());

View File

@@ -41,6 +41,15 @@ export const workcaseAPI = {
return response.data
},
/**
* 撤销工单
* @param workcaseId 工单ID
*/
async revokeWorkcase(workcaseId: string): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
const response = await api.post<TbWorkcaseProcessDTO>(`${this.baseUrl}/revoke/${workcaseId}`)
return response.data
},
/**
* 获取工单详情
* @param workcaseId 工单ID

View File

@@ -1,11 +1,11 @@
<template>
<AdminLayout title="工单管理" info="查看和处理客户服务工单">
<template #action>
<!-- <template #action>
<el-button type="primary" @click="showCreateDialog = true">
<el-icon><Plus /></el-icon>
创建工单
</el-button>
</template>
</template> -->
<div class="workcase-container">
<!-- 筛选区域 -->

View File

@@ -471,7 +471,7 @@ const handleSendMessage = async (content: string, files: File[]) => {
roomId: currentRoomId.value,
senderId: loginDomain.user.userId,
senderName: loginDomain.userInfo.username,
senderType: 'agent',
senderType: 'staff',
content,
files: fileIds,
messageType: 'text'

View File

@@ -53,7 +53,7 @@ const getMeetingParams = () => {
}
// 加载 Jitsi External API 脚本
const loadJitsiScript = (): Promise<void> => {
const loadJitsiScript = (jitsiServerUrl: string): Promise<void> => {
return new Promise((resolve, reject) => {
// 检查是否已经加载
if ((window as any).JitsiMeetExternalAPI) {
@@ -61,16 +61,19 @@ const loadJitsiScript = (): Promise<void> => {
return
}
// 从 jitsiServerUrl 提取域名
const urlObj = new URL(jitsiServerUrl)
const scriptUrl = `${urlObj.protocol}//${urlObj.host}/external_api.js`
const script = document.createElement('script')
// 从 Jitsi 子域名加载 External API
script.src = 'https://org.xyzh.yslg.jitsi/external_api.js'
script.src = scriptUrl
script.async = true
script.onload = () => {
console.log('[JitsiMeetingView] Jitsi External API 脚本加载成功')
console.log('[JitsiMeetingView] Jitsi External API 脚本加载成功:', scriptUrl)
resolve()
}
script.onerror = () => {
reject(new Error('加载 Jitsi External API 失败'))
reject(new Error('加载 Jitsi External API 失败: ' + scriptUrl))
}
document.head.appendChild(script)
})
@@ -85,8 +88,8 @@ const initJitsiMeet = async (jitsiServerUrl: string, roomName: string, jwt: stri
name: displayName
})
// 加载 External API 脚本
await loadJitsiScript()
// 加载 External API 脚本从服务器URL动态获取
await loadJitsiScript(jitsiServerUrl)
const JitsiMeetExternalAPI = (window as any).JitsiMeetExternalAPI

View File

@@ -39,44 +39,6 @@
// 检查并选择模式
checkModeSelection() {
const mode = uni.getStorageSync('userMode')
if (!mode) {
this.showModeSelector()
}
},
// 显示模式选择器
showModeSelector() {
uni.showActionSheet({
itemList: ['员工模式 (17857100375)', '访客模式 (17857100377)'],
success: (res) => {
let wechatId = ''
let userMode = ''
let phone = ''
if (res.tapIndex === 0) {
wechatId = '17857100375'
phone = '17857100375'
userMode = 'staff'
} else {
wechatId = '17857100377'
phone = '17857100377'
userMode = 'guest'
}
// 存储选择
uni.setStorageSync('userMode', userMode)
uni.setStorageSync('wechatId', wechatId)
uni.setStorageSync('phone', phone)
console.log('已选择模式:', userMode, 'wechatId:', wechatId)
uni.showToast({
title: userMode === 'staff' ? '员工模式' : '访客模式',
icon: 'success'
})
},
fail: () => {
// 用户取消,默认使用访客模式
uni.setStorageSync('userMode', 'guest')
uni.setStorageSync('wechatId', '17857100377')
console.log('默认使用访客模式')
}
})
}
}
}

View File

@@ -38,6 +38,14 @@ export const workcaseAPI = {
return request<TbWorkcaseDTO>({ url: `${this.baseUrl}/${workcaseId}`, method: 'DELETE' })
},
/**
* 撤销工单
* @param workcaseId 工单ID
*/
revokeWorkcase(workcaseId: string): Promise<ResultDomain<TbWorkcaseProcessDTO>> {
return request<TbWorkcaseProcessDTO>({ url: `${this.baseUrl}/revoke/${workcaseId}`, method: 'POST' })
},
/**
* 获取工单详情
* @param workcaseId 工单ID

View File

@@ -7,7 +7,7 @@
"uni-app-x" : {},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wx15e67484db6d431f",
"appid" : "wx3708f41b1dc31f52",
"setting" : {
"urlCheck" : false,
"postcss" : true,

View File

@@ -79,9 +79,9 @@
</view>
<!-- 普通用户/客服消息 -->
<view v-else :class="msg.senderType === 'guest' ? 'self' : 'other'">
<view v-else :class="msg.senderId === currentUserId ? 'self' : 'other'">
<!-- 对方消息(左侧) -->
<view class="message-row other-row" v-if="msg.senderType !== 'guest'">
<view class="message-row other-row" v-if="msg.senderId !== currentUserId">
<view>
<view class="avatar">
<text class="avatar-text">{{ msg.senderName?.charAt(0) || '客' }}</text>
@@ -174,11 +174,25 @@ const currentUserName = ref<string>('我')
function loadUserInfo() {
try {
const userInfo = uni.getStorageSync('userInfo')
const loginDomain = uni.getStorageSync('loginDomain')
if (userInfo) {
const user = typeof userInfo === 'string' ? JSON.parse(userInfo) : userInfo
currentUserId.value = user.userId || user.id || ''
currentUserName.value = user.username || user.nickName || '我'
// 优先从 loginDomain.userInfo 获取用户名
if (loginDomain) {
const domain = typeof loginDomain === 'string' ? JSON.parse(loginDomain) : loginDomain
if (domain.userInfo && domain.userInfo.username) {
currentUserName.value = domain.userInfo.username
} else if (domain.userInfo && domain.userInfo.realName) {
currentUserName.value = domain.userInfo.realName
} else {
currentUserName.value = user.username || user.nickName || user.realName || user.name || '用户'
}
} else {
currentUserName.value = user.username || user.nickName || user.realName || user.name || '用户'
}
}
console.log('[chatRoom] 用户信息:', currentUserId.value, currentUserName.value)
} catch (e) {
console.error('获取用户信息失败:', e)
}

View File

@@ -903,3 +903,53 @@
.skip-auth-btn::after {
border: none;
}
// 模拟用户选择列表样式
.mock-user-list {
display: flex;
flex-direction: column;
gap: 16px;
max-height: 400px;
overflow-y: auto;
}
.mock-user-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.group-title {
font-size: 13px;
color: #999;
font-weight: 500;
padding-left: 4px;
margin-bottom: 4px;
}
.mock-user-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #f8f9fa;
border-radius: 12px;
transition: all 0.2s;
}
.mock-user-item:active {
background: #e9ecef;
transform: scale(0.98);
}
.user-name {
font-size: 16px;
color: #333;
font-weight: 500;
}
.user-phone {
font-size: 14px;
color: #666;
}

View File

@@ -4,6 +4,9 @@
<view class="header" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<text class="title">泰豪小电</text>
<view class="header-right">
<button class="workcase-btn" @tap="showUserSelector">
<text class="btn-text">{{userInfo.username || '切换'}}</text>
</button>
<button class="workcase-btn" @tap="goToChatRoomList">
<text class="btn-text">聊天室</text>
</button>
@@ -178,22 +181,48 @@
<view class="modal-mask"></view>
<view class="modal-content phone-auth-content">
<view class="modal-header">
<text class="modal-title">欢迎使用泰豪小电</text>
<text class="modal-title">选择测试用户</text>
</view>
<view class="modal-body phone-auth-body">
<view class="auth-icon-wrap">
<text class="auth-icon">📱</text>
<view class="mock-user-list">
<view class="mock-user-group">
<text class="group-title">管理员</text>
<view class="mock-user-item" @tap="selectMockUser('17857100375', '超级管理员', '17857100375')">
<text class="user-name">超级管理员</text>
<text class="user-phone">17857100375</text>
</view>
</view>
<view class="mock-user-group">
<text class="group-title">工程师</text>
<view class="mock-user-item" @tap="selectMockUser('13870055185', '魏瑶', '75719954')">
<text class="user-name">魏瑶</text>
<text class="user-phone">13870055185</text>
</view>
<view class="mock-user-item" @tap="selectMockUser('15170466624', '刘杰', 'liujie1370984851')">
<text class="user-name">刘杰</text>
<text class="user-phone">15170466624</text>
</view>
<view class="mock-user-item" @tap="selectMockUser('15170037929', '万家明', 'WJM15170037929')">
<text class="user-name">万家明</text>
<text class="user-phone">15170037929</text>
</view>
</view>
<view class="mock-user-group">
<text class="group-title">客户</text>
<view class="mock-user-item" @tap="selectMockUser('19100185270', '戴斌', 'BaiBin0714')">
<text class="user-name">戴斌</text>
<text class="user-phone">19100185270</text>
</view>
<view class="mock-user-item" @tap="selectMockUser('15797790517', '余其跃', 'a540378218')">
<text class="user-name">余其跃</text>
<text class="user-phone">15797790517</text>
</view>
<view class="mock-user-item" @tap="selectMockUser('15879126468', '李小华', 'wxid_vgmmzfdcwx9021')">
<text class="user-name">李小华</text>
<text class="user-phone">15879126468</text>
</view>
</view>
</view>
<text class="auth-desc">为了给您提供更好的服务,需要获取您的手机号用于身份识别和工单通知</text>
</view>
<view class="modal-footer phone-auth-footer">
<button
class="modal-btn confirm phone-auth-btn"
open-type="getPhoneNumber"
@getphonenumber="onGetPhoneNumber"
>
<text class="btn-text">授权手机号登录</text>
</button>
</view>
</view>
</view>
@@ -364,7 +393,7 @@
}
}
// 获取手机号回调
// 获取手机号回调(保留用于正式环境)
async function onGetPhoneNumber(e: any) {
console.log('获取手机号回调:', e)
@@ -462,6 +491,72 @@
}
}
// 选择模拟用户(测试用)
async function selectMockUser(phone: string, name: string, wechatId: string) {
showPhoneAuthModal.value = false
uni.showLoading({ title: '登录中...' })
try {
// 调用 identify 接口,使用模拟模式
const identifyRes = await guestAPI.identify({
phone: phone,
wechatId: wechatId,
username: name,
mockMode: true,
loginType: 'wechat_miniprogram'
})
uni.hideLoading()
if (identifyRes.success && identifyRes.data) {
const loginDomain = identifyRes.data
// 保存登录信息
uni.setStorageSync('token', loginDomain.token || '')
uni.setStorageSync('userInfo', JSON.stringify(loginDomain.user))
uni.setStorageSync('loginDomain', JSON.stringify(loginDomain))
uni.setStorageSync('wechatId', wechatId)
// 更新用户信息
userInfo.value = {
wechatId: wechatId,
username: name,
phone: phone,
userId: loginDomain.user?.userId || ''
}
// 判断用户类型
if (loginDomain.user?.status === 'guest') {
userType.value = false
} else {
userType.value = true
}
console.log('模拟登录成功:', userInfo.value)
uni.showToast({ title: `${name} 登录成功`, icon: 'success' })
} else {
uni.showToast({
title: identifyRes.message || '登录失败',
icon: 'none'
})
showPhoneAuthModal.value = true
}
} catch (error: any) {
console.error('模拟登录失败:', error)
uni.hideLoading()
uni.showToast({
title: error.message || '登录失败',
icon: 'none'
})
showPhoneAuthModal.value = true
}
}
// 显示用户选择弹窗(切换人员)
function showUserSelector() {
showPhoneAuthModal.value = true
}
// 生命周期
onMounted(() => {
// 初始化用户信息

View File

@@ -1149,17 +1149,39 @@ async function submitWorkcase() {
}
// 撤销工单
function handleRevoke() {
async function handleRevoke() {
uni.showModal({
title: '撤销确认',
content: '确认撤销该工单?撤销后无法恢复',
success: (res) => {
success: async (res) => {
if (res.confirm) {
// TODO: 调用 API 撤销工单
uni.showToast({
title: '工单已撤销',
icon: 'success'
})
uni.showLoading({ title: '撤销中...' })
try {
const result = await workcaseAPI.revokeWorkcase(workcaseId.value)
uni.hideLoading()
if (result.success) {
uni.showToast({
title: '工单已撤销',
icon: 'success'
})
// 刷新工单详情
setTimeout(() => {
loadWorkcaseDetail()
}, 500)
} else {
uni.showToast({
title: result.message || '撤销失败',
icon: 'none'
})
}
} catch (error: any) {
uni.hideLoading()
console.error('撤销工单失败:', error)
uni.showToast({
title: error.message || '撤销失败',
icon: 'none'
})
}
}
}
})