挂牌上锁程序开发
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.

301 lines
10 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
}
export const getCurrentLocation = async (): Promise<Location> => {
if (appStore.isWorkWechat) {
const res = await ww.getLocation()
return {
latitude: res.latitude,
longitude: res.longitude,
accuracy: res.accuracy
}
} else 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) => {
ElMessage.error('获取位置失败')
resolve({
latitude: 0,
longitude: 0,
accuracy: 0
})
})
})
} else {
return Promise.resolve({
latitude: 0,
longitude: 0,
accuracy: 0
})
}
}
// 统一执行绑定逻辑:校验锁状态 -> 获取位置 -> 记录工单 -> 批量更新状态
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
}
// 获取当前位置
let location: Location
try {
location = await getCurrentLocation()
} catch (_) {
ElMessage.error('获取位置失败,无法绑定')
return
}
// // 批量更新业务状态
// 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('绑定成功')
}
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
}))
// 检查是否有可用的锁具
if (lockOptions.length === 0) {
ElMessage.error('暂无可用的锁具')
return
}
// 如果只有一个锁具,直接使用
if (lockOptions.length === 1) {
const lockId = lockOptions[0]?.value
if (lockId) {
await performBind(detail, lockId, useUserStore().getUser.id)
}
return
}
// 有多个锁具时,让用户选择
const lockOptionsHtml = lockOptions.map((option, index) =>
`<option value="${option.value}">${option.number}. ${option.label}</option>`
).join('')
let selectedLockId: string | null = null
try {
await ElMessageBox.confirm(
`<div>
<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
if (!selectedValue) {
ElMessage.warning('请选择一个锁具')
return false
}
selectedLockId = selectedValue
}
done()
}
}
)
// 用户点击确定后执行绑定
if (selectedLockId) {
await performBind(detail, Number(selectedLockId), useUserStore().getUser.id)
}
} catch (error) {
// 用户取消选择,不做任何操作
console.log('用户取消选择锁具')
}
}
} catch (error) {
ElMessage.error('绑定失败,请重试')
throw error
}
}
// 将 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 */ }
}
// 回退:使用 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
})
}
// 创建文件选择器(非企业微信环境使用)
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'
input.onchange = (event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
document.body.removeChild(input)
if (file) {
resolve(file)
} else {
reject(new Error('未选择文件'))
}
}
input.oncancel = () => {
document.body.removeChild(input)
reject(new Error('用户取消选择'))
}
document.body.appendChild(input)
input.click()
})
}
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 }>
}
// 企业微信返回可能是 tempFiles 或 localIds/localId
const directFile: File | undefined = (chooseRes as any)?.tempFiles?.[0]?.file
const localId: string | undefined = chooseRes?.localIds?.[0] || (chooseRes as any)?.localId
const file: File | null = directFile
? directFile
: (localId ? await convertLocalIdToFile(localId) : null)
if (!file) throw new Error('无法获取拍照文件')
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
}
}