From 493a27022ad30291934db825bab591c7efdcbc09 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Wed, 12 Aug 2026 16:35:45 +0800
Subject: [PATCH] feat: 保存的时候一个按钮置灰

---
 packages/utils/common/index.js |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 150 insertions(+), 2 deletions(-)

diff --git a/packages/utils/common/index.js b/packages/utils/common/index.js
index 0c1f904..97ff9a5 100644
--- a/packages/utils/common/index.js
+++ b/packages/utils/common/index.js
@@ -12,9 +12,40 @@
 	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) {
@@ -30,3 +61,120 @@
 	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 ''
+	}
+}

--
Gitblit v1.9.3