export function fieldRules(required,max) { // export function fieldRules({ required = true, isSelect = false, max = 50,type = 'string' }) { const trigger = ['blur', 'change'] const rules = [] const isInput = typeof max === 'number' if (required) { rules.push({ required: true, message: isInput ? '请输入' : '请选择', trigger }) } if (isInput) { rules.push({ max, message: `长度不超过${max}`, trigger }) } return rules } const isContactPhone = value => { const mobile = /^1[3-9]\d{9}$/ const landline = /^0\d{2,3}-?\d{7,8}$/ return mobile.test(value) || landline.test(value) } export const contactPhoneRules = (required = true, max = 50) => { const validateContactPhone = (rule, value, callback) => { const nextValue = String(value || '').trim() if (!nextValue) return callback() if (isContactPhone(nextValue)) return callback() return callback(new Error('联系电话格式不正确')) } return [...fieldRules(required, max), { validator: validateContactPhone, trigger: ['blur', 'change'] }] } export function getDictLabel(value, dictList = []) { if (value === null || value === undefined || value === '') return ''; const values = String(value).split(','); return dictList .filter(item => values.includes(String(item.dictKey))) .map(item => item.dictValue) .join(','); } // id转label xt export function getDictLabelXT(value, dictList = []) { if (value === null || value === undefined || value === '') return ''; const values = String(value).split(','); return dictList .filter(item => values.includes(String(item.dictValue))) .map(item => item.dictLabel) .join(','); } export function blobDownload(res) { const disposition = res.headers?.['content-disposition'] || res.headers?.['Content-Disposition'] const encodedName = disposition.split('filename=')[1] const filename = decodeURIComponent(encodedName) const elink = document.createElement('a') elink.download = filename elink.style.display = 'none' const blob = new Blob([res.data]) elink.href = URL.createObjectURL(blob) document.body.appendChild(elink) elink.click() document.body.removeChild(elink) } export function geomAnalysis(str) { if (!str) return [] return str .replace('POLYGON((', '') .replace('))', '') .split(',') .map(pair => { const [longitude, latitude] = pair.trim().split(' ').map(Number) return { longitude, latitude } }) } // 替换图片请求代理地址 export function replaceWithProxy(url) { if (!url) return '' return url.replace( /^https?:\/\/[^/]+/, 'https://wrj.shuixiongit.com/ja-proxy' ) } // 加载图片并获取原始尺寸 function loadImage(url) { return new Promise((resolve, reject) => { const image = new Image() image.crossOrigin = 'anonymous' image.onload = () => { if (!image.naturalWidth || !image.naturalHeight) { reject(new Error('图片尺寸无效')) return } resolve(image) } image.onerror = () => reject(new Error('图片加载失败')) image.src = url }) } // 在图片上绘制算法框 function renderAiFrame(image, aiFrame, scaleX = 1, scaleY = 1) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) return '' canvas.width = image.naturalWidth canvas.height = image.naturalHeight ctx.drawImage(image, 0, 0, canvas.width, canvas.height) aiFrame.forEach(item => { const { x_cen, y_cen, width, height } = item.bbox || {} if ([x_cen, y_cen, width, height].some(value => typeof value !== 'number')) return const scaledWidth = width * scaleX const scaledHeight = height * scaleY const x = x_cen * scaleX - scaledWidth / 2 const y = y_cen * scaleY - scaledHeight / 2 const label = item.class_name || '' const fontSize = Math.max(18, Math.round(canvas.width / 80)) const labelHeight = fontSize + 10 const labelY = y - labelHeight >= 0 ? y - labelHeight : y ctx.strokeStyle = '#FF3B30' ctx.lineWidth = Math.max(3, Math.round(canvas.width / 640)) ctx.strokeRect(x, y, scaledWidth, scaledHeight) if (label) { ctx.font = `${fontSize}px Arial` const labelWidth = ctx.measureText(label).width + 16 ctx.fillStyle = '#FF3B30' ctx.fillRect(x, labelY, labelWidth, labelHeight) ctx.fillStyle = '#FFFFFF' ctx.textBaseline = 'middle' ctx.fillText(label, x + 8, labelY + labelHeight / 2) } }) try { return canvas.toDataURL('image/jpeg', 0.92) } catch { return '' } } // 异步生成算法成果图片 export async function appendAiImages(item) { try { const aiFrame = JSON.parse(item.geojson) const resultImagePromise = loadImage(item.resultUrl) const thumbnailImagePromise = loadImage(item.thumbnail).catch(() => null) const resultImage = await resultImagePromise item.resultUrl = renderAiFrame(resultImage, aiFrame) const thumbnailImage = await thumbnailImagePromise if (!thumbnailImage) { item.thumbnail = item.resultUrl return } const scaleX = thumbnailImage.naturalWidth / resultImage.naturalWidth const scaleY = thumbnailImage.naturalHeight / resultImage.naturalHeight item.thumbnail = renderAiFrame(thumbnailImage, aiFrame, scaleX, scaleY) } catch { item.thumbnail = item.resultUrl } } // 图片转带ai框的图片 export async function drawAiFrame(url, aiFrameSource) { if (!url) return url if (!aiFrameSource) return url try { const aiFrame = JSON.parse(aiFrameSource) const image = await loadImage(url) return renderAiFrame(image, aiFrame) } catch { return '' } }