198 lines
7.9 KiB
JavaScript
198 lines
7.9 KiB
JavaScript
/** OA 审批应用图标:与 web-admin/src/utils/oa-app-icons.ts 色对/解析对齐 */
|
||
|
||
import { MINIO_BASE_URL } from './config.js'
|
||
|
||
export const OA_APP_ICON_COLOR_PAIRS = [
|
||
{ bg: '#d1fae5', fg: '#059669' },
|
||
{ bg: '#cffafe', fg: '#0891b2' },
|
||
{ bg: '#dbeafe', fg: '#2563eb' },
|
||
{ bg: '#e0e7ff', fg: '#4f46e5' },
|
||
{ bg: '#ede9fe', fg: '#7c3aed' },
|
||
{ bg: '#fce7f3', fg: '#db2777' },
|
||
{ bg: '#ffedd5', fg: '#ea580c' },
|
||
{ bg: '#fef3c7', fg: '#d97706' }
|
||
]
|
||
|
||
export const OA_APP_DEFAULT_EP_ICON = 'Document'
|
||
export const OA_APP_DEFAULT_BG = OA_APP_ICON_COLOR_PAIRS[0].bg
|
||
export const OA_APP_DEFAULT_FG = OA_APP_ICON_COLOR_PAIRS[0].fg
|
||
|
||
/** 精选 Element Plus 图标名(PascalCase,与 @element-plus/icons-vue 导出一致) */
|
||
export const OA_APP_EP_ICON_NAMES = [
|
||
'Document',
|
||
'Calendar',
|
||
'Clock',
|
||
'Wallet',
|
||
'User',
|
||
'Tickets',
|
||
'Briefcase',
|
||
'Money',
|
||
'Van',
|
||
'OfficeBuilding',
|
||
'Message',
|
||
'Setting',
|
||
'CollectionTag',
|
||
'Checked',
|
||
'Present',
|
||
'Stamp',
|
||
'CreditCard',
|
||
'Notebook',
|
||
'Bell',
|
||
'Location'
|
||
]
|
||
|
||
const BG_SET = new Set(OA_APP_ICON_COLOR_PAIRS.map((p) => p.bg.toLowerCase()))
|
||
const FG_BY_BG = Object.fromEntries(
|
||
OA_APP_ICON_COLOR_PAIRS.map((p) => [p.bg.toLowerCase(), p.fg])
|
||
)
|
||
const ICON_SET = new Set(OA_APP_EP_ICON_NAMES)
|
||
|
||
export function isEpIconUrl(iconUrl) {
|
||
return String(iconUrl || '').trim().startsWith('ep:')
|
||
}
|
||
|
||
export function iconFgFromBg(bg) {
|
||
const key = String(bg || '').trim().toLowerCase()
|
||
return FG_BY_BG[key] || OA_APP_DEFAULT_FG
|
||
}
|
||
|
||
export function buildEpIconUrl(icon, bg) {
|
||
const name = ICON_SET.has(icon) ? icon : OA_APP_DEFAULT_EP_ICON
|
||
const color = BG_SET.has(String(bg).toLowerCase())
|
||
? String(bg).toLowerCase()
|
||
: OA_APP_DEFAULT_BG
|
||
return `ep:${name}|${color}`
|
||
}
|
||
|
||
export function parseEpIconUrl(iconUrl) {
|
||
const raw = String(iconUrl || '').trim()
|
||
if (!raw.startsWith('ep:')) {
|
||
return { icon: OA_APP_DEFAULT_EP_ICON, bg: OA_APP_DEFAULT_BG, fg: OA_APP_DEFAULT_FG }
|
||
}
|
||
const body = raw.slice(3)
|
||
const pipe = body.indexOf('|')
|
||
if (pipe < 0) {
|
||
return { icon: OA_APP_DEFAULT_EP_ICON, bg: OA_APP_DEFAULT_BG, fg: OA_APP_DEFAULT_FG }
|
||
}
|
||
let icon = body.slice(0, pipe).trim()
|
||
let bg = body.slice(pipe + 1).trim().toLowerCase()
|
||
if (!ICON_SET.has(icon)) icon = OA_APP_DEFAULT_EP_ICON
|
||
if (!BG_SET.has(bg)) bg = OA_APP_DEFAULT_BG
|
||
return { icon, bg, fg: iconFgFromBg(bg) }
|
||
}
|
||
|
||
/**
|
||
* 旧版静态图标存 `/oa-app-icons/*.svg`(web-admin/public)。
|
||
* 小程序/H5 需走 `static/oa-app-icons/`,否则 5173 下 404。
|
||
* 上传图存 MinIO 相对 key(如 `202608/11/xxx.png`),需拼 MINIO_BASE_URL。
|
||
*/
|
||
export function resolveOaAppIconImageSrc(iconUrl) {
|
||
const u = String(iconUrl || '').trim()
|
||
if (!u || isEpIconUrl(u)) return ''
|
||
if (/^https?:\/\//i.test(u)) return u
|
||
if (u.startsWith('/oa-app-icons/')) {
|
||
return '/static' + u
|
||
}
|
||
if (u.startsWith('/static/')) return u
|
||
const minioBase = String(MINIO_BASE_URL || '').replace(/\/$/, '')
|
||
// 同域反代路径:本机 H5 无 /minio 代理时,改写为绝对 MinIO 基址
|
||
if (u.startsWith('/minio/zkxw/')) {
|
||
if (minioBase && /^https?:\/\//i.test(minioBase)) {
|
||
return `${minioBase}/${u.slice('/minio/zkxw/'.length)}`
|
||
}
|
||
return u
|
||
}
|
||
if (u.startsWith('/')) return u
|
||
return minioBase ? `${minioBase}/${u.replace(/^\//, '')}` : u
|
||
}
|
||
|
||
export function resolveOaAppIcon(iconUrl) {
|
||
const u = String(iconUrl || '').trim()
|
||
if (!u) {
|
||
const p = parseEpIconUrl(buildEpIconUrl(OA_APP_DEFAULT_EP_ICON, OA_APP_DEFAULT_BG))
|
||
return { kind: 'ep', icon: p.icon, bg: p.bg, fg: p.fg }
|
||
}
|
||
if (isEpIconUrl(u)) {
|
||
const p = parseEpIconUrl(u)
|
||
return { kind: 'ep', icon: p.icon, bg: p.bg, fg: p.fg }
|
||
}
|
||
return { kind: 'image', src: resolveOaAppIconImageSrc(u) }
|
||
}
|
||
|
||
const EP_ICON_SVG_INNER = {
|
||
Document:
|
||
'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/>',
|
||
Calendar:
|
||
'<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
|
||
Clock:
|
||
'<circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 14"/>',
|
||
Wallet:
|
||
'<path d="M20 7H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/><path d="M16 12h4"/><path d="M2 7l2-3h12l2 3"/>',
|
||
User:
|
||
'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
|
||
Tickets:
|
||
'<rect x="3" y="7" width="18" height="10" rx="2"/><line x1="3" y1="11" x2="21" y2="11"/>',
|
||
Briefcase:
|
||
'<rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2"/><line x1="2" y1="12" x2="22" y2="12"/>',
|
||
Money:
|
||
'<circle cx="12" cy="12" r="9"/><path d="M12 7v10"/><path d="M15 9.5c0-1.1-1.3-2-3-2s-3 .9-3 2 1.3 2 3 2 3 .9 3 2-1.3 2-3 2-3-.9-3-2"/>',
|
||
Van:
|
||
'<path d="M3 17h1a3 3 0 0 0 6 0h4a3 3 0 0 0 6 0h1"/><path d="M3 17V8a2 2 0 0 1 2-2h8l6 4v7"/><circle cx="7.5" cy="17" r="1.5"/><circle cx="16.5" cy="17" r="1.5"/>',
|
||
OfficeBuilding:
|
||
'<path d="M4 21V5a1 1 0 0 1 1-1h9v17"/><path d="M14 9h6v12"/><line x1="8" y1="9" x2="10" y2="9"/><line x1="8" y1="13" x2="10" y2="13"/><line x1="8" y1="17" x2="10" y2="17"/>',
|
||
Message:
|
||
'<path d="M21 11.5a8.5 8.5 0 0 1-12.4 7.5L3 21l1.9-5.1A8.5 8.5 0 1 1 21 11.5z"/>',
|
||
Setting:
|
||
'<circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4"/>',
|
||
CollectionTag:
|
||
'<path d="M20.6 13.4l-7.2 7.2a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z"/><circle cx="7.5" cy="7.5" r="1"/>',
|
||
Checked:
|
||
'<path d="M22 11.1V12a10 10 0 1 1-5.9-9.1"/><polyline points="22 4 12 14.01 9 11"/>',
|
||
Present:
|
||
'<polyline points="20 12 20 22 4 22 4 12"/><rect x="2" y="7" width="20" height="5"/><line x1="12" y1="22" x2="12" y2="7"/><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"/><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"/>',
|
||
Stamp:
|
||
'<path d="M5 21h14"/><path d="M12 3a4 4 0 0 0-4 4c0 2 1.5 3 4 5 2.5-2 4-3 4-5a4 4 0 0 0-4-4z"/><path d="M6 15h12v3H6z"/>',
|
||
CreditCard:
|
||
'<rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/>',
|
||
Notebook:
|
||
'<path d="M4 4h12a2 2 0 0 1 2 2v14H6a2 2 0 0 0-2 2V4z"/><line x1="8" y1="8" x2="14" y2="8"/><line x1="8" y1="12" x2="14" y2="12"/>',
|
||
Bell:
|
||
'<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/>',
|
||
Location:
|
||
'<path d="M12 21s7-4.5 7-11a7 7 0 1 0-14 0c0 6.5 7 11 7 11z"/><circle cx="12" cy="10" r="2"/>'
|
||
}
|
||
|
||
const BASE64_CHARS =
|
||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||
|
||
/** UTF-8 字符串 → base64;不依赖 btoa / Buffer(微信 appservice 两者皆无) */
|
||
function utf8ToBase64(str) {
|
||
const utf8 = unescape(encodeURIComponent(String(str || '')))
|
||
if (typeof btoa === 'function') {
|
||
return btoa(utf8)
|
||
}
|
||
let out = ''
|
||
for (let i = 0; i < utf8.length; i += 3) {
|
||
const a = utf8.charCodeAt(i)
|
||
const b = i + 1 < utf8.length ? utf8.charCodeAt(i + 1) : 0
|
||
const c = i + 2 < utf8.length ? utf8.charCodeAt(i + 2) : 0
|
||
const n = (a << 16) | (b << 8) | c
|
||
out += BASE64_CHARS[(n >> 18) & 63]
|
||
out += BASE64_CHARS[(n >> 12) & 63]
|
||
out += i + 1 < utf8.length ? BASE64_CHARS[(n >> 6) & 63] : '='
|
||
out += i + 2 < utf8.length ? BASE64_CHARS[n & 63] : '='
|
||
}
|
||
return out
|
||
}
|
||
|
||
/** 生成小程序 <image> 可用的 SVG data-URI(base64,兼容微信) */
|
||
export function epIconDataUri(icon, fg) {
|
||
const name = ICON_SET.has(icon) ? icon : OA_APP_DEFAULT_EP_ICON
|
||
const stroke = fg || OA_APP_DEFAULT_FG
|
||
const inner = EP_ICON_SVG_INNER[name] || EP_ICON_SVG_INNER.Document
|
||
const svg =
|
||
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`
|
||
// WeChat <image> 对 charset=utf-8 + encodeURIComponent 偶发不兼容,统一 base64
|
||
return 'data:image/svg+xml;base64,' + utf8ToBase64(svg)
|
||
}
|