Files
zkxw/mobile-h5/utils/oa-app-icons.js
T
2026-08-12 10:02:31 +08:00

198 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 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)
}