挂牌上锁程序开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
9.9 KiB

2 days ago
import { useAppStore } from '@/store/modules/app'
import { useUserStore } from '@/store/modules/user'
import { useElLockStore } from '@/store/modules/elLock'
const elLockStore = useElLockStore()
const appStore = useAppStore()
import ww from '@/utils/ww'
import { LockApi } from '@/api/electron/lock'
import { LockWorkRecordApi } from '@/api/electron/lockworkcord'
import { bindLock as bindLockApi } from '@/api/lock'
import { ElMessage, ElMessageBox } from 'element-plus'
import download from '@/utils/download'
import { updateFile } from '@/api/infra/file'
type Location = {
latitude: number
longitude: number
accuracy: number
2 days ago
}
2 days ago
export const getCurrentLocation = async (): Promise<Location> => {
if (appStore.isWorkWechat) {
try {
// 企业微信的 ww.getLocation 已经是 Promise-based,直接使用
const res = await ww.getLocation();
return {
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy,
};
} catch (error) {
ElMessage.error('在企业微信中获取位置失败');
// 抛出错误,让调用者可以捕获
return Promise.reject(error);
}
}
if (window.navigator.geolocation) {
return new Promise((resolve, reject) => {
window.navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
});
},
(error) => {
// 在 Promise 中,错误情况应该调用 reject
ElMessage.error('获取位置失败');
reject(error); // [9, 10, 12]
},
// 可以增加一些选项来优化定位,例如超时设置 [13, 14]
{
timeout: 10000, // 10秒超时
enableHighAccuracy: true,
}
);
});
}
// 如果环境完全不支持地理位置,也应该 reject
ElMessage.error('您的环境不支持获取地理位置');
return Promise.reject(new Error('Geolocation is not supported.'));
};
2 days ago
// 统一执行绑定逻辑:校验锁状态 -> 获取位置 -> 记录工单 -> 批量更新状态
async function performBind(detail: any, lockId: number, operatorId: number): Promise<void> {
// 校验锁具状态
const lock = await LockApi.getLock(lockId)
if (!lock) {
ElMessage.error('锁具不存在')
return
}
if (lock.lockStatus != 2) {
ElMessage.error('锁具状态异常 无法绑定')
return
}
2 days ago
// 获取当前位置
let location: Location
try {
location = await getCurrentLocation()
} catch (_) {
ElMessage.error('获取位置失败,无法绑定')
return
}
2 days ago
// // 批量更新业务状态
// await Promise.all([
// PointApi.updatePoint({
// id: detail.isolationPointId,
// status: 1 // 已锁定
// }),
// LockApi.updateLock({
// id: lockId,
// lockStatus: 7 // 已绑定
// }),
// PlanItemDetailApi.updatePlanItemDetail({
// id: detail.id,
// lockId: lockId,
// lockStatus: 1 // 未上锁
// })
// ])
await bindLockApi({ planItemDetailId: detail.id, lockId })
// 创建工单记录
await LockWorkRecordApi.createLockWorkRecord({
operatorId: Number(operatorId),
lockId: lockId,
isolationPlanItemDetailId: detail.id,
recordType: 2, // 未绑定
gpsCoordinates: `${location.latitude},${location.longitude}`
})
ElMessage.success('绑定成功')
2 days ago
}
export const bindLock = async (detail: any) => {
try {
if (appStore.isWorkWechat) {
const currentUserId = useUserStore().getUser.id
// 扫描二维码获取锁具ID
const scanRes = await (ww as any).scanQRCode({
needResult: true,
scanType: ['qrCode']
})
const lockId = Number((scanRes && scanRes.resultStr) || NaN)
if (!lockId || Number.isNaN(lockId)) {
ElMessage.error('二维码内容无效')
return
}
await performBind(detail, lockId, currentUserId)
} else {
const lockOptions = elLockStore.locks
.filter((i) => i.lockEnableStatus == 1 && i.lockStatus == 2)
.map((lock) => ({
label: lock.lockName,
number: lock.lockNumber,
value: lock.id
}))
2 days ago
// 检查是否有可用的锁具
if (lockOptions.length === 0) {
ElMessage.error('暂无可用的锁具')
return
}
2 days ago
// 如果只有一个锁具,直接使用
if (lockOptions.length === 1) {
const lockId = lockOptions[0]?.value
if (lockId) {
await performBind(detail, lockId, useUserStore().getUser.id)
}
return
}
2 days ago
// 有多个锁具时,让用户选择
const lockOptionsHtml = lockOptions
.map(
(option, index) =>
`<option value="${option.value}">${option.number}. ${option.label}</option>`
)
.join('')
2 days ago
let selectedLockId: string | null = null
2 days ago
try {
await ElMessageBox.confirm(
`<div>
2 days ago
<p style="margin-bottom: 10px;"></p>
<select id="lockSelector" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<option value="">-- --</option>
${lockOptionsHtml}
</select>
</div>`,
'选择锁具',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
beforeClose: (action, _instance, done) => {
if (action === 'confirm') {
const selector = document.getElementById('lockSelector') as HTMLSelectElement
const selectedValue = selector?.value
2 days ago
if (!selectedValue) {
ElMessage.warning('请选择一个锁具')
return false
2 days ago
}
selectedLockId = selectedValue
}
done()
2 days ago
}
}
)
// 用户点击确定后执行绑定
if (selectedLockId) {
await performBind(detail, Number(selectedLockId), useUserStore().getUser.id)
2 days ago
}
} catch (error) {
// 用户取消选择,不做任何操作
console.log('用户取消选择锁具')
2 days ago
throw error
}
2 days ago
}
} catch (error) {
// ElMessage.error('绑定失败,请重试')
throw error
}
2 days ago
}
// 将 localId 转换为 File(先尝试 getLocalImgData,失败则用 Canvas 回退)
async function convertLocalIdToFile(localId: string): Promise<File> {
// 优先:getLocalImgData(iOS 常见)
if ((ww as any).getLocalImgData) {
try {
const localDataRes = await (ww as any).getLocalImgData({ localId })
const base64 = String((localDataRes && localDataRes.localData) || '')
const dataUrl = base64.startsWith('data:') ? base64 : `data:image/jpeg;base64,${base64}`
return download.base64ToFile(dataUrl, 'photo')
} catch (_) {
/* fallback to canvas */
2 days ago
}
}
2 days ago
// 回退:使用 Canvas 生成 base64 再转 File(Android 常见)
return await new Promise<File>((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('canvas context 获取失败')
ctx.drawImage(img, 0, 0, img.width, img.height)
const dataUrl = canvas.toDataURL('image/jpeg')
resolve(download.base64ToFile(dataUrl, 'photo'))
} catch (err) {
reject(err)
}
}
img.onerror = (err) => reject(err)
img.src = localId
})
2 days ago
}
// 创建文件选择器(非企业微信环境使用)
async function selectFile(): Promise<File> {
return new Promise((resolve, reject) => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/*'
input.style.display = 'none'
2 days ago
input.onchange = (event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
document.body.removeChild(input)
2 days ago
if (file) {
resolve(file)
} else {
reject(new Error('未选择文件'))
}
}
2 days ago
input.oncancel = () => {
document.body.removeChild(input)
reject(new Error('用户取消选择'))
}
2 days ago
document.body.appendChild(input)
input.click()
})
2 days ago
}
export const getPhoto = async () => {
try {
if (appStore.isWorkWechat) {
// 放宽企业微信JSSDK类型限制,兼容不同终端返回与参数
const chooseRes = (await (ww as any).chooseImage({
count: 1,
sizeType: ['original'],
sourceType: ['camera'],
defaultCameraMode: 'normal',
isSaveToAlbum: false
} as any)) as {
localIds?: string[]
localId?: string
tempFiles?: Array<{ file?: File }>
}
2 days ago
// 企业微信返回可能是 tempFiles 或 localIds/localId
const directFile: File | undefined = (chooseRes as any)?.tempFiles?.[0]?.file
const localId: string | undefined = chooseRes?.localIds?.[0] || (chooseRes as any)?.localId
2 days ago
const file: File | null = directFile
? directFile
: localId
? await convertLocalIdToFile(localId)
: null
2 days ago
if (!file) throw new Error('无法获取拍照文件')
2 days ago
const uploadRes = await updateFile({ file })
return uploadRes && uploadRes.data
} else {
// 非企业微信环境:选择文件并上传
const file = await selectFile()
const uploadRes = await updateFile({ file })
return uploadRes && uploadRes.data
}
} catch (err) {
ElMessage.error('文件上传失败')
throw err
}
2 days ago
}