| | |
| | | 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 function getDictLabel(value, dictList) { |
| | | return dictList.find(item => item.dictKey === value)?.dictValue || 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) { |
| | |
| | | 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 '' |
| | | } |
| | | } |