mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2026-07-22 15:17:45 +00:00
Compare commits
No commits in common. "master" and "v1.2.4" have entirely different histories.
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,4 +19,5 @@ bin-release/
|
|||||||
|
|
||||||
|
|
||||||
/.idea
|
/.idea
|
||||||
|
/upgrade
|
||||||
niucloud/public/.htaccess
|
niucloud/public/.htaccess
|
||||||
|
|||||||
4
admin/.gitignore
vendored
4
admin/.gitignore
vendored
@ -22,7 +22,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.build
|
|
||||||
build-report.json
|
|
||||||
auto-imports.d.ts
|
|
||||||
components.d.ts
|
|
||||||
|
|||||||
3
admin/auto-imports.d.ts
vendored
3
admin/auto-imports.d.ts
vendored
@ -1,5 +1,6 @@
|
|||||||
// Generated by 'unplugin-auto-import'
|
// Generated by 'unplugin-auto-import'
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||||
|
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||||
}
|
}
|
||||||
|
|||||||
40
admin/components.d.ts
vendored
40
admin/components.d.ts
vendored
@ -11,36 +11,60 @@ declare module '@vue/runtime-core' {
|
|||||||
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
|
DiyLink: typeof import('./src/components/diy-link/index.vue')['default']
|
||||||
DiyPage: typeof import('./src/components/diy-page/index.vue')['default']
|
DiyPage: typeof import('./src/components/diy-page/index.vue')['default']
|
||||||
Editor: typeof import('./src/components/editor/index.vue')['default']
|
Editor: typeof import('./src/components/editor/index.vue')['default']
|
||||||
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
|
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||||
|
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
ElCarousel: typeof import('element-plus/es')['ElCarousel']
|
||||||
|
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
|
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||||
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElImage: typeof import('element-plus/es')['ElImage']
|
ElImage: typeof import('element-plus/es')['ElImage']
|
||||||
|
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
ElResult: typeof import('element-plus/es')['ElResult']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||||
|
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
||||||
|
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||||
|
ElStep: typeof import('element-plus/es')['ElStep']
|
||||||
|
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||||
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTree: typeof import('element-plus/es')['ElTree']
|
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||||
|
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||||
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
|
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||||
ExportSure: typeof import('./src/components/export-sure/index.vue')['default']
|
ExportSure: typeof import('./src/components/export-sure/index.vue')['default']
|
||||||
HeatMap: typeof import('./src/components/heat-map/index.vue')['default']
|
HeatMap: typeof import('./src/components/heat-map/index.vue')['default']
|
||||||
Icon: typeof import('./src/components/icon/index.vue')['default']
|
Icon: typeof import('./src/components/icon/index.vue')['default']
|
||||||
|
|||||||
@ -8,16 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script>
|
|
||||||
// 外挂配置 — 部署后可直接修改此处的值,无需重新编译
|
|
||||||
window.__ENV__ = {
|
|
||||||
VITE_APP_BASE_URL: "",
|
|
||||||
VITE_IMG_DOMAIN: "",
|
|
||||||
VITE_REQUEST_HEADER_TOKEN_KEY: "token",
|
|
||||||
VITE_REQUEST_HEADER_SITEID_KEY: "site-id",
|
|
||||||
VITE_DETAULT_TITLE: ""
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -5,11 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "node scripts/build-all.cjs",
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build && node publish.cjs",
|
||||||
"build:core": "node scripts/build-shared.cjs && node scripts/clean-core.cjs && node node_modules/vite/bin/vite.js build --config vite.config.core.ts --force && node scripts/verify-core-lang.cjs && node scripts/assemble-admin.cjs",
|
|
||||||
"build:shared": "node scripts/build-shared.cjs",
|
|
||||||
"build:addon": "node scripts/build-addon.cjs",
|
|
||||||
"build:assemble": "node scripts/assemble-admin.cjs",
|
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -28,8 +24,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"day": "^0.0.2",
|
"day": "^0.0.2",
|
||||||
"echarts": "5.4.1",
|
"echarts": "5.4.1",
|
||||||
"element-plus": "2.7.4",
|
"element-plus": "^2.7.4",
|
||||||
"esbuild": "^0.16.17",
|
|
||||||
"highlight.js": "11.0.1",
|
"highlight.js": "11.0.1",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
@ -38,7 +33,7 @@
|
|||||||
"sass": "1.58.0",
|
"sass": "1.58.0",
|
||||||
"sortablejs": "1.15.0",
|
"sortablejs": "1.15.0",
|
||||||
"vditor": "^3.10.9",
|
"vditor": "^3.10.9",
|
||||||
"vue": "3.3.7",
|
"vue": "3.2.45",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-jsonp": "2.0.0",
|
"vue-jsonp": "2.0.0",
|
||||||
"vue-router": "4.1.6",
|
"vue-router": "4.1.6",
|
||||||
@ -53,7 +48,6 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "5.53.0",
|
"@typescript-eslint/eslint-plugin": "5.53.0",
|
||||||
"@vitejs/plugin-vue": "4.0.0",
|
"@vitejs/plugin-vue": "4.0.0",
|
||||||
"autoprefixer": "10.4.13",
|
"autoprefixer": "10.4.13",
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"eslint": "8.34.0",
|
"eslint": "8.34.0",
|
||||||
"eslint-config-standard-with-typescript": "34.0.0",
|
"eslint-config-standard-with-typescript": "34.0.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
@ -66,6 +60,7 @@
|
|||||||
"unplugin-auto-import": "0.13.0",
|
"unplugin-auto-import": "0.13.0",
|
||||||
"unplugin-vue-components": "0.23.0",
|
"unplugin-vue-components": "0.23.0",
|
||||||
"vite": "4.1.0",
|
"vite": "4.1.0",
|
||||||
"vue-tsc": "1.0.24"
|
"vue-tsc": "1.0.24",
|
||||||
|
"cross-env": "^7.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,47 +1,40 @@
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const ROOT = path.resolve(__dirname)
|
const publish = () => {
|
||||||
const src = path.join(ROOT, 'dist')
|
const src = './dist'
|
||||||
const dest = path.resolve(ROOT, process.env.PUBLISH_DEST || '../niucloud/public/admin')
|
const dest = '../niucloud/public/admin'
|
||||||
|
|
||||||
function fixIndexHtml() {
|
solve()
|
||||||
const fn = path.join(src, 'index.html')
|
|
||||||
if (!fs.existsSync(fn)) {
|
|
||||||
const core = path.join(src, '.core', 'index.html')
|
|
||||||
if (fs.existsSync(core)) {
|
|
||||||
fs.copyFileSync(core, fn)
|
|
||||||
} else {
|
|
||||||
throw new Error('missing dist/index.html — run assemble or build:core first')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let text = fs.readFileSync(fn, 'utf-8')
|
|
||||||
text = text.replaceAll('./assets/', '/admin/assets/')
|
|
||||||
text = text.replace('./niucloud.ico', '/admin/niucloud.ico')
|
|
||||||
fs.writeFileSync(fn, text, 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
function publish() {
|
// 目标目录不存在停止复制
|
||||||
fixIndexHtml()
|
try {
|
||||||
|
const dir = fs.readdirSync(dest)
|
||||||
if (!fs.existsSync(dest)) {
|
} catch (e) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.rmSync(dest, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
// 删除目标目录下文件
|
||||||
fs.cpSync(src, dest, {
|
fs.rm(dest, { recursive: true }, err => {
|
||||||
recursive: true,
|
if(err) {
|
||||||
force: true,
|
console.log(err)
|
||||||
filter: (srcPath) => {
|
return
|
||||||
const relative = path.relative(src, srcPath)
|
}
|
||||||
const parts = relative.split(path.sep)
|
|
||||||
return !parts.includes('.core') && !parts.includes('.shared')
|
fs.cp(src, dest, { recursive: true }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const shopEntry = path.join(dest, 'assets', 'addons', 'shop', 'index.js')
|
const solve = () => {
|
||||||
console.log(`[publish] ${src} -> ${dest}`)
|
const fn = './dist/index.html'
|
||||||
console.log(`[publish] shop addon: ${fs.existsSync(shopEntry) ? 'OK' : 'MISSING — run node scripts/sync-addon.cjs shop'}`)
|
const fc = fs.readFileSync(fn, 'utf-8')
|
||||||
|
let text = new String(fc)
|
||||||
|
text = text.replaceAll('./assets/', '/admin/assets/')
|
||||||
|
text = text.replace('./niucloud.ico', '/admin/niucloud.ico')
|
||||||
|
fs.writeFileSync(fn, text, 'utf8')
|
||||||
}
|
}
|
||||||
|
|
||||||
publish()
|
publish()
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* 插件构建公共工具
|
|
||||||
* - 扫描 src/addon 下目录、视图、语言包
|
|
||||||
* - 供 generate-addon-entry / build-all / assemble 等脚本复用
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
/** 项目根目录(admin/) */
|
|
||||||
const ROOT = path.resolve(__dirname, '..')
|
|
||||||
/** 插件源码根目录 */
|
|
||||||
const ADDON_DIR = path.join(ROOT, 'src', 'addon')
|
|
||||||
|
|
||||||
/** 列出 src/addon 下所有插件 key(目录名) */
|
|
||||||
function listAddonKeys() {
|
|
||||||
if (!fs.existsSync(ADDON_DIR)) return []
|
|
||||||
return fs.readdirSync(ADDON_DIR, { withFileTypes: true })
|
|
||||||
.filter((d) => d.isDirectory())
|
|
||||||
.map((d) => d.name)
|
|
||||||
.sort()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 递归遍历目录,收集满足 filter 的文件相对路径 */
|
|
||||||
function walkFiles(dir, filter, base = dir) {
|
|
||||||
const out = []
|
|
||||||
if (!fs.existsSync(dir)) return out
|
|
||||||
for (const name of fs.readdirSync(dir)) {
|
|
||||||
const full = path.join(dir, name)
|
|
||||||
const stat = fs.statSync(full)
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
out.push(...walkFiles(full, filter, base))
|
|
||||||
} else if (filter(full)) {
|
|
||||||
out.push(path.relative(base, full).replace(/\\/g, '/'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扫描插件 views 目录,返回视图路径列表(不含 .vue)
|
|
||||||
* 例:order/list、goods/category
|
|
||||||
*/
|
|
||||||
function scanAddonViews(key) {
|
|
||||||
const viewsDir = path.join(ADDON_DIR, key, 'views')
|
|
||||||
return walkFiles(viewsDir, (f) => f.endsWith('.vue'), viewsDir)
|
|
||||||
.map((p) => p.replace(/\.vue$/, ''))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扫描插件 lang 目录
|
|
||||||
* @returns {Record<string, string[]>} locale -> json 文件名(不含 .json)
|
|
||||||
* 例:{ 'zh-cn': ['common', 'order.list'], 'en': ['common'] }
|
|
||||||
*/
|
|
||||||
function scanAddonLang(key) {
|
|
||||||
const langDir = path.join(ADDON_DIR, key, 'lang')
|
|
||||||
const out = {}
|
|
||||||
if (!fs.existsSync(langDir)) return out
|
|
||||||
for (const locale of fs.readdirSync(langDir)) {
|
|
||||||
const localeDir = path.join(langDir, locale)
|
|
||||||
if (!fs.statSync(localeDir).isDirectory()) continue
|
|
||||||
out[locale] = fs.readdirSync(localeDir)
|
|
||||||
.filter((f) => f.endsWith('.json'))
|
|
||||||
.map((f) => f.replace(/\.json$/, ''))
|
|
||||||
.sort()
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { ROOT, ADDON_DIR, listAddonKeys, walkFiles, scanAddonViews, scanAddonLang }
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* 修复构建产物中错误的 admin-lang import 路径
|
|
||||||
*
|
|
||||||
* Rollup external @/lang 时可能生成相对路径,从 /admin/assets/*.js 解析会变成:
|
|
||||||
* ../admin/assets/shared/admin-lang.js → /admin/admin/assets/...(HTML 404)
|
|
||||||
* ../shared/admin-lang.js → /admin/shared/...(HTML 404)
|
|
||||||
*
|
|
||||||
* 被 verify-core-lang、assemble-admin、check-deploy 引用。
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ADMIN_LANG_URL } = require('./shared-external.cjs')
|
|
||||||
|
|
||||||
/** [错误路径, 正确绝对路径] */
|
|
||||||
const REPLACEMENTS = [
|
|
||||||
['../admin/assets/shared/admin-lang.js', ADMIN_LANG_URL],
|
|
||||||
['../shared/admin-lang.js', ADMIN_LANG_URL],
|
|
||||||
['./shared/admin-lang.js', ADMIN_LANG_URL]
|
|
||||||
]
|
|
||||||
|
|
||||||
/** 递归收集目录下所有 .js 文件 */
|
|
||||||
function walkJs(dir, out = []) {
|
|
||||||
if (!fs.existsSync(dir)) return out
|
|
||||||
const stat = fs.statSync(dir)
|
|
||||||
if (!stat.isDirectory()) return out
|
|
||||||
for (const name of fs.readdirSync(dir)) {
|
|
||||||
const p = path.join(dir, name)
|
|
||||||
if (fs.statSync(p).isDirectory()) walkJs(p, out)
|
|
||||||
else if (name.endsWith('.js')) out.push(p)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixCode(code) {
|
|
||||||
let next = code
|
|
||||||
for (const [bad, good] of REPLACEMENTS) {
|
|
||||||
if (next.includes(bad)) next = next.split(bad).join(good)
|
|
||||||
}
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasBadImport(code) {
|
|
||||||
return REPLACEMENTS.some(([bad]) => code.includes(bad))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 扫描目录内 JS 并就地替换错误路径
|
|
||||||
* @returns 修改过的文件数量
|
|
||||||
*/
|
|
||||||
function fixDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
throw new Error(`[admin-lang-import] directory not found: ${dir}`)
|
|
||||||
}
|
|
||||||
if (!fs.statSync(dir).isDirectory()) {
|
|
||||||
throw new Error(`[admin-lang-import] not a directory: ${dir}`)
|
|
||||||
}
|
|
||||||
let fixed = 0
|
|
||||||
for (const file of walkJs(dir)) {
|
|
||||||
const code = fs.readFileSync(file, 'utf-8')
|
|
||||||
if (!hasBadImport(code)) continue
|
|
||||||
fs.writeFileSync(file, fixCode(code), 'utf-8')
|
|
||||||
fixed++
|
|
||||||
}
|
|
||||||
return fixed
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { fixCode, hasBadImport, REPLACEMENTS, ADMIN_LANG_URL, fixDir }
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
/**
|
|
||||||
* 组装最终可部署 dist/
|
|
||||||
*
|
|
||||||
* 将 staging 目录合并为站点可访问结构:
|
|
||||||
* dist/.addons/* → dist/assets/addons/{key}/
|
|
||||||
* dist/.core/* → dist/(index.html、assets 等,跳过 addons 子目录)
|
|
||||||
* dist/.shared/* → dist/assets/shared/
|
|
||||||
*
|
|
||||||
* 并注入 Import Map、修正 /admin 路径、修复 admin-lang import、剥离多余 style import。
|
|
||||||
*
|
|
||||||
* 用法:npm run build:assemble(需先 build-shared + build:core + build-addon)
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT, listAddonKeys } = require('./addon-utils.cjs')
|
|
||||||
const { IMPORT_MAP } = require('./shared-external.cjs')
|
|
||||||
const { stripBuiltAssets } = require('./strip-style-imports.cjs')
|
|
||||||
|
|
||||||
const CORE_DIR = path.join(ROOT, 'dist', '.core')
|
|
||||||
const ADDONS_STAGING = path.join(ROOT, 'dist', '.addons')
|
|
||||||
const OUT_DIR = path.join(ROOT, 'dist')
|
|
||||||
const SHARED_DIR = path.join(ROOT, 'dist', '.shared')
|
|
||||||
const REPORT_PATH = path.join(ROOT, 'build-report.json')
|
|
||||||
|
|
||||||
function toWinLongPath(dir) {
|
|
||||||
if (process.platform !== 'win32') return dir
|
|
||||||
if (dir.startsWith('\\\\?\\')) return dir
|
|
||||||
return `\\\\?\\${path.resolve(dir)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function rmDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) return
|
|
||||||
try {
|
|
||||||
fs.rmSync(toWinLongPath(dir), { recursive: true, force: true, maxRetries: 8, retryDelay: 300 })
|
|
||||||
} catch {
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 8, retryDelay: 300 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Windows 下 rename 容易因文件锁失败,加重试 + 降级为 copy+delete */
|
|
||||||
function safeRename(src, dest, maxRetries = 5) {
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
|
||||||
try {
|
|
||||||
fs.renameSync(src, dest)
|
|
||||||
return
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code === 'EPERM' || e.code === 'EBUSY') {
|
|
||||||
if (i < maxRetries - 1) {
|
|
||||||
console.log(`[assemble] rename busy, retry ${i + 1}/${maxRetries}...`)
|
|
||||||
const s = Date.now(); while (Date.now() - s < 500) {}
|
|
||||||
} else {
|
|
||||||
// 最终降级:copy + delete
|
|
||||||
copyDir(src, dest)
|
|
||||||
rmDir(src)
|
|
||||||
console.log(`[assemble] rename failed, fallback to copy+delete`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyDir(src, dest) {
|
|
||||||
fs.mkdirSync(dest, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(src)) {
|
|
||||||
const s = path.join(src, name)
|
|
||||||
const d = path.join(dest, name)
|
|
||||||
if (fs.statSync(s).isDirectory()) copyDir(s, d)
|
|
||||||
else fs.copyFileSync(s, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyTree(src, dest) {
|
|
||||||
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
||||||
fs.cpSync(src, dest, { recursive: true, force: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理 dist/assets 下旧的 core 产物,保留 addons(shared 随后从 .shared 重拷)
|
|
||||||
* Vite 每次构建 chunk hash 会变,不清理会留下上一版同名不同 hash 的孤儿文件
|
|
||||||
*/
|
|
||||||
function cleanCoreAssetsOutput() {
|
|
||||||
const outAssets = path.join(OUT_DIR, 'assets')
|
|
||||||
if (!fs.existsSync(outAssets)) {
|
|
||||||
fs.mkdirSync(outAssets, { recursive: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const addonsSrc = path.join(outAssets, 'addons')
|
|
||||||
const addonsTmp = path.join(OUT_DIR, '.addons_staging')
|
|
||||||
if (fs.existsSync(addonsTmp)) rmDir(addonsTmp)
|
|
||||||
if (fs.existsSync(addonsSrc)) {
|
|
||||||
safeRename(addonsSrc, addonsTmp)
|
|
||||||
}
|
|
||||||
if (fs.existsSync(outAssets)) {
|
|
||||||
const staleAssets = path.join(ROOT, '.build', '.assets_stale_' + Date.now())
|
|
||||||
try { safeRename(outAssets, staleAssets) } catch { rmDir(outAssets) }
|
|
||||||
}
|
|
||||||
fs.mkdirSync(outAssets, { recursive: true })
|
|
||||||
if (fs.existsSync(addonsTmp)) {
|
|
||||||
safeRename(addonsTmp, path.join(outAssets, 'addons'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并 core 产物到 dist 根目录
|
|
||||||
* 注意:跳过 core/assets/addons,避免覆盖已同步的插件目录
|
|
||||||
*/
|
|
||||||
function mergeCoreOutput() {
|
|
||||||
for (const name of ['index.html', 'manifest.json', 'niucloud.ico']) {
|
|
||||||
const src = path.join(CORE_DIR, name)
|
|
||||||
if (fs.existsSync(src)) {
|
|
||||||
fs.copyFileSync(src, path.join(OUT_DIR, name))
|
|
||||||
console.log(`[assemble] core ${name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const ueditor = path.join(CORE_DIR, 'ueditor')
|
|
||||||
if (fs.existsSync(ueditor)) {
|
|
||||||
copyTree(ueditor, path.join(OUT_DIR, 'ueditor'))
|
|
||||||
console.log('[assemble] core ueditor/')
|
|
||||||
}
|
|
||||||
const coreAssets = path.join(CORE_DIR, 'assets')
|
|
||||||
const outAssets = path.join(OUT_DIR, 'assets')
|
|
||||||
if (!fs.existsSync(coreAssets)) return
|
|
||||||
fs.mkdirSync(outAssets, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(coreAssets)) {
|
|
||||||
if (name === 'addons') continue
|
|
||||||
const src = path.join(coreAssets, name)
|
|
||||||
const dest = path.join(outAssets, name)
|
|
||||||
if (fs.statSync(src).isDirectory()) {
|
|
||||||
copyDir(src, dest)
|
|
||||||
} else {
|
|
||||||
fs.copyFileSync(src, dest)
|
|
||||||
}
|
|
||||||
console.log(`[assemble] core assets/${name}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyLang(key, destAddonDir) {
|
|
||||||
const langSrc = path.join(ROOT, 'src', 'addon', key, 'lang')
|
|
||||||
if (!fs.existsSync(langSrc)) return
|
|
||||||
const langDest = path.join(destAddonDir, 'lang')
|
|
||||||
copyDir(langSrc, langDest)
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeAddonManifest(key, destAddonDir) {
|
|
||||||
const manifest = {
|
|
||||||
key,
|
|
||||||
version: '1.0.0',
|
|
||||||
sharedVersion: 'admin-core-1.0.0',
|
|
||||||
entry: './index.js',
|
|
||||||
langBase: './lang/'
|
|
||||||
}
|
|
||||||
fs.writeFileSync(path.join(destAddonDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Vite 默认相对路径 ./assets/ → 部署绝对路径 /admin/assets/ */
|
|
||||||
function fixIndexHtml() {
|
|
||||||
const fn = path.join(OUT_DIR, 'index.html')
|
|
||||||
if (!fs.existsSync(fn)) return
|
|
||||||
let text = fs.readFileSync(fn, 'utf-8')
|
|
||||||
text = text.replaceAll('./assets/', '/admin/assets/')
|
|
||||||
text = text.replace('./niucloud.ico', '/admin/niucloud.ico')
|
|
||||||
fs.writeFileSync(fn, text, 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 注入 Import Map,使 vue / @/lang 等解析到 shared 单例 */
|
|
||||||
function injectImportMap() {
|
|
||||||
const fn = path.join(OUT_DIR, 'index.html')
|
|
||||||
if (!fs.existsSync(fn)) return
|
|
||||||
let text = fs.readFileSync(fn, 'utf-8')
|
|
||||||
const script = `<script type="importmap">\n${JSON.stringify({ imports: IMPORT_MAP }, null, 2)}\n</script>`
|
|
||||||
if (text.includes('type="importmap"')) {
|
|
||||||
text = text.replace(/<script type="importmap">[\s\S]*?<\/script>/, script)
|
|
||||||
} else {
|
|
||||||
text = text.replace('<head>', `<head>\n ${script}`)
|
|
||||||
}
|
|
||||||
fs.writeFileSync(fn, text, 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理 dist 根下旧文件,保留 staging(.core/.addons/.shared)与 assets
|
|
||||||
* addon 子目录在 runAssemble 里按 key 单独 rm,避免 Windows ENOTEMPTY
|
|
||||||
*/
|
|
||||||
function cleanAssembledOutput() {
|
|
||||||
if (!fs.existsSync(OUT_DIR)) {
|
|
||||||
fs.mkdirSync(OUT_DIR, { recursive: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const keep = new Set(['.core', '.addons', '.shared', '.addons_staging', 'assets'])
|
|
||||||
for (const name of fs.readdirSync(OUT_DIR)) {
|
|
||||||
if (keep.has(name)) continue
|
|
||||||
// 历史 trash 目录不阻塞构建,可手动删除 dist/assets.__trash_*
|
|
||||||
if (name.includes('.__trash_') || name.startsWith('assets.__')) continue
|
|
||||||
rmDir(path.join(OUT_DIR, name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Element Plus 语言包,importmap 中 locale 路径指向此处 */
|
|
||||||
function copySharedLocales() {
|
|
||||||
const srcBase = path.join(ROOT, 'node_modules', 'element-plus', 'dist', 'locale')
|
|
||||||
const destBase = path.join(OUT_DIR, 'assets', 'shared', 'locale')
|
|
||||||
fs.mkdirSync(destBase, { recursive: true })
|
|
||||||
for (const file of ['zh-cn.mjs', 'en.mjs']) {
|
|
||||||
const src = path.join(srcBase, file)
|
|
||||||
if (fs.existsSync(src)) {
|
|
||||||
fs.copyFileSync(src, path.join(destBase, file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
try {
|
|
||||||
runAssemble()
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[assemble] ERROR:', err)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function runAssemble() {
|
|
||||||
const report = fs.existsSync(REPORT_PATH)
|
|
||||||
? JSON.parse(fs.readFileSync(REPORT_PATH, 'utf-8'))
|
|
||||||
: { success: listAddonKeys(), failed: [] }
|
|
||||||
|
|
||||||
cleanAssembledOutput()
|
|
||||||
|
|
||||||
if (!fs.existsSync(CORE_DIR)) {
|
|
||||||
console.error('[assemble] missing dist/.core — run core build first')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const successKeys = report.success || []
|
|
||||||
const installedKeys = []
|
|
||||||
|
|
||||||
// 先同步 addon,避免后续 core 合并耗时过长时 assets/addons 仍为空
|
|
||||||
for (const key of successKeys) {
|
|
||||||
const src = path.join(ADDONS_STAGING, key)
|
|
||||||
if (!fs.existsSync(src)) continue
|
|
||||||
const dest = path.join(OUT_DIR, 'assets', 'addons', key)
|
|
||||||
rmDir(dest)
|
|
||||||
copyTree(src, dest)
|
|
||||||
copyLang(key, dest)
|
|
||||||
writeAddonManifest(key, dest)
|
|
||||||
const entryFile = path.join(dest, 'index.js')
|
|
||||||
if (!fs.existsSync(entryFile)) {
|
|
||||||
console.error(`[assemble] missing ${key}/index.js — run: node scripts/build-addon.cjs ${key}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
installedKeys.push(key)
|
|
||||||
console.log(`[assemble] addon "${key}" -> assets/addons/${key}/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const indexJson = { keys: installedKeys, sharedVersion: 'admin-core-1.0.0' }
|
|
||||||
fs.mkdirSync(path.join(OUT_DIR, 'assets', 'addons'), { recursive: true })
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(OUT_DIR, 'assets', 'addons', 'index.json'),
|
|
||||||
JSON.stringify(indexJson, null, 2) + '\n',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
|
|
||||||
cleanCoreAssetsOutput()
|
|
||||||
mergeCoreOutput()
|
|
||||||
|
|
||||||
// 修复 Rollup 生成的错误 admin-lang 相对路径(否则会请求到 HTML 404)
|
|
||||||
const { fixDir } = require('./admin-lang-import-utils.cjs')
|
|
||||||
fixDir(path.join(OUT_DIR, 'assets'))
|
|
||||||
|
|
||||||
if (fs.existsSync(SHARED_DIR)) {
|
|
||||||
const sharedDest = path.join(OUT_DIR, 'assets', 'shared')
|
|
||||||
fs.mkdirSync(sharedDest, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(SHARED_DIR)) {
|
|
||||||
copyTree(path.join(SHARED_DIR, name), path.join(sharedDest, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 SFC 组件 wrapper:同时提供 named 和 default 导出
|
|
||||||
// addon 的 Components() 插件生成 import { X } (命名),默认导入 import X from 也同样支持
|
|
||||||
const { SFC_WRAPPERS } = require('./shared-external.cjs')
|
|
||||||
for (const [fileName, exportName] of Object.entries(SFC_WRAPPERS)) {
|
|
||||||
const wrapperPath = path.join(sharedDest, fileName)
|
|
||||||
fs.writeFileSync(
|
|
||||||
wrapperPath,
|
|
||||||
`export { ${exportName} as default, ${exportName} } from './core-shared.js'\n`,
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
console.log(`[assemble] shared -> assets/shared/ (${Object.keys(SFC_WRAPPERS).length} SFC wrappers generated)`)
|
|
||||||
} else {
|
|
||||||
console.warn('[assemble] missing dist/.shared — run build-shared first')
|
|
||||||
}
|
|
||||||
copySharedLocales()
|
|
||||||
|
|
||||||
// core 合并可能覆盖 addons/index.json,需再次写入
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(OUT_DIR, 'assets', 'addons', 'index.json'),
|
|
||||||
JSON.stringify(indexJson, null, 2) + '\n',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
for (const key of installedKeys) {
|
|
||||||
writeAddonManifest(key, path.join(OUT_DIR, 'assets', 'addons', key))
|
|
||||||
}
|
|
||||||
|
|
||||||
fixIndexHtml()
|
|
||||||
injectImportMap()
|
|
||||||
const cleaned = stripBuiltAssets(OUT_DIR)
|
|
||||||
if (cleaned.length) {
|
|
||||||
console.log(`[assemble] stripped element-plus style imports from ${cleaned.length} js files`)
|
|
||||||
}
|
|
||||||
console.log(`[assemble] done: core + ${installedKeys.length} addons`)
|
|
||||||
if (report.failed?.length) {
|
|
||||||
console.warn('[assemble] skipped failed addons:', report.failed.map((f) => f.key).join(', '))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* 编译单个 addon
|
|
||||||
*
|
|
||||||
* 流程:generate-addon-entry → vite lib build → sync-addon
|
|
||||||
* 输出 staging:dist/.addons/{key}/
|
|
||||||
* 同步部署目录:dist/assets/addons/{key}/
|
|
||||||
*
|
|
||||||
* 用法:
|
|
||||||
* node scripts/build-addon.cjs shop
|
|
||||||
* ADDON_KEY=shop node scripts/build-addon.cjs
|
|
||||||
*/
|
|
||||||
const { spawnSync } = require('child_process')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT } = require('./addon-utils.cjs')
|
|
||||||
|
|
||||||
const key = process.argv[2] || process.env.ADDON_KEY
|
|
||||||
if (!key) {
|
|
||||||
console.error('Usage: node build-addon.cjs <addon-key>')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 .build/addons/{key}/entry.ts
|
|
||||||
require('./generate-addon-entry.cjs')
|
|
||||||
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
ADDON_KEY: key,
|
|
||||||
NODE_OPTIONS: process.env.NODE_OPTIONS || '--max-old-space-size=4096'
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[build-addon] building "${key}"...`)
|
|
||||||
const r = spawnSync('npx', ['vite', 'build', '--config', 'vite.config.addon.ts'], {
|
|
||||||
cwd: ROOT,
|
|
||||||
env,
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: process.platform === 'win32'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (r.status !== 0) {
|
|
||||||
console.error(`[build-addon] FAILED: ${key}`)
|
|
||||||
process.exit(r.status ?? 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制到 dist/assets/addons 并更新 build-report.json
|
|
||||||
const sync = spawnSync('node', ['scripts/sync-addon.cjs', key], {
|
|
||||||
cwd: ROOT,
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: process.platform === 'win32'
|
|
||||||
})
|
|
||||||
if (sync.status !== 0) {
|
|
||||||
process.exit(sync.status ?? 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[build-addon] OK: ${key}`)
|
|
||||||
process.exit(0)
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* 全量生产构建(npm run build)
|
|
||||||
*
|
|
||||||
* 1. build-shared 共享 ESM(vue、admin-lang 等)
|
|
||||||
* 2. vite core 主应用 → dist/.core
|
|
||||||
* 3. addon 并行编译 每个 src/addon/* → dist/.addons/*
|
|
||||||
* 4. assemble 合并为可部署 dist/
|
|
||||||
* 5. publish 复制到 PUBLISH_DEST(默认 ../niucloud/public/admin)
|
|
||||||
*/
|
|
||||||
const { spawnSync } = require('child_process')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT, listAddonKeys } = require('./addon-utils.cjs')
|
|
||||||
|
|
||||||
const REPORT_PATH = path.join(ROOT, 'build-report.json')
|
|
||||||
|
|
||||||
function run(cmd, args, env = {}) {
|
|
||||||
const r = spawnSync(cmd, args, {
|
|
||||||
cwd: ROOT,
|
|
||||||
env: { ...process.env, ...env },
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: process.platform === 'win32'
|
|
||||||
})
|
|
||||||
return r.status ?? 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 多 worker 从队列取 key 构建 addon,限制并发降低内存峰值 */
|
|
||||||
async function buildAddonsParallel(keys, concurrency = 2) {
|
|
||||||
const success = []
|
|
||||||
const failed = []
|
|
||||||
const queue = [...keys]
|
|
||||||
|
|
||||||
async function worker() {
|
|
||||||
while (queue.length) {
|
|
||||||
const key = queue.shift()
|
|
||||||
if (!key) break
|
|
||||||
const code = run('node', [path.join(__dirname, 'build-addon.cjs'), key])
|
|
||||||
if (code === 0) success.push(key)
|
|
||||||
else failed.push({ key, error: 'build failed' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const workers = Array.from({ length: Math.min(concurrency, keys.length) }, () => worker())
|
|
||||||
await Promise.all(workers)
|
|
||||||
return { success, failed }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
console.log('[build-all] step 1/5: shared deps')
|
|
||||||
const sharedCode = run('node', [path.join(__dirname, 'build-shared.cjs')])
|
|
||||||
if (sharedCode !== 0) {
|
|
||||||
console.error('[build-all] shared build failed')
|
|
||||||
process.exit(sharedCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[build-all] step 2/5: core build')
|
|
||||||
const coreCode = run('npx', ['vite', 'build', '--config', 'vite.config.core.ts'], {
|
|
||||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
|
||||||
})
|
|
||||||
if (coreCode !== 0) {
|
|
||||||
console.error('[build-all] core build failed')
|
|
||||||
process.exit(coreCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = listAddonKeys()
|
|
||||||
console.log(`[build-all] step 3/5: addon builds (${keys.length} addons, concurrency 2)`)
|
|
||||||
const { success, failed } = await buildAddonsParallel(keys, 2)
|
|
||||||
|
|
||||||
fs.writeFileSync(REPORT_PATH, JSON.stringify({ success, failed, time: new Date().toISOString() }, null, 2) + '\n', 'utf-8')
|
|
||||||
console.log(`[build-all] addons: ${success.length} ok, ${failed.length} failed`)
|
|
||||||
|
|
||||||
console.log('[build-all] step 4/5: assemble')
|
|
||||||
const asmCode = run('node', [path.join(__dirname, 'assemble-admin.cjs')])
|
|
||||||
if (asmCode !== 0) process.exit(asmCode)
|
|
||||||
|
|
||||||
console.log('[build-all] step 5/5: publish')
|
|
||||||
const pubCode = run('node', [path.join(ROOT, 'publish.cjs')])
|
|
||||||
if (pubCode !== 0) process.exit(pubCode)
|
|
||||||
|
|
||||||
console.log('[build-all] complete')
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((e) => {
|
|
||||||
console.error(e)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
/**
|
|
||||||
* 构建浏览器 ESM 共享依赖 → dist/.shared/
|
|
||||||
*
|
|
||||||
* 产物经 assemble 复制到 dist/assets/shared/,由 Import Map 加载。
|
|
||||||
* admin-lang 将 src/lang 打成独立包,供 core 与 addon 共用 t()。
|
|
||||||
*
|
|
||||||
* 用法:npm run build:shared
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { spawnSync } = require('child_process')
|
|
||||||
const { ROOT } = require('./addon-utils.cjs')
|
|
||||||
const { SHARED_BUILD_ORDER } = require('./shared-external.cjs')
|
|
||||||
const { scanDirExports, resolveConflicts, buildReExportStatements } = require('./scan-exports.cjs')
|
|
||||||
|
|
||||||
/** 临时入口目录,每次构建前重写 */
|
|
||||||
const SHARED_SRC = path.join(ROOT, '.build', 'shared')
|
|
||||||
/** Vite shared 构建 staging 输出 */
|
|
||||||
const OUT_DIR = path.join(ROOT, 'dist', '.shared')
|
|
||||||
|
|
||||||
/** 各 shared 包的极简 re-export 入口(由 vite.config.shared.ts 打包) */
|
|
||||||
const ENTRY_CONTENT = {
|
|
||||||
vue: "import * as Vue from 'vue'\nexport * from 'vue'\nexport default Vue\n",
|
|
||||||
'vue-router': "export * from 'vue-router'\n",
|
|
||||||
pinia: "export * from 'pinia'\n",
|
|
||||||
'element-plus': "import * as ElementPlus from 'element-plus'\nexport * from 'element-plus'\nexport default ElementPlus\n",
|
|
||||||
'icons-vue': "export * from '@element-plus/icons-vue'\n",
|
|
||||||
axios: "export { default } from 'axios'\nexport * from 'axios'\n",
|
|
||||||
'vue-i18n': "export * from 'vue-i18n/dist/vue-i18n.esm-bundler.js'\n",
|
|
||||||
'admin-lang': "export { language, t, default } from '../../src/lang/index.ts'\n",
|
|
||||||
'core-shared': '' // 动态生成,从 API 目录扫描
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeEntries() {
|
|
||||||
fs.mkdirSync(SHARED_SRC, { recursive: true })
|
|
||||||
|
|
||||||
// 通用冲突映射:{ "module:name" -> "module_name" }
|
|
||||||
const allConflicts = {}
|
|
||||||
|
|
||||||
// 1a. 扫描 app/api 目录(排除 addon.ts / module.ts)
|
|
||||||
const apiDir = path.join(ROOT, 'src', 'app', 'api')
|
|
||||||
const apiFiles = scanDirExports(apiDir, { exclude: ['addon.ts', 'module.ts'] })
|
|
||||||
const { resolved: apiResolved, conflicts: apiConflicts } = resolveConflicts(apiFiles)
|
|
||||||
Object.assign(allConflicts, apiConflicts)
|
|
||||||
const apiExports = buildReExportStatements(apiResolved, '../../src/app/api')
|
|
||||||
|
|
||||||
// 1b. 扫描 utils 目录中被重导出的文件
|
|
||||||
const utilsDir = path.join(ROOT, 'src', 'utils')
|
|
||||||
const utilsExclude = ['common.ts', 'request.ts', 'storage.ts', 'config.ts', 'test.ts',
|
|
||||||
'directives.ts', 'addon-loader.ts', 'addon-lang.ts', 'tianditu.ts', 'qqmap.ts']
|
|
||||||
const utilsFiles = scanDirExports(utilsDir, { exclude: utilsExclude })
|
|
||||||
const { resolved: utilsResolved, conflicts: utilsConflicts } = resolveConflicts(utilsFiles)
|
|
||||||
Object.assign(allConflicts, utilsConflicts)
|
|
||||||
const utilsExports = buildReExportStatements(utilsResolved, '../../src/utils')
|
|
||||||
|
|
||||||
// 2. 生成 core-shared 入口
|
|
||||||
ENTRY_CONTENT['core-shared'] = [
|
|
||||||
"export * from '../../src/utils/common'",
|
|
||||||
"import _request from '../../src/utils/request'",
|
|
||||||
"import _storage from '../../src/utils/storage'",
|
|
||||||
"import _config from '../../src/utils/config'",
|
|
||||||
"import _test from '../../src/utils/test'",
|
|
||||||
"export { _request as request }",
|
|
||||||
"export { _storage as storage }",
|
|
||||||
"export { _config as config }",
|
|
||||||
"export { _test as test }",
|
|
||||||
"export default _request",
|
|
||||||
"export * from '../../src/utils/qqmap'",
|
|
||||||
...utilsExports,
|
|
||||||
...apiExports,
|
|
||||||
"export { default as UploadImage } from '../../src/components/upload-image/index.vue'",
|
|
||||||
"export { default as DiyPage } from '../../src/components/diy-page/index.vue'",
|
|
||||||
"export { default as MapSelector } from '../../src/components/map-selector/index.vue'",
|
|
||||||
"export { default as Editor } from '../../src/components/editor/index.vue'"
|
|
||||||
].join('\n')
|
|
||||||
|
|
||||||
for (const [key, content] of Object.entries(ENTRY_CONTENT)) {
|
|
||||||
fs.writeFileSync(path.join(SHARED_SRC, `${key}.ts`), content, 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 保存冲突映射
|
|
||||||
if (Object.keys(allConflicts).length) {
|
|
||||||
fs.writeFileSync(path.join(ROOT, '.build', 'export-conflicts.json'), JSON.stringify(allConflicts, null, 2), 'utf-8')
|
|
||||||
console.log(`[build-shared] conflicts: ${Object.keys(allConflicts).length} entries`)
|
|
||||||
}
|
|
||||||
if (Object.keys(apiConflicts).length) {
|
|
||||||
fs.writeFileSync(path.join(ROOT, '.build', 'api-conflicts.json'), JSON.stringify(apiConflicts, null, 2), 'utf-8')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 构建单个 shared 包;仅第一个包 emptyOutDir 清空 .shared */
|
|
||||||
function runBuild(pkgKey, emptyOutDir) {
|
|
||||||
const r = spawnSync('npx', ['vite', 'build', '--config', 'vite.config.shared.ts'], {
|
|
||||||
cwd: ROOT,
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
SHARED_PKG: pkgKey,
|
|
||||||
SHARED_EMPTY: emptyOutDir ? '1' : '0',
|
|
||||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
|
||||||
},
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: process.platform === 'win32'
|
|
||||||
})
|
|
||||||
if ((r.status ?? 1) !== 0) {
|
|
||||||
console.error(`[build-shared] failed: ${pkgKey}`)
|
|
||||||
process.exit(r.status ?? 1)
|
|
||||||
}
|
|
||||||
console.log(`[build-shared] ok: ${pkgKey}.js`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
writeEntries()
|
|
||||||
fs.mkdirSync(OUT_DIR, { recursive: true })
|
|
||||||
|
|
||||||
SHARED_BUILD_ORDER.forEach((pkgKey, i) => {
|
|
||||||
runBuild(pkgKey, i === 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`[build-shared] done -> ${path.relative(ROOT, OUT_DIR)}/`)
|
|
||||||
|
|
||||||
// 若 dist/assets 已存在(曾 assemble 过),顺便同步一份 shared 便于单独调试
|
|
||||||
const distAssets = path.join(ROOT, 'dist', 'assets', 'shared')
|
|
||||||
if (fs.existsSync(path.join(ROOT, 'dist', 'assets'))) {
|
|
||||||
fs.mkdirSync(distAssets, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(OUT_DIR)) {
|
|
||||||
if (!name.endsWith('.js')) continue
|
|
||||||
fs.copyFileSync(path.join(OUT_DIR, name), path.join(distAssets, name))
|
|
||||||
}
|
|
||||||
console.log(`[build-shared] synced -> dist/assets/shared/`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* 部署目录健康检查
|
|
||||||
*
|
|
||||||
* 校验 public/admin 是否具备 split 架构所需文件:
|
|
||||||
* - index.html 入口、shared、admin-lang.js
|
|
||||||
* - addons/index.json、shop 等插件
|
|
||||||
* - 无错误 admin-lang 相对路径、无重复 createI18n
|
|
||||||
*
|
|
||||||
* 用法:node scripts/check-deploy.cjs [deploy-dir]
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const DEPLOY = process.argv[2] || 'D:/AppData/phpstudy_pro/WWW/test/niucloud/public/admin'
|
|
||||||
const issues = []
|
|
||||||
const ok = []
|
|
||||||
|
|
||||||
function check(name, pass, detail) {
|
|
||||||
if (pass) ok.push(`[OK] ${name}${detail ? ': ' + detail : ''}`)
|
|
||||||
else issues.push(`[FAIL] ${name}${detail ? ': ' + detail : ''}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(DEPLOY)) {
|
|
||||||
console.error('deploy dir not found:', DEPLOY)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = fs.readFileSync(path.join(DEPLOY, 'index.html'), 'utf-8')
|
|
||||||
const entryMatch = html.match(/\/admin\/assets\/(index-[a-f0-9]+\.js)/)
|
|
||||||
const entry = entryMatch ? entryMatch[1] : null
|
|
||||||
check('index.html entry', !!entry, entry || 'no index-*.js in script src')
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
const entryPath = path.join(DEPLOY, 'assets', entry)
|
|
||||||
check('entry file exists', fs.existsSync(entryPath))
|
|
||||||
if (fs.existsSync(entryPath)) {
|
|
||||||
const code = fs.readFileSync(entryPath, 'utf-8')
|
|
||||||
check('core not stale', !code.includes('loadAddonCommonLocales'), 'must not contain loadAddonCommonLocales')
|
|
||||||
check('core has addon loader', code.includes('assets/addons') || code.includes('/admin/assets/addons'))
|
|
||||||
const oldPreload = /async function \w+\(\w+,t=\["zh-cn","en"\]\)/.test(code)
|
|
||||||
check('core preload not old-only-common', !oldPreload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSS hash 随构建变化;若 core 重建后此处失败,需同步更新此文件名
|
|
||||||
const css = 'index-c72b4d30.css'
|
|
||||||
check('entry css', fs.existsSync(path.join(DEPLOY, 'assets', css)), css)
|
|
||||||
|
|
||||||
for (const f of ['vue.js', 'element-plus.js', 'vue-i18n.js', 'admin-lang.js']) {
|
|
||||||
check('shared/' + f, fs.existsSync(path.join(DEPLOY, 'assets', 'shared', f)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 抽样检查 shop 列表页是否走共享 admin-lang
|
|
||||||
const shopList = fs.existsSync(path.join(DEPLOY, 'assets', 'addons', 'shop'))
|
|
||||||
? fs.readdirSync(path.join(DEPLOY, 'assets', 'addons', 'shop')).find((n) => n.startsWith('list-') && n.endsWith('.js'))
|
|
||||||
: null
|
|
||||||
if (shopList) {
|
|
||||||
const code = fs.readFileSync(path.join(DEPLOY, 'assets', 'addons', 'shop', shopList), 'utf-8')
|
|
||||||
check('shop list uses shared t()', code.includes('/admin/assets/shared/admin-lang.js'))
|
|
||||||
check('shop list not duplicate i18n', !code.includes('createI18n'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const addonsIndex = path.join(DEPLOY, 'assets', 'addons', 'index.json')
|
|
||||||
check('addons/index.json', fs.existsSync(addonsIndex))
|
|
||||||
if (fs.existsSync(addonsIndex)) {
|
|
||||||
const data = JSON.parse(fs.readFileSync(addonsIndex, 'utf-8'))
|
|
||||||
check('addons keys', Array.isArray(data.keys) && data.keys.length > 0, data.keys?.join(', '))
|
|
||||||
}
|
|
||||||
|
|
||||||
const shopIndex = path.join(DEPLOY, 'assets', 'addons', 'shop', 'index.js')
|
|
||||||
check('shop/index.js', fs.existsSync(shopIndex))
|
|
||||||
if (fs.existsSync(shopIndex)) {
|
|
||||||
const shop = fs.readFileSync(shopIndex, 'utf-8')
|
|
||||||
check('shop exports langs', shop.includes('langs'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hasBadImport } = require('./admin-lang-import-utils.cjs')
|
|
||||||
let badLangImports = 0
|
|
||||||
const assetsDir = path.join(DEPLOY, 'assets')
|
|
||||||
if (fs.existsSync(assetsDir)) {
|
|
||||||
for (const name of fs.readdirSync(assetsDir)) {
|
|
||||||
if (!name.endsWith('.js')) continue
|
|
||||||
if (hasBadImport(fs.readFileSync(path.join(assetsDir, name), 'utf-8'))) badLangImports++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check('no bad admin-lang import paths', badLangImports === 0, badLangImports ? `${badLangImports} core chunk(s)` : '')
|
|
||||||
|
|
||||||
const staleEntry = path.join(DEPLOY, 'assets', 'index-d2fee835.js')
|
|
||||||
if (fs.existsSync(staleEntry)) {
|
|
||||||
issues.push('[WARN] old bundle still present: index-d2fee835.js (harmless if index.html points to new entry)')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n=== Deploy check:', DEPLOY, '===\n')
|
|
||||||
ok.forEach((l) => console.log(l))
|
|
||||||
issues.forEach((l) => console.log(l))
|
|
||||||
console.log('\nSummary:', ok.length, 'ok,', issues.length, 'issue(s)')
|
|
||||||
process.exit(issues.some((l) => l.startsWith('[FAIL]')) ? 1 : 0)
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* 删除 dist/.core(core 构建 staging)
|
|
||||||
*
|
|
||||||
* build:core 在 vite build 前执行,避免 Windows 上旧 chunk 残留导致 hash 不一致。
|
|
||||||
* 使用 rename → 再删 的策略,降低文件被占用时 rmSync 失败的概率。
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const core = path.join(__dirname, '..', 'dist', '.core')
|
|
||||||
|
|
||||||
function rmDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) return
|
|
||||||
const trash = `${dir}.__trash_${Date.now()}`
|
|
||||||
try {
|
|
||||||
fs.renameSync(dir, trash)
|
|
||||||
} catch {
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fs.rmSync(trash, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
||||||
}
|
|
||||||
|
|
||||||
rmDir(core)
|
|
||||||
console.log('[clean-core] removed dist/.core')
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
* 发布 dist 到目标 admin 目录(保留已 drop-in 的插件)
|
|
||||||
*
|
|
||||||
* 用法:
|
|
||||||
* node scripts/deploy-to.cjs [deploy-dir]
|
|
||||||
* node scripts/deploy-to.cjs "D:/path/to/public/admin"
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT } = require('./addon-utils.cjs')
|
|
||||||
const { fixDir } = require('./admin-lang-import-utils.cjs')
|
|
||||||
|
|
||||||
const src = path.join(ROOT, 'dist')
|
|
||||||
const dest = path.resolve(process.argv[2] || process.env.PUBLISH_DEST || path.join(ROOT, '../niucloud/public/admin'))
|
|
||||||
const backup = path.join(ROOT, '.deploy_addons_backup')
|
|
||||||
|
|
||||||
function rmDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) return
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 8, retryDelay: 200 })
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyTree(from, to) {
|
|
||||||
fs.mkdirSync(path.dirname(to), { recursive: true })
|
|
||||||
fs.cpSync(from, to, { recursive: true, force: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
function readKeys(addonsDir) {
|
|
||||||
const p = path.join(addonsDir, 'index.json')
|
|
||||||
if (!fs.existsSync(p)) return []
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(fs.readFileSync(p, 'utf-8'))
|
|
||||||
return Array.isArray(data.keys) ? data.keys : []
|
|
||||||
} catch {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeKeys(addonsDir, keys) {
|
|
||||||
fs.mkdirSync(addonsDir, { recursive: true })
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(addonsDir, 'index.json'),
|
|
||||||
JSON.stringify({ keys: [...new Set(keys)].sort(), sharedVersion: 'admin-core-1.0.0' }, null, 2) + '\n'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function listDropInKeys(addonsDir) {
|
|
||||||
if (!fs.existsSync(addonsDir)) return []
|
|
||||||
return fs.readdirSync(addonsDir).filter((name) => {
|
|
||||||
if (name === 'index.json' || name.startsWith('.')) return false
|
|
||||||
return fs.existsSync(path.join(addonsDir, name, 'index.js'))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
if (!fs.existsSync(path.join(src, 'index.html'))) {
|
|
||||||
console.error('[deploy] missing dist/index.html — run: npm run build:core')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(dest)) {
|
|
||||||
console.error(`[deploy] target not found: ${dest}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const destAddons = path.join(dest, 'assets', 'addons')
|
|
||||||
rmDir(backup)
|
|
||||||
if (fs.existsSync(destAddons)) {
|
|
||||||
copyTree(destAddons, backup)
|
|
||||||
console.log(`[deploy] backed up existing addons -> ${path.relative(ROOT, backup)}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const preserveKeys = [...new Set([...readKeys(backup), ...listDropInKeys(backup)])]
|
|
||||||
|
|
||||||
// 根目录静态文件
|
|
||||||
for (const name of ['index.html', 'manifest.json', 'niucloud.ico']) {
|
|
||||||
const f = path.join(src, name)
|
|
||||||
if (fs.existsSync(f)) fs.copyFileSync(f, path.join(dest, name))
|
|
||||||
}
|
|
||||||
const ueditor = path.join(src, 'ueditor')
|
|
||||||
if (fs.existsSync(ueditor)) {
|
|
||||||
rmDir(path.join(dest, 'ueditor'))
|
|
||||||
copyTree(ueditor, path.join(dest, 'ueditor'))
|
|
||||||
}
|
|
||||||
|
|
||||||
// core assets(跳过 addons,单独合并)
|
|
||||||
const srcAssets = path.join(src, 'assets')
|
|
||||||
const destAssets = path.join(dest, 'assets')
|
|
||||||
fs.mkdirSync(destAssets, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(srcAssets)) {
|
|
||||||
if (name === 'addons') continue
|
|
||||||
const from = path.join(srcAssets, name)
|
|
||||||
const to = path.join(destAssets, name)
|
|
||||||
rmDir(to)
|
|
||||||
copyTree(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并 addons:dist 构建产物 + 目标站 drop-in
|
|
||||||
const mergedAddons = path.join(destAssets, 'addons')
|
|
||||||
fs.mkdirSync(mergedAddons, { recursive: true })
|
|
||||||
const srcAddons = path.join(srcAssets, 'addons')
|
|
||||||
if (fs.existsSync(srcAddons)) {
|
|
||||||
copyTree(srcAddons, mergedAddons)
|
|
||||||
}
|
|
||||||
for (const key of preserveKeys) {
|
|
||||||
const from = path.join(backup, key)
|
|
||||||
const to = path.join(mergedAddons, key)
|
|
||||||
if (!fs.existsSync(path.join(from, 'index.js'))) continue
|
|
||||||
if (fs.existsSync(path.join(to, 'index.js'))) continue
|
|
||||||
copyTree(from, to)
|
|
||||||
console.log(`[deploy] preserved drop-in addon "${key}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = [...new Set([
|
|
||||||
...readKeys(mergedAddons),
|
|
||||||
...readKeys(backup),
|
|
||||||
...preserveKeys
|
|
||||||
])]
|
|
||||||
if (keys.length) writeKeys(mergedAddons, keys)
|
|
||||||
|
|
||||||
fixDir(destAssets)
|
|
||||||
rmDir(backup)
|
|
||||||
|
|
||||||
console.log(`[deploy] ${src} -> ${dest}`)
|
|
||||||
console.log(`[deploy] addons: ${keys.join(', ') || '(none)'}`)
|
|
||||||
const hasDropIn = fs.readFileSync(path.join(destAssets, 'shared', 'admin-lang.js'), 'utf-8').includes('drop-in entry')
|
|
||||||
console.log(`[deploy] drop-in runtime: ${hasDropIn ? 'OK' : 'MISSING — rebuild shared'}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
/**
|
|
||||||
* 构建前确保 esbuild 原生二进制可用。
|
|
||||||
*
|
|
||||||
* Windows 上 @esbuild/win32-x64/esbuild.exe 常被杀毒删除,或 npm postinstall
|
|
||||||
* 在二进制缺失时调用 install.js 形成死循环。本脚本直接从 npm 拉取 tgz 解压,
|
|
||||||
* 不依赖 esbuild/install.js。
|
|
||||||
*
|
|
||||||
* 若 npm install 因 esbuild postinstall 失败,请先:
|
|
||||||
* npm install --ignore-scripts
|
|
||||||
* node scripts/ensure-esbuild.cjs
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const https = require('https')
|
|
||||||
const zlib = require('zlib')
|
|
||||||
const { spawnSync, execFileSync } = require('child_process')
|
|
||||||
|
|
||||||
const ROOT = path.resolve(__dirname, '..')
|
|
||||||
const DEFAULT_ESBUILD_VERSION = '0.16.17'
|
|
||||||
|
|
||||||
function platformPackage() {
|
|
||||||
const key = `${process.platform} ${process.arch} ${require('os').endianness()}`
|
|
||||||
const map = {
|
|
||||||
'win32 x64 LE': { pkg: '@esbuild/win32-x64', subpath: 'esbuild.exe' },
|
|
||||||
'win32 ia32 LE': { pkg: '@esbuild/win32-ia32', subpath: 'esbuild.exe' },
|
|
||||||
'win32 arm64 LE': { pkg: '@esbuild/win32-arm64', subpath: 'esbuild.exe' },
|
|
||||||
'darwin x64 LE': { pkg: '@esbuild/darwin-x64', subpath: 'bin/esbuild' },
|
|
||||||
'darwin arm64 LE': { pkg: '@esbuild/darwin-arm64', subpath: 'bin/esbuild' },
|
|
||||||
'linux x64 LE': { pkg: '@esbuild/linux-x64', subpath: 'bin/esbuild' }
|
|
||||||
}
|
|
||||||
const hit = map[key]
|
|
||||||
if (!hit) throw new Error(`Unsupported platform for esbuild: ${key}`)
|
|
||||||
return hit
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveEsbuildVersion() {
|
|
||||||
const candidates = [
|
|
||||||
path.join(ROOT, 'node_modules', 'esbuild', 'package.json'),
|
|
||||||
path.join(ROOT, 'node_modules', 'vite', 'node_modules', 'esbuild', 'package.json')
|
|
||||||
]
|
|
||||||
for (const file of candidates) {
|
|
||||||
try {
|
|
||||||
return require(file).version
|
|
||||||
} catch {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DEFAULT_ESBUILD_VERSION
|
|
||||||
}
|
|
||||||
|
|
||||||
function platformBinaryPath() {
|
|
||||||
const { pkg, subpath } = platformPackage()
|
|
||||||
return path.join(ROOT, 'node_modules', ...pkg.split('/'), subpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadedBinaryPath(version) {
|
|
||||||
const { pkg, subpath } = platformPackage()
|
|
||||||
const libDir = path.join(ROOT, 'node_modules', 'esbuild', 'lib')
|
|
||||||
const base = path.basename(subpath)
|
|
||||||
return path.join(libDir, `downloaded-${pkg.replace('/', '-')}-${base}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function binaryCandidates(version) {
|
|
||||||
return [platformBinaryPath(), downloadedBinaryPath(version)]
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchBuffer(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
https.get(url, (res) => {
|
|
||||||
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
||||||
return fetchBuffer(res.headers.location).then(resolve, reject)
|
|
||||||
}
|
|
||||||
if (res.statusCode !== 200) {
|
|
||||||
return reject(new Error(`HTTP ${res.statusCode} for ${url}`))
|
|
||||||
}
|
|
||||||
const chunks = []
|
|
||||||
res.on('data', (c) => chunks.push(c))
|
|
||||||
res.on('end', () => resolve(Buffer.concat(chunks)))
|
|
||||||
}).on('error', reject)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractFileFromTarGzip(buffer, subpath) {
|
|
||||||
try {
|
|
||||||
buffer = zlib.unzipSync(buffer)
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`Invalid gzip: ${err.message}`)
|
|
||||||
}
|
|
||||||
const str = (i, n) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '')
|
|
||||||
let offset = 0
|
|
||||||
const target = `package/${subpath}`
|
|
||||||
while (offset < buffer.length) {
|
|
||||||
const name = str(offset, 100)
|
|
||||||
const size = parseInt(str(offset + 124, 12), 8)
|
|
||||||
offset += 512
|
|
||||||
if (!Number.isNaN(size)) {
|
|
||||||
if (name === target) return buffer.subarray(offset, offset + size)
|
|
||||||
offset += (size + 511) & ~511
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Not found in archive: ${target}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadPlatformBinary(version) {
|
|
||||||
const { pkg, subpath } = platformPackage()
|
|
||||||
const shortName = pkg.replace('@esbuild/', '')
|
|
||||||
const url = `https://registry.npmjs.org/${pkg}/-/${shortName}-${version}.tgz`
|
|
||||||
console.log(`[ensure-esbuild] downloading ${pkg}@${version} ...`)
|
|
||||||
const tgz = await fetchBuffer(url)
|
|
||||||
const data = extractFileFromTarGzip(tgz, subpath)
|
|
||||||
const dest = platformBinaryPath()
|
|
||||||
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
||||||
fs.writeFileSync(dest, data)
|
|
||||||
if (process.platform !== 'win32') fs.chmodSync(dest, 0o755)
|
|
||||||
console.log(`[ensure-esbuild] wrote ${path.relative(ROOT, dest)} (${data.length} bytes)`)
|
|
||||||
return dest
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToDownloadedCache(version, src) {
|
|
||||||
const cache = downloadedBinaryPath(version)
|
|
||||||
const libDir = path.dirname(cache)
|
|
||||||
if (!fs.existsSync(path.join(ROOT, 'node_modules', 'esbuild'))) return
|
|
||||||
fs.mkdirSync(libDir, { recursive: true })
|
|
||||||
fs.copyFileSync(src, cache)
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyBinary(binPath, version) {
|
|
||||||
const out = execFileSync(binPath, ['--version'], { stdio: 'pipe' }).toString().trim()
|
|
||||||
if (out !== version) {
|
|
||||||
throw new Error(`Expected esbuild ${version}, got ${out}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasWorkingBinary(version) {
|
|
||||||
for (const p of binaryCandidates(version)) {
|
|
||||||
try {
|
|
||||||
if (fs.existsSync(p) && fs.statSync(p).size > 1024) {
|
|
||||||
verifyBinary(p, version)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// try next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function repair(version) {
|
|
||||||
const dest = await downloadPlatformBinary(version)
|
|
||||||
copyToDownloadedCache(version, dest)
|
|
||||||
verifyBinary(dest, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const version = resolveEsbuildVersion()
|
|
||||||
const existing = hasWorkingBinary(version)
|
|
||||||
if (existing) {
|
|
||||||
console.log(`[ensure-esbuild] ok (${path.relative(ROOT, existing)}, v${version})`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[ensure-esbuild] binary missing or invalid, repairing (esbuild@${version})...`)
|
|
||||||
try {
|
|
||||||
await repair(version)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[ensure-esbuild] repair failed:', err.message || err)
|
|
||||||
console.error('')
|
|
||||||
console.error('Try:')
|
|
||||||
console.error(' npm install --ignore-scripts')
|
|
||||||
console.error(' node scripts/ensure-esbuild.cjs')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ok = hasWorkingBinary(version)
|
|
||||||
if (!ok) {
|
|
||||||
console.error('[ensure-esbuild] binary still not usable after repair.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
console.log('[ensure-esbuild] ok')
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((err) => {
|
|
||||||
console.error('[ensure-esbuild]', err)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* CLI:修复构建产物中的 admin-lang import 路径
|
|
||||||
*
|
|
||||||
* 默认扫描 dist/.core/assets 与 dist/assets;
|
|
||||||
* 部署后可对 public/admin/assets 单独执行。
|
|
||||||
*
|
|
||||||
* 用法:
|
|
||||||
* node scripts/fix-admin-lang-imports.cjs
|
|
||||||
* node scripts/fix-admin-lang-imports.cjs "D:/path/to/public/admin/assets"
|
|
||||||
*/
|
|
||||||
const path = require('path')
|
|
||||||
const { fixDir, ADMIN_LANG_URL } = require('./admin-lang-import-utils.cjs')
|
|
||||||
|
|
||||||
const targets = process.argv.slice(2)
|
|
||||||
const dirs = targets.length
|
|
||||||
? targets.map((d) => path.resolve(d))
|
|
||||||
: [
|
|
||||||
path.join(__dirname, '..', 'dist', '.core', 'assets'),
|
|
||||||
path.join(__dirname, '..', 'dist', 'assets')
|
|
||||||
]
|
|
||||||
|
|
||||||
let total = 0
|
|
||||||
for (const dir of dirs) {
|
|
||||||
try {
|
|
||||||
const n = fixDir(dir)
|
|
||||||
if (n) console.log(`[fix-admin-lang-imports] ${path.relative(process.cwd(), dir)}: ${n} file(s)`)
|
|
||||||
total += n
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err.message)
|
|
||||||
console.error('Usage: node scripts/fix-admin-lang-imports.cjs <assets-directory>')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total === 0) {
|
|
||||||
console.log('[fix-admin-lang-imports] no bad imports found')
|
|
||||||
} else {
|
|
||||||
console.log(`[fix-admin-lang-imports] fixed ${total} file(s) -> ${ADMIN_LANG_URL}`)
|
|
||||||
}
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
/**
|
|
||||||
* 为单个 addon 生成 Vite lib 构建入口:.build/addons/{key}/entry.ts
|
|
||||||
*
|
|
||||||
* 入口导出:
|
|
||||||
* - addonKey / sharedVersion
|
|
||||||
* - views:视图路径 → 动态 import 函数(运行时 loadAddonView 调用)
|
|
||||||
* - langs:各 locale 语言 json 静态 import(打进 index.js,启动时 preload)
|
|
||||||
*
|
|
||||||
* 由 build-addon.cjs 在 vite build 前调用。
|
|
||||||
* 用法:node scripts/generate-addon-entry.cjs <addon-key>
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT, ADDON_DIR, scanAddonViews, scanAddonLang } = require('./addon-utils.cjs')
|
|
||||||
const { scanRecursive, resolveConflicts } = require('./scan-exports.cjs')
|
|
||||||
|
|
||||||
const key = process.argv[2] || process.env.ADDON_KEY
|
|
||||||
if (!key) {
|
|
||||||
console.error('Usage: node generate-addon-entry.cjs <addon-key>')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const views = scanAddonViews(key)
|
|
||||||
const langMap = scanAddonLang(key)
|
|
||||||
const outDir = path.join(ROOT, '.build', 'addons', key)
|
|
||||||
fs.mkdirSync(outDir, { recursive: true })
|
|
||||||
|
|
||||||
// views 映射:'order/list' => () => import('.../order/list.vue')
|
|
||||||
const viewLines = views.map((v) =>
|
|
||||||
` '${v.replace(/'/g, "\\'")}': () => import('@/addon/${key}/views/${v}.vue'),`
|
|
||||||
).join('\n')
|
|
||||||
|
|
||||||
// components 映射:diy/poster/printer/delivery 等子目录下的可复用组件
|
|
||||||
// 供 loadAddonComponent / diy 编辑页在生产环境使用
|
|
||||||
const componentSubdirs = ['diy/components', 'diy_form/components', 'poster/components', 'printer/components', 'delivery/components']
|
|
||||||
const componentLines = []
|
|
||||||
for (const sub of componentSubdirs) {
|
|
||||||
const dir = path.join(ADDON_DIR, key, 'views', sub)
|
|
||||||
if (!fs.existsSync(dir)) continue
|
|
||||||
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.vue'))
|
|
||||||
for (const f of files) {
|
|
||||||
const name = f.replace('.vue', '')
|
|
||||||
componentLines.push(` '${sub}/${name}': () => import('@/addon/${key}/views/${sub}/${f}'),`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const componentBlock = componentLines.length ? componentLines.join('\n') : ''
|
|
||||||
|
|
||||||
// 插件 router/index.ts 中的 ROUTE/NO_LOGIN_ROUTES 不做 re-export
|
|
||||||
// 原因:
|
|
||||||
// 1. 生产环境下插件路由由 core 通过后端菜单 API 下发(src/router/index.ts:13 DEV only)
|
|
||||||
// 2. router/index.ts 通常引用 @/layout/index.vue,该文件又导入 @/utils/addon-loader
|
|
||||||
// 其中的 import.meta.glob 会匹配数百个核心 .vue 文件,全部打进插件包导致 404
|
|
||||||
let routerExport = ''
|
|
||||||
// 如需开发调试,设置 ADDON_ENTRY_DEV=1 环境变量
|
|
||||||
const isDev = process.env.ADDON_ENTRY_DEV === '1'
|
|
||||||
const routerPath = path.join(ADDON_DIR, key, 'router', 'index.ts')
|
|
||||||
if (fs.existsSync(routerPath)) {
|
|
||||||
const routerText = fs.readFileSync(routerPath, 'utf-8')
|
|
||||||
const routerExports = []
|
|
||||||
if (/export\s+(?:const|let|var)\s+ROUTE\b/.test(routerText) || /export\s*\{[^}]*\bROUTE\b/.test(routerText)) {
|
|
||||||
routerExports.push('ROUTE')
|
|
||||||
}
|
|
||||||
if (/export\s+(?:const|let|var)\s+NO_LOGIN_ROUTES\b/.test(routerText) || /export\s*\{[^}]*\bNO_LOGIN_ROUTES\b/.test(routerText)) {
|
|
||||||
routerExports.push('NO_LOGIN_ROUTES')
|
|
||||||
}
|
|
||||||
if (routerExports.length && isDev) {
|
|
||||||
routerExport = `export { ${routerExports.join(', ')} } from '@/addon/${key}/router/index.ts'\n`
|
|
||||||
}
|
|
||||||
if (routerExports.length && !isDev) {
|
|
||||||
console.log(`[addon-entry] ${key}: skipping ROUTE/NO_LOGIN_ROUTES re-export (prod: routes from backend API)`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 语言 json 文件名含点号,变量名需消毒为合法标识符 */
|
|
||||||
function safeImportVar(locale, file) {
|
|
||||||
return 'lang_' + `${locale}_${file}`.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
||||||
}
|
|
||||||
|
|
||||||
const langImports = []
|
|
||||||
const langByLocale = {}
|
|
||||||
for (const [locale, files] of Object.entries(langMap)) {
|
|
||||||
langByLocale[locale] = []
|
|
||||||
for (const file of files) {
|
|
||||||
const varName = safeImportVar(locale, file)
|
|
||||||
langImports.push(`import ${varName} from '@/addon/${key}/lang/${locale}/${file}.json'`)
|
|
||||||
langByLocale[locale].push(` '${file.replace(/'/g, "\\'")}': ${varName}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const langsBlock = Object.keys(langByLocale).length
|
|
||||||
? `export const langs: Record<string, Record<string, Record<string, string>>> = {\n${Object.entries(langByLocale).map(([locale, entries]) =>
|
|
||||||
` '${locale.replace(/'/g, "\\'")}': {\n${entries.join(',\n')}\n }`
|
|
||||||
).join(',\n')
|
|
||||||
}\n}\n`
|
|
||||||
: 'export const langs: Record<string, Record<string, Record<string, string>>> = {}\n'
|
|
||||||
|
|
||||||
const langCount = Object.values(langMap).reduce((n, files) => n + files.length, 0)
|
|
||||||
|
|
||||||
// 插件内部导出冲突检测:扫描 ts/js,告警同名导出及入口保留名冲突
|
|
||||||
const addonSrcDir = path.join(ADDON_DIR, key)
|
|
||||||
const addonFiles = scanRecursive(addonSrcDir, { exclude: ['lang', 'views', 'components'], extensions: ['.ts', '.js'] })
|
|
||||||
const { conflicts: addonConflicts, resolved: addonResolved } = resolveConflicts(addonFiles)
|
|
||||||
const entryReserved = new Set(['addonKey', 'sharedVersion', 'views', 'components', 'langs', 'ROUTE', 'NO_LOGIN_ROUTES'])
|
|
||||||
const routerFileKey = 'router_index'
|
|
||||||
const reservedConflicts = addonResolved.filter(r =>
|
|
||||||
r.normal.some(n => entryReserved.has(n) && r.module !== routerFileKey)
|
|
||||||
)
|
|
||||||
if (reservedConflicts.length) {
|
|
||||||
console.warn(`[addon-entry] ${key}: exports conflicting with entry reserved names:`)
|
|
||||||
reservedConflicts.forEach(r => r.normal.filter(n => entryReserved.has(n)).forEach(n =>
|
|
||||||
console.warn(` ${r.module} exports "${n}" which is reserved`)))
|
|
||||||
}
|
|
||||||
if (Object.keys(addonConflicts).length) {
|
|
||||||
console.warn(`[addon-entry] ${key}: ${Object.keys(addonConflicts).length} internal export conflict(s)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = `/* AUTO-GENERATED entry for addon "${key}" */
|
|
||||||
${langImports.join('\n')}
|
|
||||||
|
|
||||||
export const addonKey = '${key}'
|
|
||||||
export const sharedVersion = 'admin-core-1.0.0'
|
|
||||||
|
|
||||||
export const views: Record<string, () => Promise<any>> = {
|
|
||||||
${viewLines}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const components: Record<string, () => Promise<any>> = {
|
|
||||||
${componentBlock}
|
|
||||||
}
|
|
||||||
|
|
||||||
${langsBlock}${routerExport}`
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(outDir, 'entry.ts'), content, 'utf-8')
|
|
||||||
console.log(`[addon-entry] ${key}: ${views.length} views, ${langCount} lang files -> ${path.relative(ROOT, outDir)}/entry.ts`)
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* 手动 drop-in 插件后登记到 assets/addons/index.json(可选)
|
|
||||||
*
|
|
||||||
* 场景:从另一个 admin 项目拷贝 assets/addons/{key}/ 到目标站点。
|
|
||||||
* 运行时也会在登录后根据后端已安装插件 + 菜单自动探测 index.js,不强制依赖本脚本。
|
|
||||||
*
|
|
||||||
* 用法:
|
|
||||||
* node scripts/register-addon.cjs <addon-key> [deploy-dir]
|
|
||||||
* 示例:
|
|
||||||
* node scripts/register-addon.cjs sd_minsu
|
|
||||||
* node scripts/register-addon.cjs sd_minsu "D:/path/to/public/admin"
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT } = require('./addon-utils.cjs')
|
|
||||||
|
|
||||||
const key = process.argv[2]
|
|
||||||
const deployDir = process.argv[3] ? path.resolve(process.argv[3]) : path.join(ROOT, 'dist')
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
console.error('Usage: node scripts/register-addon.cjs <addon-key> [deploy-dir]')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addonDir = path.join(deployDir, 'assets', 'addons', key)
|
|
||||||
const indexPath = path.join(deployDir, 'assets', 'addons', 'index.json')
|
|
||||||
const entryPath = path.join(addonDir, 'index.js')
|
|
||||||
const manifestPath = path.join(addonDir, 'manifest.json')
|
|
||||||
|
|
||||||
if (!fs.existsSync(entryPath)) {
|
|
||||||
console.error(`[register-addon] missing ${entryPath}`)
|
|
||||||
console.error('[register-addon] copy the full addon folder (index.js + chunks + lang/) first')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
|
||||||
const manifest = {
|
|
||||||
key,
|
|
||||||
version: '1.0.0',
|
|
||||||
sharedVersion: 'admin-core-1.0.0',
|
|
||||||
entry: './index.js',
|
|
||||||
langBase: './lang/'
|
|
||||||
}
|
|
||||||
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8')
|
|
||||||
console.log(`[register-addon] wrote manifest.json`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = { keys: [], sharedVersion: 'admin-core-1.0.0' }
|
|
||||||
if (fs.existsSync(indexPath)) {
|
|
||||||
try {
|
|
||||||
data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'))
|
|
||||||
} catch {
|
|
||||||
console.warn('[register-addon] index.json parse failed, recreating')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = new Set(Array.isArray(data.keys) ? data.keys : [])
|
|
||||||
const before = keys.size
|
|
||||||
keys.add(key)
|
|
||||||
data.keys = [...keys].sort()
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(indexPath), { recursive: true })
|
|
||||||
fs.writeFileSync(indexPath, JSON.stringify(data, null, 2) + '\n', 'utf-8')
|
|
||||||
|
|
||||||
console.log(`[register-addon] ${key} registered in assets/addons/index.json (${before} -> ${data.keys.length} keys)`)
|
|
||||||
console.log(`[register-addon] keys: ${data.keys.join(', ')}`)
|
|
||||||
console.log('[register-addon] reminder: backend must also install this addon (menu API returns addon=sd_minsu)')
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
/**
|
|
||||||
* 统一用 node 直接启动 vite(避免 Windows 上 npx + shell 触发 spawn EPERM)
|
|
||||||
*
|
|
||||||
* 用法:node scripts/run-vite.cjs build --config vite.config.shared.ts
|
|
||||||
*/
|
|
||||||
const path = require('path')
|
|
||||||
const { spawnSync } = require('child_process')
|
|
||||||
|
|
||||||
const ROOT = path.resolve(__dirname, '..')
|
|
||||||
const VITE_BIN = path.join(ROOT, 'node_modules', 'vite', 'bin', 'vite.js')
|
|
||||||
|
|
||||||
require('./ensure-esbuild.cjs')
|
|
||||||
|
|
||||||
const args = process.argv.slice(2)
|
|
||||||
if (!args.length) {
|
|
||||||
console.error('Usage: node scripts/run-vite.cjs <vite-args...>')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = spawnSync(process.execPath, [VITE_BIN, ...args], {
|
|
||||||
cwd: ROOT,
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
NODE_OPTIONS: process.env.NODE_OPTIONS || '--max-old-space-size=4096'
|
|
||||||
},
|
|
||||||
stdio: 'inherit',
|
|
||||||
shell: false
|
|
||||||
})
|
|
||||||
|
|
||||||
process.exit(r.status ?? 1)
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* 通用导出冲突扫描器
|
|
||||||
* 扫描目录下 .ts/.js 文件的 export,检测同名冲突,返回冲突映射。
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const DEFAULT_EXTENSIONS = ['.ts', '.js', '.tsx', '.jsx']
|
|
||||||
|
|
||||||
/** 从源码中提取所有命名导出名称 */
|
|
||||||
function scanExports(src) {
|
|
||||||
const names = []
|
|
||||||
// export const|let|var|function|class|type|interface|enum NAME
|
|
||||||
const declRe = /export\s+(?:const|let|var|function|class|type|interface|enum)\s+(\w+)/g
|
|
||||||
for (const m of src.matchAll(declRe)) names.push(m[1])
|
|
||||||
// export { a, b, c as d }
|
|
||||||
const braceRe = /export\s*(?:type\s*)?\{([^}]+)\}/g
|
|
||||||
for (const m of src.matchAll(braceRe)) {
|
|
||||||
for (const item of m[1].split(',').map(s => s.trim()).filter(Boolean)) {
|
|
||||||
const parts = item.split(/\s+(?:as|:)\s+/)
|
|
||||||
names.push(parts.length > 1 ? parts[parts.length - 1].trim() : parts[0].trim())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...new Set(names)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 扫描目录,返回文件列表 */
|
|
||||||
function scanDirExports(dir, opts = {}) {
|
|
||||||
const exclude = new Set(opts.exclude || [])
|
|
||||||
const exts = new Set(opts.extensions || DEFAULT_EXTENSIONS)
|
|
||||||
if (!fs.existsSync(dir)) return []
|
|
||||||
const out = []
|
|
||||||
for (const f of fs.readdirSync(dir)) {
|
|
||||||
const full = path.join(dir, f)
|
|
||||||
if (!fs.statSync(full).isFile()) continue
|
|
||||||
if (![...exts].some(e => f.endsWith(e))) continue
|
|
||||||
if (exclude.has(f) || exclude.has(full)) continue
|
|
||||||
out.push({ moduleKey: f.replace(/\.[^.]+$/, ''), path: full })
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 递归扫描目录 */
|
|
||||||
function scanRecursive(dir, opts = {}) {
|
|
||||||
const exclude = new Set(opts.exclude || [])
|
|
||||||
const exts = new Set(opts.extensions || DEFAULT_EXTENSIONS)
|
|
||||||
const out = []
|
|
||||||
if (!fs.existsSync(dir)) return out
|
|
||||||
function walk(base, rel) {
|
|
||||||
for (const name of fs.readdirSync(base)) {
|
|
||||||
const relPath = rel ? `${rel}/${name}` : name
|
|
||||||
if (exclude.has(relPath) || exclude.has(name)) continue
|
|
||||||
const full = path.join(base, name)
|
|
||||||
const stat = fs.statSync(full)
|
|
||||||
if (stat.isDirectory()) walk(full, relPath)
|
|
||||||
else if (stat.isFile() && [...exts].some(e => name.endsWith(e)))
|
|
||||||
out.push({ moduleKey: relPath.replace(/\.[^.]+$/, '').replace(/[\\/]/g, '_'), path: full })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walk(dir, '')
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测并解决导出冲突。按 moduleKey 字母序处理,
|
|
||||||
* 先导出的保留原名,后出现的加 moduleKey 前缀。
|
|
||||||
*/
|
|
||||||
function resolveConflicts(files, opts = {}) {
|
|
||||||
const sorted = [...files].sort((a, b) => a.moduleKey.localeCompare(b.moduleKey))
|
|
||||||
const exportedNames = new Set()
|
|
||||||
const conflicts = {}
|
|
||||||
const resolved = []
|
|
||||||
for (const file of sorted) {
|
|
||||||
const src = fs.readFileSync(file.path, 'utf-8')
|
|
||||||
const names = scanExports(src)
|
|
||||||
const normal = []; const renamed = []
|
|
||||||
for (const name of names) {
|
|
||||||
if (!exportedNames.has(name)) { exportedNames.add(name); normal.push(name) }
|
|
||||||
else {
|
|
||||||
const newName = opts.prefixer ? opts.prefixer(file, name) : file.moduleKey + '_' + name
|
|
||||||
exportedNames.add(newName); renamed.push(name)
|
|
||||||
conflicts[file.moduleKey + ':' + name] = newName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolved.push({ module: file.moduleKey, normal, renamed, src: file.path })
|
|
||||||
}
|
|
||||||
return { resolved, conflicts }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 生成 re-export 语句 */
|
|
||||||
function buildReExportStatements(resolved, modulePathPrefix) {
|
|
||||||
const lines = []
|
|
||||||
for (const r of resolved) {
|
|
||||||
if (r.normal.length) lines.push(`export { ${r.normal.join(', ')} } from '${modulePathPrefix}/${r.module}'`)
|
|
||||||
if (r.renamed.length) {
|
|
||||||
const renamedStr = r.renamed.map(n => `${n} as ${r.module}_${n}`).join(', ')
|
|
||||||
lines.push(`export { ${renamedStr} } from '${modulePathPrefix}/${r.module}'`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { scanExports, scanDirExports, scanRecursive, resolveConflicts, buildReExportStatements }
|
|
||||||
@ -1,186 +0,0 @@
|
|||||||
/**
|
|
||||||
* Core / Addon 共用的「运行时 external」配置
|
|
||||||
*
|
|
||||||
* 生产构建时,vue / element-plus / @/lang 等不打进 bundle,
|
|
||||||
* 由 index.html 的 Import Map 指向 /admin/assets/shared/*.js,
|
|
||||||
* 保证全应用只有一份 Vue 实例与一套 i18n。
|
|
||||||
*/
|
|
||||||
const SHARED_PACKAGES = [
|
|
||||||
'vue',
|
|
||||||
'vue-router',
|
|
||||||
'pinia',
|
|
||||||
'element-plus',
|
|
||||||
'@element-plus/icons-vue',
|
|
||||||
'axios',
|
|
||||||
'vue-i18n',
|
|
||||||
'core-shared'
|
|
||||||
]
|
|
||||||
|
|
||||||
/** build-shared 按此顺序逐个 vite lib 构建(core-shared/admin-lang 最后) */
|
|
||||||
const SHARED_BUILD_ORDER = [
|
|
||||||
'vue',
|
|
||||||
'vue-router',
|
|
||||||
'pinia',
|
|
||||||
'element-plus',
|
|
||||||
'icons-vue',
|
|
||||||
'axios',
|
|
||||||
'vue-i18n',
|
|
||||||
'core-shared',
|
|
||||||
'admin-lang'
|
|
||||||
]
|
|
||||||
|
|
||||||
/** build-shared 包名 -> npm 模块名 */
|
|
||||||
const PKG_TO_MODULE = {
|
|
||||||
vue: 'vue',
|
|
||||||
'vue-router': 'vue-router',
|
|
||||||
pinia: 'pinia',
|
|
||||||
'element-plus': 'element-plus',
|
|
||||||
'icons-vue': '@element-plus/icons-vue',
|
|
||||||
axios: 'axios',
|
|
||||||
'vue-i18n': 'vue-i18n'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 全局 t() / language 的浏览器绝对路径(须与部署前缀 /admin 一致) */
|
|
||||||
const ADMIN_LANG_URL = '/admin/assets/shared/admin-lang.js'
|
|
||||||
const CORE_SHARED_URL = '/admin/assets/shared/core-shared.js'
|
|
||||||
|
|
||||||
/** 写入 index.html 的 importmap(assemble-admin 注入) */
|
|
||||||
const IMPORT_MAP = {
|
|
||||||
vue: '/admin/assets/shared/vue.js',
|
|
||||||
'vue-router': '/admin/assets/shared/vue-router.js',
|
|
||||||
pinia: '/admin/assets/shared/pinia.js',
|
|
||||||
'element-plus': '/admin/assets/shared/element-plus.js',
|
|
||||||
'element-plus/es': '/admin/assets/shared/element-plus.js',
|
|
||||||
'element-plus/dist/locale/zh-cn.mjs': '/admin/assets/shared/locale/zh-cn.mjs',
|
|
||||||
'element-plus/dist/locale/en.mjs': '/admin/assets/shared/locale/en.mjs',
|
|
||||||
'@element-plus/icons-vue': '/admin/assets/shared/icons-vue.js',
|
|
||||||
axios: '/admin/assets/shared/axios.js',
|
|
||||||
'vue-i18n': '/admin/assets/shared/vue-i18n.js',
|
|
||||||
'@/lang': ADMIN_LANG_URL,
|
|
||||||
'@/utils/common': CORE_SHARED_URL,
|
|
||||||
'@/utils/request': CORE_SHARED_URL,
|
|
||||||
'@/utils/storage': CORE_SHARED_URL,
|
|
||||||
'@/utils/config': CORE_SHARED_URL,
|
|
||||||
'@/utils/test': CORE_SHARED_URL,
|
|
||||||
'@/utils/qqmap': CORE_SHARED_URL,
|
|
||||||
'@/app/api/diy': CORE_SHARED_URL,
|
|
||||||
'@/app/api/sys': CORE_SHARED_URL,
|
|
||||||
'@/app/api/member': CORE_SHARED_URL,
|
|
||||||
'@/app/api/site': CORE_SHARED_URL,
|
|
||||||
'@/components/upload-image': CORE_SHARED_URL,
|
|
||||||
'@/components/diy-page': CORE_SHARED_URL,
|
|
||||||
'@/components/map-selector': CORE_SHARED_URL,
|
|
||||||
'@/components/editor': CORE_SHARED_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
/** SFC 组件映射:不在 external 中处理,直接打进插件 bundle */
|
|
||||||
const SFC_WRAPPERS = {}
|
|
||||||
|
|
||||||
/** 插件中 auto-import 的组件/模块,不 external,直接打进插件 */
|
|
||||||
const PACK_INTO_ADDON = new Set([
|
|
||||||
'utils/config', // export default → core-shared 的 export * 不会转发
|
|
||||||
'utils/storage', // export default → core-shared 的 export * 不会转发
|
|
||||||
'utils/tianditu' // 与 qqmap 同名导出冲突,由 addon 直接打包
|
|
||||||
])
|
|
||||||
|
|
||||||
function isSfcBundleComponent(id) {
|
|
||||||
const norm = id.replace(/\\/g, '/')
|
|
||||||
for (const name of PACK_INTO_ADDON) {
|
|
||||||
if (norm === `@/${name}` || norm.startsWith(`@/${name}/`)) return true
|
|
||||||
if (norm.endsWith(`/${name}.ts`)) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 所有需要 external 化并映射到 core-shared 的核心路径 */
|
|
||||||
const CORE_EXTERNAL_PATHS = Object.keys(IMPORT_MAP).filter(k => k.startsWith('@/'))
|
|
||||||
|
|
||||||
/** 是否将 @/lang 或 src/lang 标为 external(避免 addon/core 内嵌第二套 i18n) */
|
|
||||||
function isAdminLangExternal(id) {
|
|
||||||
const norm = id.replace(/\\/g, '/')
|
|
||||||
if (id === '@/lang' || id.startsWith('@/lang/')) return true
|
|
||||||
if (norm.includes('/src/lang/')) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Rollup output.paths 中 @/lang 解析到的 URL */
|
|
||||||
function adminLangExternalPath() {
|
|
||||||
return ADMIN_LANG_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 核心应用模块路径是否应 external(不打进 addon 包) */
|
|
||||||
function isCoreExternal(id) {
|
|
||||||
// 先处理带 query 参数的情况(如 ?vue&type=script)
|
|
||||||
const cleanId = id.split('?')[0]
|
|
||||||
const norm = cleanId.replace(/\\/g, '/')
|
|
||||||
// .vue 文件一律不 external(避免 default/named import 不匹配)
|
|
||||||
if (/\.vue$/.test(norm) && (norm.includes('/src/') || norm.startsWith('@/'))) return false
|
|
||||||
// @/components/ 下的组件一律不 external(unplugin-vue-components 解析的 import 可能不带 .vue 后缀)
|
|
||||||
if (norm === '@/components' || norm.startsWith('@/components/')) return false
|
|
||||||
// 已解析的 src/components/ 路径也一律不 external
|
|
||||||
if (/\/src\/components\//.test(norm)) return false
|
|
||||||
// utils/config 默认导出 → 不 external(core-shared 的 named export 不会转发 default)
|
|
||||||
if (norm === '@/utils/config' || norm === '@/utils/config.ts' || norm.endsWith('/src/utils/config.ts')) return false
|
|
||||||
if (isSfcBundleComponent(norm)) return false
|
|
||||||
// 直接匹配 @/ 别名形式(Vite resolveId 前的 source)
|
|
||||||
if (CORE_EXTERNAL_PATHS.some(p => norm === p || norm.startsWith(p + '/'))) return true
|
|
||||||
// 匹配已解析的文件路径(如 /path/src/utils/common.ts)
|
|
||||||
if (norm.includes('/src/') && !norm.includes('/src/addon/')) {
|
|
||||||
return /\/src\/(utils|app)\//.test(norm)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** core external 路径统一映射到 core-shared.js */
|
|
||||||
function coreExternalPath() {
|
|
||||||
return CORE_SHARED_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否将依赖标为 external(不打进当前 chunk)
|
|
||||||
* element-plus 的 locale / theme-chalk 除外,仍由各自包处理
|
|
||||||
*/
|
|
||||||
function isSharedExternal(id) {
|
|
||||||
const norm = id.replace(/\\/g, '/')
|
|
||||||
if (norm.includes('/style/css') || norm.includes('/style/index')) return false
|
|
||||||
if (norm.includes('element-plus/dist/')) return false
|
|
||||||
if (norm.includes('element-plus/theme-chalk')) return false
|
|
||||||
if (SHARED_PACKAGES.includes(id)) return true
|
|
||||||
if (SHARED_PACKAGES.some((p) => id === p || id.startsWith(p + '/'))) return true
|
|
||||||
if (!norm.includes('node_modules/')) return false
|
|
||||||
return SHARED_PACKAGES.some((p) => {
|
|
||||||
return norm.includes(`/node_modules/${p}/`) || norm.endsWith(`/node_modules/${p}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 构建 shared 单包时,不把「自身」再 external 掉 */
|
|
||||||
function isSelfSharedPackage(id, pkgKey) {
|
|
||||||
const mod = PKG_TO_MODULE[pkgKey]
|
|
||||||
if (!mod) return false
|
|
||||||
if (id === mod || id.startsWith(mod + '/')) return true
|
|
||||||
const norm = id.replace(/\\/g, '/')
|
|
||||||
return norm.includes(`/node_modules/${mod}/`) || norm.endsWith(`/node_modules/${mod}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** vite.config.shared.ts 用:external 除当前正在打的包以外的 shared 依赖 */
|
|
||||||
function sharedExternalForBuild(pkgKey) {
|
|
||||||
return (id) => isSharedExternal(id) && !isSelfSharedPackage(id, pkgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
SHARED_PACKAGES,
|
|
||||||
SHARED_BUILD_ORDER,
|
|
||||||
PKG_TO_MODULE,
|
|
||||||
IMPORT_MAP,
|
|
||||||
ADMIN_LANG_URL,
|
|
||||||
CORE_SHARED_URL,
|
|
||||||
SFC_WRAPPERS,
|
|
||||||
isSfcBundleComponent,
|
|
||||||
isSharedExternal,
|
|
||||||
isAdminLangExternal,
|
|
||||||
adminLangExternalPath,
|
|
||||||
isCoreExternal,
|
|
||||||
coreExternalPath,
|
|
||||||
isSelfSharedPackage,
|
|
||||||
sharedExternalForBuild
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* 从已构建 JS 中移除 element-plus style/css side-effect import
|
|
||||||
*
|
|
||||||
* assemble 最后一步兜底:vite 插件可能未覆盖到的 import 语句在此二次清理。
|
|
||||||
* 也可单独运行:node scripts/strip-style-imports.cjs [dist-root]
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const STYLE_IMPORT_RE = /import\s*["']element-plus[^"']*\/style\/css["'];?/g
|
|
||||||
|
|
||||||
function stripFile(filePath) {
|
|
||||||
const text = fs.readFileSync(filePath, 'utf-8')
|
|
||||||
if (!STYLE_IMPORT_RE.test(text)) return false
|
|
||||||
STYLE_IMPORT_RE.lastIndex = 0
|
|
||||||
const next = text.replace(STYLE_IMPORT_RE, '')
|
|
||||||
if (next === text) return false
|
|
||||||
fs.writeFileSync(filePath, next, 'utf-8')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function walkJs(dir, changed) {
|
|
||||||
if (!fs.existsSync(dir)) return
|
|
||||||
for (const name of fs.readdirSync(dir)) {
|
|
||||||
const full = path.join(dir, name)
|
|
||||||
if (fs.statSync(full).isDirectory()) {
|
|
||||||
walkJs(full, changed)
|
|
||||||
} else if (name.endsWith('.js')) {
|
|
||||||
if (stripFile(full)) changed.push(full)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param rootDir dist 根目录,递归处理 assets 下所有 .js */
|
|
||||||
function stripBuiltAssets(rootDir) {
|
|
||||||
const assetsDir = path.join(rootDir, 'assets')
|
|
||||||
const changed = []
|
|
||||||
walkJs(assetsDir, changed)
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
const root = process.argv[2] || path.join(__dirname, '..', 'dist')
|
|
||||||
const changed = stripBuiltAssets(root)
|
|
||||||
console.log(`[strip-style-imports] cleaned ${changed.length} files under ${root}/assets`)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { stripBuiltAssets, stripFile }
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
/**
|
|
||||||
* 将 dist/.addons/{key} 同步到 dist/assets/addons/{key}
|
|
||||||
*
|
|
||||||
* build-addon 成功后调用;也可单独执行以刷新已构建插件到 assets 目录。
|
|
||||||
* 同时写入 manifest.json、lang/ 备份、assets/addons/index.json,并更新 build-report.json。
|
|
||||||
*
|
|
||||||
* 用法:node scripts/sync-addon.cjs <addon-key>
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const { ROOT } = require('./addon-utils.cjs')
|
|
||||||
|
|
||||||
/** Vite addon 构建 staging */
|
|
||||||
const ADDONS_STAGING = path.join(ROOT, 'dist', '.addons')
|
|
||||||
const OUT_DIR = path.join(ROOT, 'dist')
|
|
||||||
/** 记录各 addon 构建成功/失败,assemble 据此决定复制哪些 key */
|
|
||||||
const REPORT_PATH = path.join(ROOT, 'build-report.json')
|
|
||||||
|
|
||||||
function rmDir(dir) {
|
|
||||||
if (!fs.existsSync(dir)) return
|
|
||||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyDir(src, dest) {
|
|
||||||
fs.mkdirSync(dest, { recursive: true })
|
|
||||||
for (const name of fs.readdirSync(src)) {
|
|
||||||
const s = path.join(src, name)
|
|
||||||
const d = path.join(dest, name)
|
|
||||||
if (fs.statSync(s).isDirectory()) copyDir(s, d)
|
|
||||||
else fs.copyFileSync(s, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 复制源码 lang 目录,供运行时 fetch json 降级或运维查阅 */
|
|
||||||
function copyLang(key, destAddonDir) {
|
|
||||||
const langSrc = path.join(ROOT, 'src', 'addon', key, 'lang')
|
|
||||||
if (!fs.existsSync(langSrc)) return
|
|
||||||
copyDir(langSrc, path.join(destAddonDir, 'lang'))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 运行时 initAddonManifests 会 fetch 此文件 */
|
|
||||||
function writeAddonManifest(key, destAddonDir) {
|
|
||||||
const manifest = {
|
|
||||||
key,
|
|
||||||
version: '1.0.0',
|
|
||||||
sharedVersion: 'admin-core-1.0.0',
|
|
||||||
entry: './index.js',
|
|
||||||
langBase: './lang/'
|
|
||||||
}
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(destAddonDir, 'manifest.json'),
|
|
||||||
JSON.stringify(manifest, null, 2) + '\n',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function readReport() {
|
|
||||||
if (!fs.existsSync(REPORT_PATH)) return { success: [], failed: [] }
|
|
||||||
return JSON.parse(fs.readFileSync(REPORT_PATH, 'utf-8'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeReport(report) {
|
|
||||||
fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2) + '\n', 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将 key 记入 success 列表,并从 failed 中移除 */
|
|
||||||
function markSuccess(key) {
|
|
||||||
const report = readReport()
|
|
||||||
const success = new Set(report.success || [])
|
|
||||||
success.add(key)
|
|
||||||
report.success = [...success].sort()
|
|
||||||
report.failed = (report.failed || []).filter((f) => f.key !== key)
|
|
||||||
writeReport(report)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 浏览器启动时 fetch:/admin/assets/addons/index.json */
|
|
||||||
function writeAddonsIndex(keys) {
|
|
||||||
const addonsDir = path.join(OUT_DIR, 'assets', 'addons')
|
|
||||||
fs.mkdirSync(addonsDir, { recursive: true })
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(addonsDir, 'index.json'),
|
|
||||||
JSON.stringify({ keys, sharedVersion: 'admin-core-1.0.0' }, null, 2) + '\n',
|
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncAddon(key) {
|
|
||||||
const src = path.join(ADDONS_STAGING, key)
|
|
||||||
if (!fs.existsSync(src)) {
|
|
||||||
console.error(`[sync-addon] missing staging: ${src}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
const entry = path.join(src, 'index.js')
|
|
||||||
if (!fs.existsSync(entry)) {
|
|
||||||
console.error(`[sync-addon] missing ${key}/index.js — rebuild addon first`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dest = path.join(OUT_DIR, 'assets', 'addons', key)
|
|
||||||
rmDir(dest)
|
|
||||||
fs.cpSync(src, dest, { recursive: true, force: true })
|
|
||||||
copyLang(key, dest)
|
|
||||||
writeAddonManifest(key, dest)
|
|
||||||
|
|
||||||
markSuccess(key)
|
|
||||||
const report = readReport()
|
|
||||||
writeAddonsIndex(report.success || [key])
|
|
||||||
|
|
||||||
console.log(`[sync-addon] ${key} -> dist/assets/addons/${key}/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = process.argv[2] || process.env.ADDON_KEY
|
|
||||||
if (!key) {
|
|
||||||
console.error('Usage: node scripts/sync-addon.cjs <addon-key>')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
syncAddon(key)
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Core 构建后校验(build:core 中 vite build 之后、assemble 之前执行)
|
|
||||||
*
|
|
||||||
* 检查项:
|
|
||||||
* 1. dist/.core/manifest.json 存在
|
|
||||||
* 2. 入口 bundle 不含旧版 loadAddonCommonLocales(防 stale 缓存)
|
|
||||||
* 3. 含新版 preloadAllAddonLangs 逻辑
|
|
||||||
* 4. 修复并校验 admin-lang import 路径
|
|
||||||
*/
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const core = path.join(__dirname, '..', 'dist', '.core')
|
|
||||||
const manifestPath = path.join(core, 'manifest.json')
|
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
|
||||||
console.error('[verify-core-lang] missing dist/.core/manifest.json — run build:core first')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
|
||||||
const entry = manifest['index.html']?.file
|
|
||||||
const code = fs.readFileSync(path.join(core, entry), 'utf-8')
|
|
||||||
|
|
||||||
// 旧构建特征:仅加载 common.json 或旧函数名
|
|
||||||
const stale = code.includes('loadAddonCommonLocales') || code.includes('async function os(e,t=["zh-cn","en"])')
|
|
||||||
const hasPreload = code.includes('preloadAllAddonLangs') || code.includes('.langs')
|
|
||||||
|
|
||||||
if (stale) {
|
|
||||||
console.error('[verify-core-lang] STALE bundle:', entry)
|
|
||||||
console.error(' still contains old loadAddonCommonLocales — delete dist/.core and rebuild')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasPreload) {
|
|
||||||
console.warn('[verify-core-lang] warn: preloadAllAddonLangs string not found (may be minified)')
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetsDir = path.join(core, 'assets')
|
|
||||||
const { fixDir, hasBadImport } = require('./admin-lang-import-utils.cjs')
|
|
||||||
fixDir(assetsDir)
|
|
||||||
if (fs.existsSync(assetsDir)) {
|
|
||||||
for (const name of fs.readdirSync(assetsDir)) {
|
|
||||||
if (!name.endsWith('.js')) continue
|
|
||||||
const code = fs.readFileSync(path.join(assetsDir, name), 'utf-8')
|
|
||||||
if (hasBadImport(code)) {
|
|
||||||
console.error('[verify-core-lang] bad admin-lang import still in', name)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[verify-core-lang] ok:', entry)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Vite 插件:构建期剥离 element-plus 按需 style/css import
|
|
||||||
*
|
|
||||||
* 背景:
|
|
||||||
* - 全局样式已在 src/styles/index.scss 引入 element-plus
|
|
||||||
* - Core/Addon external element-plus 后,残留的 style/css side-effect import
|
|
||||||
* 会进入 chunk 且浏览器无法通过 importmap 解析,导致运行时报错
|
|
||||||
*
|
|
||||||
* 用于 vite.config.core.ts / vite.config.addon.ts
|
|
||||||
*/
|
|
||||||
const STYLE_IMPORT_RE = /import\s*["']element-plus[^"']*["'];?/g
|
|
||||||
|
|
||||||
function isElementPlusStyleImport(source) {
|
|
||||||
const norm = source.replace(/\\/g, '/')
|
|
||||||
if (!norm.includes('element-plus')) return false
|
|
||||||
// 匹配 style/css、style/index、theme-chalk、dist/{comp}/style 等
|
|
||||||
if (/\/style\/(css|index)(\.mjs|\.js)?$/i.test(norm)) return true
|
|
||||||
if (/\/theme-chalk\//i.test(norm)) return true
|
|
||||||
if (/\/dist\/[^/]+\/style\//i.test(norm)) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripElementPlusStylePlugin() {
|
|
||||||
const EMPTY_ID = '\0element-plus-empty-style'
|
|
||||||
return {
|
|
||||||
name: 'strip-element-plus-style',
|
|
||||||
enforce: 'pre',
|
|
||||||
// resolve 阶段将 style import 指向空模块
|
|
||||||
resolveId(source) {
|
|
||||||
if (isElementPlusStyleImport(source)) return EMPTY_ID
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (id === EMPTY_ID) return 'export {}'
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
// generateBundle 再扫一遍 chunk 文本,删除遗漏的 import 语句
|
|
||||||
generateBundle(_, bundle) {
|
|
||||||
for (const item of Object.values(bundle)) {
|
|
||||||
if (item.type !== 'chunk') continue
|
|
||||||
item.code = item.code.replace(STYLE_IMPORT_RE, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { stripElementPlusStylePlugin, isElementPlusStyleImport }
|
|
||||||
@ -2,7 +2,7 @@ import axios from 'axios'
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
|
|
||||||
export const CLOUD_COMPILE_BASE_URL = location.protocol + '//go.site.niucloud.com'
|
export const CLOUD_COMPILE_BASE_URL = 'http://go.site.niucloud.com'
|
||||||
|
|
||||||
function createCloudCompileRequest() {
|
function createCloudCompileRequest() {
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
|
|||||||
@ -377,6 +377,13 @@ export function setPayConfig(params: Record<string, any>) {
|
|||||||
return request.put(`pay/config/${ params.type }`, params, { showSuccessMessage: true });
|
return request.put(`pay/config/${ params.type }`, params, { showSuccessMessage: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支付列表
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getPayList() {
|
||||||
|
return request.get(`pay/lists`)
|
||||||
|
}
|
||||||
|
|
||||||
/***************************************************** 打款设置 ****************************************************/
|
/***************************************************** 打款设置 ****************************************************/
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -114,6 +114,12 @@
|
|||||||
<div class="flex flex-col" v-show="active == 'backup'">
|
<div class="flex flex-col" v-show="active == 'backup'">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<div class="bg-[#fff] my-3">
|
<div class="bg-[#fff] my-3">
|
||||||
|
<div class="p-[20px] mt-[50px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]">
|
||||||
|
<div class="flex justify-between items-center mt-[-9px]">
|
||||||
|
<el-checkbox v-model="upgradeOption.is_need_cloudbuild" :label="t('upgrade.isNeedCloudbuild')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="text-[14px] text-[#374151] mb-[10px]">{{ t('upgrade.cloudbuildTips') }}</div>
|
||||||
|
</div>
|
||||||
<div class="p-[20px] mt-[20px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]" v-if="upgradeContent.last_backup">
|
<div class="p-[20px] mt-[20px] mx-[10px] border-[1px] border-[#E6E6E6] rounded-[10px]" v-if="upgradeContent.last_backup">
|
||||||
<div class="flex justify-between items-center mt-[-9px]">
|
<div class="flex justify-between items-center mt-[-9px]">
|
||||||
<el-checkbox v-model="upgradeOption.is_need_backup" :label="t('upgrade.isNeedBackup')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
|
<el-checkbox v-model="upgradeOption.is_need_backup" :label="t('upgrade.isNeedBackup')" :true-value="true" :false-value="false" size="large" ></el-checkbox>
|
||||||
@ -136,6 +142,7 @@
|
|||||||
<img src="@/app/assets/images/success_icon.png" alt="">
|
<img src="@/app/assets/images/success_icon.png" alt="">
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
|
<div class="text-[16px] text-[#4F516D] mt-[-5px]" v-show="upgradeTask && upgradeTask.executed && !upgradeTask.executed.includes('cloudBuild')">{{ t('upgrade.upgradeCompleteTips') }}</div>
|
||||||
<!-- <div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div> -->
|
<!-- <div class="text-[16px] text-[#9699B6] mt-[10px]">本次升级用时{{ formatUpgradeDuration }}</div> -->
|
||||||
<div class="w-[750px]" v-if="upgradeTask.cloud_build_error">
|
<div class="w-[750px]" v-if="upgradeTask.cloud_build_error">
|
||||||
<el-alert class="!w-[750px] border-warning !border-[1px] !rounded-[0px] border-solid" type="warning" :closable="false">
|
<el-alert class="!w-[750px] border-warning !border-[1px] !rounded-[0px] border-solid" type="warning" :closable="false">
|
||||||
@ -191,7 +198,8 @@
|
|||||||
<el-button v-if="step == 1 && upgradeContent.content.length && isAllowUpgrade" @click="step = 2" type="primary">{{ t("upgrade.upgradeButton") }}</el-button>
|
<el-button v-if="step == 1 && upgradeContent.content.length && isAllowUpgrade" @click="step = 2" type="primary">{{ t("upgrade.upgradeButton") }}</el-button>
|
||||||
<el-button type="primary" v-show="step == 2 && showTerminal && upgradeTask && !errorDialog" :loading="timeloading" class="!w-[140px]">已用时 {{ formatUpgradeDuration }}</el-button>
|
<el-button type="primary" v-show="step == 2 && showTerminal && upgradeTask && !errorDialog" :loading="timeloading" class="!w-[140px]">已用时 {{ formatUpgradeDuration }}</el-button>
|
||||||
<template v-if="step == 2 && active != 'complete'">
|
<template v-if="step == 2 && active != 'complete'">
|
||||||
<el-button type="primary" :disabled="!is_pass" v-if="active == 'upgrade' && !upgradeTask" @click="showUpgradeOption">{{ t("nextStep") }}</el-button>
|
<!-- <el-button v-if="active == 'content'" @click="showDialog = false">{{ t("return") }}</el-button>-->
|
||||||
|
<el-button type="primary" :disabled="!is_pass" v-if="active == 'upgrade' && !upgradeTask" @click="() => { active = 'backup'; numberOfSteps = 1 }">{{ t("nextStep") }}</el-button>
|
||||||
<el-button v-if="active == 'backup'" @click="() => { active = 'upgrade'; numberOfSteps = 1 } ">{{ t("prev") }}</el-button>
|
<el-button v-if="active == 'backup'" @click="() => { active = 'upgrade'; numberOfSteps = 1 } ">{{ t("prev") }}</el-button>
|
||||||
<el-button type="primary" v-if="active == 'backup'" :loading="loading" @click="() => { upgradeAddonFn() }">{{ t("nextStep") }}</el-button>
|
<el-button type="primary" v-if="active == 'backup'" :loading="loading" @click="() => { upgradeAddonFn() }">{{ t("nextStep") }}</el-button>
|
||||||
<el-button v-if="active == 'complete'" @click="showDialog = false">{{ t("complete") }}</el-button>
|
<el-button v-if="active == 'complete'" @click="showDialog = false">{{ t("complete") }}</el-button>
|
||||||
@ -275,7 +283,7 @@ const retrySecond = ref(30)
|
|||||||
let retrySecondInterval: any = null
|
let retrySecondInterval: any = null
|
||||||
const upgradeOption = ref({
|
const upgradeOption = ref({
|
||||||
is_need_backup: true,
|
is_need_backup: true,
|
||||||
is_need_cloudbuild: false
|
is_need_cloudbuild: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// 升级步骤排除,backupCode 备份代码,backupSql 备份数据库
|
// 升级步骤排除,backupCode 备份代码,backupSql 备份数据库
|
||||||
@ -535,15 +543,6 @@ const handleUpgrade = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showUpgradeOption = () => {
|
|
||||||
if (upgradeContent.value.last_backup) {
|
|
||||||
active.value = 'backup';
|
|
||||||
numberOfSteps.value = 1
|
|
||||||
} else {
|
|
||||||
upgradeAddonFn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const upgradeAddonFn = () => {
|
const upgradeAddonFn = () => {
|
||||||
if (!is_pass.value) return
|
if (!is_pass.value) return
|
||||||
if (loading.value) return
|
if (loading.value) return
|
||||||
@ -575,12 +574,6 @@ const upgradeAddonFn = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNeedUpgrade = (currVersion: string, onlineVersion: string) => {
|
|
||||||
if (isNaN(parseInt(onlineVersion.replaceAll('.', ''))) || isNaN(parseInt(currVersion.replaceAll('.', '')))) return false
|
|
||||||
return parseInt(onlineVersion.replaceAll('.', '')) > parseInt(currVersion.replaceAll('.', ''))
|
|
||||||
}
|
|
||||||
|
|
||||||
const dialogUpgradePrompt = ref(false)
|
const dialogUpgradePrompt = ref(false)
|
||||||
const applicationVersion = ref('')
|
const applicationVersion = ref('')
|
||||||
const newApplicationVersion = ref('')
|
const newApplicationVersion = ref('')
|
||||||
@ -595,7 +588,7 @@ const open = (addonKey: string = '', callback = null, otherData: any = {}) => {
|
|||||||
active.value = 'upgrade'
|
active.value = 'upgrade'
|
||||||
if (callback) callback()
|
if (callback) callback()
|
||||||
} else {
|
} else {
|
||||||
if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value && isNeedUpgrade(frameworkVersion.value, newFrameworkVersion.value)) {
|
if (addonKey && addonKey.indexOf('niucloud-admin') == -1 && frameworkVersion.value != newFrameworkVersion.value) {
|
||||||
applicationVersion.value = otherData.install_info.version
|
applicationVersion.value = otherData.install_info.version
|
||||||
newApplicationVersion.value = otherData.version
|
newApplicationVersion.value = otherData.version
|
||||||
applicationType.value = otherData.type
|
applicationType.value = otherData.type
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<div class="flex-1 h-0 flex justify-center items-center flex-col">
|
<div class="flex-1 h-0 flex justify-center items-center flex-col">
|
||||||
<el-result icon="success" title="上传成功">
|
<el-result icon="success" title="上传成功">
|
||||||
<template #sub-title>
|
<template #sub-title>
|
||||||
<p class="text-[16px]">小程序上传成功</p>
|
<p class="text-[16px]">小程序上传成功,耗时 {{ formattedDuration }}</p>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-button type="primary" @click="handleComplete">完成</el-button>
|
<el-button type="primary" @click="handleComplete">完成</el-button>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { computed, ref } from 'vue'
|
|||||||
import { getToken } from '@/utils/common'
|
import { getToken } from '@/utils/common'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import { ElMessage, UploadFile, UploadFiles } from 'element-plus'
|
import { ElMessage, UploadFile, UploadFiles } from 'element-plus'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
const prop = defineProps({
|
const prop = defineProps({
|
||||||
type: {
|
type: {
|
||||||
@ -24,11 +23,11 @@ const uploadRef = ref<Record<string, any> | null>(null)
|
|||||||
// 上传文件
|
// 上传文件
|
||||||
const upload = computed(() => {
|
const upload = computed(() => {
|
||||||
const headers: Record<string, any> = {}
|
const headers: Record<string, any> = {}
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: `${envConfig.VITE_APP_BASE_URL}/wechat/media/${prop.type}`,
|
action: `${import.meta.env.VITE_APP_BASE_URL}/wechat/media/${prop.type}`,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
headers,
|
headers,
|
||||||
accept: prop.type == 'image' ? '.bmp,.png,.jpeg,.jpg,.gif' : '.mp4',
|
accept: prop.type == 'image' ? '.bmp,.png,.jpeg,.jpg,.gif' : '.mp4',
|
||||||
|
|||||||
@ -213,7 +213,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, toRaw, watch, inject, defineAsyncComponent } from 'vue'
|
import { ref, reactive, toRaw, watch, inject } from 'vue'
|
||||||
import { t } from '@/lang'
|
import { t } from '@/lang'
|
||||||
import { ArrowLeft } from "@element-plus/icons-vue"
|
import { ArrowLeft } from "@element-plus/icons-vue"
|
||||||
import { img } from '@/utils/common'
|
import { img } from '@/utils/common'
|
||||||
@ -296,14 +296,10 @@ const goBack = () => {
|
|||||||
|
|
||||||
// 动态加载后台自定义组件编辑
|
// 动态加载后台自定义组件编辑
|
||||||
const modulesFiles = import.meta.glob('./components/*.vue', { eager: true })
|
const modulesFiles = import.meta.glob('./components/*.vue', { eager: true })
|
||||||
|
|
||||||
// 开发环境:直接 glob 插件源码
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
const addonModulesFiles = import.meta.glob('@/addon/**/views/diy/components/*.vue', { eager: true })
|
const addonModulesFiles = import.meta.glob('@/addon/**/views/diy/components/*.vue', { eager: true })
|
||||||
Object.assign(modulesFiles, addonModulesFiles)
|
addonModulesFiles && Object.assign(modulesFiles, addonModulesFiles)
|
||||||
}
|
|
||||||
|
|
||||||
const modules: Record<string, any> = {}
|
const modules = {}
|
||||||
for (const [key, value] of Object.entries(modulesFiles)) {
|
for (const [key, value] of Object.entries(modulesFiles)) {
|
||||||
const moduleName = key.split('/').pop()
|
const moduleName = key.split('/').pop()
|
||||||
const name = moduleName.split('.')[0]
|
const name = moduleName.split('.')[0]
|
||||||
@ -459,33 +455,6 @@ initPage({
|
|||||||
diyStore.components.push(com)
|
diyStore.components.push(com)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生产环境:通过编译产物异步加载插件 DIY 组件编辑器
|
|
||||||
if (!import.meta.env.DEV && component.value) {
|
|
||||||
const { loadAddonComponent, getInstalledAddonKeys } = await import('@/utils/addon-loader')
|
|
||||||
const addonKeys = getInstalledAddonKeys()
|
|
||||||
for (const type of Object.values(component.value) as any[]) {
|
|
||||||
if (!type.list) continue
|
|
||||||
for (const rawKey of Object.keys(type.list)) {
|
|
||||||
const compDef = type.list[rawKey]
|
|
||||||
const componentPath = compDef.value?.path || compDef.path || rawKey
|
|
||||||
if (modules[componentPath]) continue
|
|
||||||
modules[componentPath] = defineAsyncComponent(() =>
|
|
||||||
(async () => {
|
|
||||||
for (const addon of addonKeys) {
|
|
||||||
const loader = loadAddonComponent(addon, 'diy/components', componentPath)
|
|
||||||
try {
|
|
||||||
const result = await loader()
|
|
||||||
if (result?.default) return result.default
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
throw new Error(`DIY component "${componentPath}" not found in any addon`)
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log( component.value )
|
console.log( component.value )
|
||||||
loadDiyTemplatePages(data.type)
|
loadDiyTemplatePages(data.type)
|
||||||
|
|
||||||
@ -500,19 +469,22 @@ initPage({
|
|||||||
if (import.meta.env.MODE == 'development') {
|
if (import.meta.env.MODE == 'development') {
|
||||||
|
|
||||||
// env文件配置过wap域名
|
// env文件配置过wap域名
|
||||||
if (wapDomain.value) {
|
// if (wapDomain.value) {
|
||||||
wapUrl.value = wapDomain.value + '/wap'
|
// wapUrl.value = wapDomain.value + '/wap'
|
||||||
repeat = false
|
// repeat = false
|
||||||
setDomain()
|
// setDomain()
|
||||||
}
|
// }
|
||||||
|
|
||||||
let wap_domain_storage = storage.get('wap_domain')
|
// let wap_domain_storage = storage.get('wap_domain')
|
||||||
if (wap_domain_storage) {
|
// if (wap_domain_storage) {
|
||||||
wapUrl.value = wap_domain_storage
|
// wapUrl.value = wap_domain_storage
|
||||||
|
// repeat = false
|
||||||
|
// setDomain()
|
||||||
|
// }
|
||||||
|
wapUrl.value = 'http://localhost:5173/wap'
|
||||||
repeat = false
|
repeat = false
|
||||||
setDomain()
|
setDomain()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
setDomain()
|
setDomain()
|
||||||
|
|||||||
@ -61,12 +61,12 @@
|
|||||||
<div class="w-[42px] h-[42px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
|
<div class="w-[42px] h-[42px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
|
||||||
<img class="max-w-full max-h-full" src="@/app/assets/images/app_store/system_version.png" alt="" />
|
<img class="max-w-full max-h-full" src="@/app/assets/images/app_store/system_version.png" alt="" />
|
||||||
<div
|
<div
|
||||||
v-if="frameworkVersion != frameworkNewVersion && isNeedUpgrade(frameworkVersion, frameworkNewVersion)"
|
v-if="frameworkVersion != frameworkNewVersion"
|
||||||
class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]"
|
class="w-[8px] h-[8px] bg-[red] rounded-[8px] z-1 absolute top-[-2px] right-[-2px]"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="flex items-center" v-if="frameworkNewVersion != frameworkVersion && isNeedUpgrade(frameworkVersion, frameworkNewVersion)">
|
<p class="flex items-center" v-if="frameworkNewVersion != frameworkVersion">
|
||||||
<span class="text-[16px] font-[500] text-[#1D1F3A]">框架更新</span>
|
<span class="text-[16px] font-[500] text-[#1D1F3A]">框架更新</span>
|
||||||
<span class="text-[12px] text-[#9699B6] ml-[10px]">必须优先更新</span>
|
<span class="text-[12px] text-[#9699B6] ml-[10px]">必须优先更新</span>
|
||||||
</p>
|
</p>
|
||||||
@ -74,12 +74,10 @@
|
|||||||
<span class="text-[16px] font-[500] text-[#1D1F3A]">当前框架为最新版 V{{frameworkVersion}}</span>
|
<span class="text-[16px] font-[500] text-[#1D1F3A]">当前框架为最新版 V{{frameworkVersion}}</span>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-[14px] text-[#374151]">
|
<p class="text-[14px] text-[#374151]">
|
||||||
<template v-if="frameworkNewVersion != frameworkVersion && isNeedUpgrade(frameworkVersion, frameworkNewVersion)">
|
<span v-if="frameworkNewVersion != frameworkVersion" class="">框架 V{{ frameworkVersion }}</span>
|
||||||
<span class="">框架 V{{ frameworkVersion }}</span>
|
<span v-if="frameworkNewVersion != frameworkVersion" class="iconfont iconjiang-right text-[#F09000] mx-[2px]"></span>
|
||||||
<span class="iconfont iconjiang-right text-[#F09000] mx-[2px]"></span>
|
<span v-if="frameworkNewVersion != frameworkVersion" class="text-[#F09000] mr-[4px]">{{ frameworkNewVersion }}</span>
|
||||||
<span class="text-[#F09000] mr-[4px]">{{ frameworkNewVersion }}</span>
|
<span v-if="frameworkNewVersion != frameworkVersion" class="mr-[4px]">和</span>
|
||||||
<span class="mr-[4px]">和</span>
|
|
||||||
</template>
|
|
||||||
<span class="text-[#F09000] mr-[4px]">{{batchUpgradeApp.length}}</span>
|
<span class="text-[#F09000] mr-[4px]">{{batchUpgradeApp.length}}</span>
|
||||||
<span>个应用可更新</span>
|
<span>个应用可更新</span>
|
||||||
</p>
|
</p>
|
||||||
@ -130,7 +128,7 @@
|
|||||||
<div class="flex items-start flex-1 w-0">
|
<div class="flex items-start flex-1 w-0">
|
||||||
<div class="w-[42px] h-[42px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
|
<div class="w-[42px] h-[42px] bg-purple-100 rounded flex items-center justify-center mr-2 relative">
|
||||||
<div class="w-full h-full overflow-hidden rounded">
|
<div class="w-full h-full overflow-hidden rounded">
|
||||||
<el-image class="w-full h-full overflow-hidden rounded" :src="img(row.icon)" fit="contain">
|
<el-image class="w-full h-full overflow-hidden rounded" :src="row.icon" fit="contain">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="flex items-center w-full h-full">
|
<div class="flex items-center w-full h-full">
|
||||||
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
||||||
@ -176,7 +174,7 @@
|
|||||||
<el-button
|
<el-button
|
||||||
class="!text-[#008C1E] mt-[auto] !p-0 !border-[0] !bg-transparent !text-[12px] !h-[20px]"
|
class="!text-[#008C1E] mt-[auto] !p-0 !border-[0] !bg-transparent !text-[12px] !h-[20px]"
|
||||||
@click.stop="upgradeAddonFn(row)"
|
@click.stop="upgradeAddonFn(row)"
|
||||||
v-if="row.install_info.version != row.version && isNeedUpgrade(row.install_info.version, row.version)"
|
v-if="row.install_info.version != row.version"
|
||||||
>
|
>
|
||||||
升级
|
升级
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -222,7 +220,7 @@
|
|||||||
<el-table-column :label="t('appName')" align="left" width="300">
|
<el-table-column :label="t('appName')" align="left" width="300">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="flex items-center cursor-pointer relative left-[-10px]" @click="openDetail(row)">
|
<div class="flex items-center cursor-pointer relative left-[-10px]" @click="openDetail(row)">
|
||||||
<el-image class="w-[54px] h-[54px] rounded-[5px]" :src="img(row.icon)" fit="contain">
|
<el-image class="w-[54px] h-[54px] rounded-[5px]" :src="row.icon" fit="contain">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="flex items-center w-full h-full rounded-[5px]">
|
<div class="flex items-center w-full h-full rounded-[5px]">
|
||||||
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
||||||
@ -584,9 +582,30 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- </el-scrollbar> -->
|
<!-- </el-scrollbar> -->
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<el-button type="primary" class="!w-[140px]" :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall"
|
<el-tooltip effect="dark" placement="top">
|
||||||
>{{ t('install') }}
|
<template #content>
|
||||||
|
<div class="w-[400px]">
|
||||||
|
{{ t('installTips') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-button :disabled="!installCheckResult.is_pass || cloudInstalling" :loading="localInstalling" @click="handleInstall"
|
||||||
|
>{{ t('localInstall') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" placement="top">
|
||||||
|
<template #content>
|
||||||
|
<div class="w-[400px]">
|
||||||
|
{{ t('cloudInstallTips') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!installCheckResult.is_pass || localInstalling"
|
||||||
|
:loading="cloudInstalling"
|
||||||
|
@click="handleCloudInstall"
|
||||||
|
>{{ t('cloudInstall') }}
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="installStep == 1 && !errorDialog" class="h-[50vh] mt-[20px]">
|
<div v-show="installStep == 1 && !errorDialog" class="h-[50vh] mt-[20px]">
|
||||||
@ -736,7 +755,7 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-[42px] h-[42px] rounded-[6px] flex items-center justify-center mr-[10px] overflow-hidden">
|
<div class="w-[42px] h-[42px] rounded-[6px] flex items-center justify-center mr-[10px] overflow-hidden">
|
||||||
<el-image class="w-full h-full" :src="img(detailAddon?.icon)" fit="contain">
|
<el-image class="w-full h-full" :src="detailAddon?.icon" fit="contain">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="flex items-center w-full h-full">
|
<div class="flex items-center w-full h-full">
|
||||||
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
<img class="max-w-full max-h-full" src="@/app/assets/images/icon-addon-one.png" alt="" />
|
||||||
@ -940,7 +959,7 @@ import useUserStore from '@/stores/modules/user'
|
|||||||
import Upgrade from '@/app/components/upgrade/index.vue'
|
import Upgrade from '@/app/components/upgrade/index.vue'
|
||||||
import CloudBuild from '@/app/components/cloud-build/index.vue'
|
import CloudBuild from '@/app/components/cloud-build/index.vue'
|
||||||
import UpgradeLog from '@/app/components/upgrade-log/index.vue'
|
import UpgradeLog from '@/app/components/upgrade-log/index.vue'
|
||||||
import { deepClone, img } from '@/utils/common'
|
import { deepClone } from '@/utils/common'
|
||||||
|
|
||||||
const tableRef = ref(null)
|
const tableRef = ref(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -1138,7 +1157,7 @@ const localListFn = () => {
|
|||||||
|
|
||||||
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
||||||
localList.value.installed.push(data[i])
|
localList.value.installed.push(data[i])
|
||||||
if (data[i].install_info.version != data[i].version && isNeedUpgrade(data[i].install_info.version, data[i].version)) {
|
if (data[i].install_info.version != data[i].version) {
|
||||||
localList.value.recentlyUpdated.push(data[i])
|
localList.value.recentlyUpdated.push(data[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1723,7 +1742,7 @@ const appKeySingleSelect = (event: any, key: string) => {
|
|||||||
|
|
||||||
const batchUpgrade = () => {
|
const batchUpgrade = () => {
|
||||||
const appKeys = deepClone(batchUpgradeApp.value)
|
const appKeys = deepClone(batchUpgradeApp.value)
|
||||||
if (frameworkVersion.value != frameworkNewVersion.value && isNeedUpgrade(frameworkVersion.value, frameworkNewVersion.value)) {
|
if (frameworkVersion.value != frameworkNewVersion.value) {
|
||||||
appKeys.unshift('niucloud-admin')
|
appKeys.unshift('niucloud-admin')
|
||||||
}
|
}
|
||||||
if (!appKeys.length) {
|
if (!appKeys.length) {
|
||||||
@ -1808,11 +1827,6 @@ const toLink = (type)=>{
|
|||||||
window.open(`https://wap.niucloud.com/pages/product/detail?id=${detailAddon.value.app_id}`)
|
window.open(`https://wap.niucloud.com/pages/product/detail?id=${detailAddon.value.app_id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNeedUpgrade = (currVersion: string, onlineVersion: string) => {
|
|
||||||
if (isNaN(parseInt(onlineVersion.replaceAll('.', ''))) || isNaN(parseInt(currVersion.replaceAll('.', '')))) return false
|
|
||||||
return parseInt(onlineVersion.replaceAll('.', '')) > parseInt(currVersion.replaceAll('.', ''))
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
|
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
|
||||||
<el-image :src="img(item.icon)" v-if="item.icon" class="w-full h-full" />
|
<el-image :src="item.icon" v-if="item.icon" class="w-full h-full" />
|
||||||
<el-image v-else class="w-full h-full">
|
<el-image v-else class="w-full h-full">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-error">
|
<div class="image-error">
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
|
<div class="w-[60px] h-[60px] mr-[10px] rounded-md overflow-hidden">
|
||||||
<el-image :src="img(item.icon)" v-if="item.icon" class="w-full h-full" />
|
<el-image :src="item.icon" v-if="item.icon" class="w-full h-full" />
|
||||||
<el-image v-else class="w-full h-full">
|
<el-image v-else class="w-full h-full">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-error">
|
<div class="image-error">
|
||||||
@ -100,7 +100,7 @@ import { ArrowLeft } from '@element-plus/icons-vue'
|
|||||||
import { menuRefresh } from '@/app/api/sys'
|
import { menuRefresh } from '@/app/api/sys'
|
||||||
import { addSiteGroup, editSiteGroup, getSiteGroupInfo } from '@/app/api/site'
|
import { addSiteGroup, editSiteGroup, getSiteGroupInfo } from '@/app/api/site'
|
||||||
import { getInstalledAddonList } from '@/app/api/addon'
|
import { getInstalledAddonList } from '@/app/api/addon'
|
||||||
import { deepClone, img } from '@/utils/common'
|
import { deepClone } from '@/utils/common'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
|
<upload-attachment type="video" ref="videoRef" @confirm="videoSelect" />
|
||||||
<vue-ueditor-wrap v-model="content" :config="editorConfig"
|
<vue-ueditor-wrap v-model="content" :config="editorConfig"
|
||||||
:editorDependencies="['ueditor.config.js', 'ueditor.all.js']" ref="editorRef"
|
:editorDependencies="['ueditor.config.js', 'ueditor.all.js']" ref="editorRef"
|
||||||
mode="observer"
|
|
||||||
@ready="handleEditorReady"></vue-ueditor-wrap>
|
@ready="handleEditorReady"></vue-ueditor-wrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ import { computed, ref } from 'vue'
|
|||||||
import { getToken, img } from '@/utils/common'
|
import { getToken, img } from '@/utils/common'
|
||||||
import { VueUeditorWrap } from 'vue-ueditor-wrap'
|
import { VueUeditorWrap } from 'vue-ueditor-wrap'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
const editorRef = ref()
|
const editorRef = ref()
|
||||||
|
|
||||||
@ -48,10 +46,9 @@ const content = computed({
|
|||||||
let editorEl = null
|
let editorEl = null
|
||||||
|
|
||||||
const serverHeaders = {}
|
const serverHeaders = {}
|
||||||
serverHeaders[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
serverHeaders[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
serverHeaders[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
serverHeaders[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
const appBaseUrl = envConfig.VITE_APP_BASE_URL || '/adminapi/'
|
const baseUrl = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||||
const baseUrl = appBaseUrl.endsWith('/') ? appBaseUrl : `${appBaseUrl}/`
|
|
||||||
|
|
||||||
const editorConfig = ref({
|
const editorConfig = ref({
|
||||||
debug: false,
|
debug: false,
|
||||||
@ -91,9 +88,7 @@ const handleEditorReady = (editor) => {
|
|||||||
// console.log('扩展原型链', editor)
|
// console.log('扩展原型链', editor)
|
||||||
editor.addListener('blur', () => {
|
editor.addListener('blur', () => {
|
||||||
// console.log('失焦了')
|
// console.log('失焦了')
|
||||||
const content = editor.getContent()
|
emit('handleBlur', editor.getContent()) // 把内容传出去
|
||||||
emit('update:modelValue', content) // 确保 v-model 同步
|
|
||||||
emit('handleBlur', content) // 把内容传出去
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 全屏切换监听
|
// 全屏切换监听
|
||||||
|
|||||||
@ -199,7 +199,6 @@ import {
|
|||||||
import { debounce, img, getToken } from '@/utils/common'
|
import { debounce, img, getToken } from '@/utils/common'
|
||||||
import { ElMessage, UploadFile, UploadFiles, ElMessageBox, MessageParams } from 'element-plus'
|
import { ElMessage, UploadFile, UploadFiles, ElMessageBox, MessageParams } from 'element-plus'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
const attachmentCategoryName = ref('')
|
const attachmentCategoryName = ref('')
|
||||||
const operate = ref(false)
|
const operate = ref(false)
|
||||||
@ -447,10 +446,9 @@ const time = ref<any>(null)
|
|||||||
// 上传文件
|
// 上传文件
|
||||||
const upload = computed(() => {
|
const upload = computed(() => {
|
||||||
const headers: Record<string, any> = {}
|
const headers: Record<string, any> = {}
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
const apiBase = envConfig.VITE_APP_BASE_URL || ''
|
const baseURL = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||||
const baseURL = apiBase.endsWith('/') ? apiBase : `${apiBase}/`
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: `${baseURL}sys/${prop.type}`,
|
action: `${baseURL}sys/${prop.type}`,
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { t } from '@/lang'
|
|||||||
import { getToken } from '@/utils/common'
|
import { getToken } from '@/utils/common'
|
||||||
import { UploadFile, ElMessage } from 'element-plus'
|
import { UploadFile, ElMessage } from 'element-plus'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
const prop = defineProps({
|
const prop = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -37,7 +36,7 @@ const value = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const upload: Record<string, any> = {
|
const upload: Record<string, any> = {
|
||||||
action: `${envConfig.VITE_APP_BASE_URL || '/adminapi/'}${prop.api}`,
|
action: `${import.meta.env.VITE_APP_BASE_URL}/${prop.api}`,
|
||||||
showFileList: false,
|
showFileList: false,
|
||||||
headers: {},
|
headers: {},
|
||||||
accept: 'audio/*,.mp3,.wav,.ogg,.m4a,.flac,.aac,.wma',
|
accept: 'audio/*,.mp3,.wav,.ogg,.m4a,.flac,.aac,.wma',
|
||||||
@ -63,8 +62,8 @@ const upload: Record<string, any> = {
|
|||||||
ElMessage({ message: t('upload.success'), type: 'success' })
|
ElMessage({ message: t('upload.success'), type: 'success' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
upload.headers[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
upload.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
upload.headers[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
upload.headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { t } from '@/lang'
|
|||||||
import { getToken } from '@/utils/common'
|
import { getToken } from '@/utils/common'
|
||||||
import { UploadFile, ElMessage } from 'element-plus'
|
import { UploadFile, ElMessage } from 'element-plus'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
const prop = defineProps({
|
const prop = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -44,10 +43,9 @@ const value = computed({
|
|||||||
|
|
||||||
const upload = computed(() => {
|
const upload = computed(() => {
|
||||||
const headers: Record<string, any> = {}
|
const headers: Record<string, any> = {}
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
headers[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
const appBaseUrl = envConfig.VITE_APP_BASE_URL || '/adminapi/'
|
const baseURL = import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`
|
||||||
const baseURL = appBaseUrl.endsWith('/') ? appBaseUrl : `${appBaseUrl}/`
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: `${baseURL}${prop.api}`,
|
action: `${baseURL}${prop.api}`,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
axios.defaults.baseURL = envConfig.VITE_APP_BASE_URL || '/adminapi/'
|
axios.defaults.baseURL = import.meta.env.VITE_APP_BASE_URL
|
||||||
|
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
timeout: 40000,
|
timeout: 40000,
|
||||||
|
|||||||
@ -4,21 +4,18 @@ import Language from "./language"
|
|||||||
import zhCn from "./zh-cn/common.json";
|
import zhCn from "./zh-cn/common.json";
|
||||||
import en from "./en/common.json"
|
import en from "./en/common.json"
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
const addonZhCnCommon = import.meta.globEager('@/addon/**/lang/zh-cn/common.json')
|
const addonZhCnCommon = import.meta.globEager('@/addon/**/lang/zh-cn/common.json')
|
||||||
const addonEnCommon = import.meta.globEager('@/addon/**/lang/en/common.json')
|
const addonEnCommon = import.meta.globEager('@/addon/**/lang/en/common.json')
|
||||||
for (const key in addonZhCnCommon) {
|
|
||||||
|
for (let key in addonZhCnCommon) {
|
||||||
Object.assign(zhCn, addonZhCnCommon[key].default)
|
Object.assign(zhCn, addonZhCnCommon[key].default)
|
||||||
}
|
}
|
||||||
for (const key in addonEnCommon) {
|
for (let key in addonEnCommon) {
|
||||||
Object.assign(en, addonEnCommon[key].default)
|
Object.assign(en, addonEnCommon[key].default)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//创建实例
|
//创建实例
|
||||||
let i18n = createI18n({
|
let i18n = createI18n({
|
||||||
locale: 'zh-cn',
|
|
||||||
fallbackLocale: 'zh-cn',
|
|
||||||
datetimeFormats: {},
|
datetimeFormats: {},
|
||||||
numberFormats: {},
|
numberFormats: {},
|
||||||
globalInjection: true, //是否全局注入
|
globalInjection: true, //是否全局注入
|
||||||
|
|||||||
@ -1,22 +1,10 @@
|
|||||||
import i18n, { language } from "./i18n"
|
import i18n, { language } from "./i18n"
|
||||||
import useAppStore from '@/stores/modules/app'
|
import useAppStore from '@/stores/modules/app'
|
||||||
import { resolveLangFile, resolveRouteAddon, resolveRouteView, inferAddonFromPath } from '@/utils/addon-lang'
|
|
||||||
import { getCurrentInstance } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
function currentRoute() {
|
|
||||||
if (getCurrentInstance()) {
|
|
||||||
return useRoute()
|
|
||||||
}
|
|
||||||
return useAppStore().route as Parameters<typeof resolveRouteAddon>[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = (message: string) => {
|
const t = (message: string) => {
|
||||||
const route = currentRoute()
|
const route = useAppStore().route
|
||||||
let app = resolveRouteAddon(route)
|
const path = route.meta.view || route.path
|
||||||
const path = resolveRouteView(route)
|
const file = path == '/' ? 'index' : path.replace(/^(\/admin\/|\/site\/|\/)/, '').replaceAll('/', '.')
|
||||||
if (!app) app = inferAddonFromPath(path)
|
|
||||||
const file = resolveLangFile(app, path)
|
|
||||||
const key = `${file}.${message}`
|
const key = `${file}.${message}`
|
||||||
return i18n.global.t(key) != key ? i18n.global.t(key) : i18n.global.t(message)
|
return i18n.global.t(key) != key ? i18n.global.t(key) : i18n.global.t(message)
|
||||||
}
|
}
|
||||||
@ -25,6 +13,9 @@ export { language, t }
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: any) {
|
install(app: any) {
|
||||||
|
//注册i18n
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import { loadAddonLang, preloadAllAddonLangs, resolveLangFile, inferAddonFromPath } from '@/utils/addon-lang'
|
|
||||||
|
|
||||||
class Language {
|
class Language {
|
||||||
private i18n: any;
|
private i18n: any;
|
||||||
@ -32,42 +31,25 @@ class Language {
|
|||||||
*/
|
*/
|
||||||
public async loadLocaleMessages(app: string, path: string, locale: string) {
|
public async loadLocaleMessages(app: string, path: string, locale: string) {
|
||||||
try {
|
try {
|
||||||
if (!app) app = inferAddonFromPath(path)
|
const file = path == '/' ? 'index' : path.replace(/^(\/admin\/|\/site\/|\/)/, '').replaceAll('/', '.')
|
||||||
const file = resolveLangFile(app, path)
|
|
||||||
|
|
||||||
let pageMessages: Record<string, string> = {}
|
// 引入语言包文件
|
||||||
if (app) {
|
const messages = await import(app ? `@/addon/${app}/lang/${locale}/${file}.json` : `@/app/lang/${locale}/${file}.json`)
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
const messages = await import(/* @vite-ignore */ `@/addon/${app}/lang/${locale}/${file}.json`)
|
|
||||||
pageMessages = messages.default || {}
|
|
||||||
} else {
|
|
||||||
pageMessages = await loadAddonLang(app, locale, file)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const messages = await import(`@/app/lang/${locale}/${file}.json`)
|
|
||||||
pageMessages = messages.default || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: Record<string, string> = {}
|
let data: Record<string, string> = {}
|
||||||
Object.keys(pageMessages).forEach(key => {
|
Object.keys(messages.default).forEach(key => {
|
||||||
data[`${file}.${key}`] = pageMessages[key]
|
data[`${file}.${key}`] = messages.default[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 查询插件的公共语言包(合并到根 key,与 dev 启动时 globEager 行为一致)
|
// 查询插件的公共语言包
|
||||||
if (app) {
|
if (app) {
|
||||||
try {
|
try {
|
||||||
let commonMessages: Record<string, string> = {}
|
const messagesCommon = await import( `@/${ app }/lang/${ locale }/common.json`);
|
||||||
if (import.meta.env.DEV) {
|
Object.keys(messagesCommon.default).forEach(key => {
|
||||||
const messagesCommon = await import(/* @vite-ignore */ `@/addon/${app}/lang/${locale}/common.json`)
|
data[`${file}.${key}`] = messagesCommon.default[key]
|
||||||
commonMessages = messagesCommon.default || {}
|
|
||||||
} else {
|
|
||||||
commonMessages = await loadAddonLang(app, locale, 'common')
|
|
||||||
}
|
|
||||||
Object.keys(commonMessages).forEach(key => {
|
|
||||||
data[key] = commonMessages[key]
|
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 未找到插件公共语言包
|
// console.log('未找到插件公共语言包')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,13 +61,6 @@ class Language {
|
|||||||
return nextTick()
|
return nextTick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生产环境启动时预加载各插件全部语言包 */
|
|
||||||
public async preloadAddonLangs() {
|
|
||||||
await preloadAllAddonLangs((locale, messages) => {
|
|
||||||
this.i18n.global.mergeLocaleMessage(locale, messages)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Language
|
export default Language
|
||||||
|
|||||||
@ -86,11 +86,6 @@ watch(route, () => {
|
|||||||
defaultOpeneds.value = twoMenuData.value.map(item => item.name)
|
defaultOpeneds.value = twoMenuData.value.map(item => item.name)
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const isNeedUpgrade = (currVersion: string, onlineVersion: string) => {
|
|
||||||
if (isNaN(parseInt(onlineVersion.replaceAll('.', ''))) || isNaN(parseInt(currVersion.replaceAll('.', '')))) return false
|
|
||||||
return parseInt(onlineVersion.replaceAll('.', '')) > parseInt(currVersion.replaceAll('.', ''))
|
|
||||||
}
|
|
||||||
|
|
||||||
const recentlyUpdated = ref([])
|
const recentlyUpdated = ref([])
|
||||||
const localListFn = () => {
|
const localListFn = () => {
|
||||||
getAddonLocal({}).then((res) => {
|
getAddonLocal({}).then((res) => {
|
||||||
@ -98,7 +93,7 @@ const localListFn = () => {
|
|||||||
recentlyUpdated.value = []
|
recentlyUpdated.value = []
|
||||||
for (const i in data) {
|
for (const i in data) {
|
||||||
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
if (data[i].install_info && Object.keys(data[i].install_info)?.length) {
|
||||||
if (data[i].install_info.version != data[i].version && isNeedUpgrade(data[i].install_info.version, data[i].version)) {
|
if (data[i].install_info.version != data[i].version) {
|
||||||
recentlyUpdated.value.push(data[i])
|
recentlyUpdated.value.push(data[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,9 +105,16 @@ localListFn()
|
|||||||
const frameworkVersionList = ref([])
|
const frameworkVersionList = ref([])
|
||||||
const isNewVersion = computed(() => {
|
const isNewVersion = computed(() => {
|
||||||
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
if (!newVersion.value || newVersion.value.version_no === version.value) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
return isNeedUpgrade(version.value, newVersion.value.version_no)
|
|
||||||
|
// 将版本号转为字符串再处理
|
||||||
|
const currentVersionStr = String(version.value);
|
||||||
|
const latestVersionStr = String(newVersion.value.version_no);
|
||||||
|
// 移除点号并转为数字比较
|
||||||
|
const currentVersionNum = parseInt(currentVersionStr.replace(/\./g, ''), 10);
|
||||||
|
const latestVersionNum = parseInt(latestVersionStr.replace(/\./g, ''), 10);
|
||||||
|
return latestVersionNum > currentVersionNum;
|
||||||
})
|
})
|
||||||
|
|
||||||
const getFrameworkVersionListFn = () => {
|
const getFrameworkVersionListFn = () => {
|
||||||
@ -136,7 +138,7 @@ getVersionsInfo()
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.one-menu{
|
.one-menu{
|
||||||
padding: 20px 10px 10px;
|
padding: 20px 10px 10px;
|
||||||
width: 78px;
|
width: 78px;
|
||||||
@ -196,10 +198,10 @@ getVersionsInfo()
|
|||||||
&.expanded .menu-item .text-center {
|
&.expanded .menu-item .text-center {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
:deep(.el-menu){
|
.el-menu{
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
:deep(.el-scrollbar){
|
.el-scrollbar{
|
||||||
height: calc(100vh - 65px);
|
height: calc(100vh - 65px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +210,7 @@ getVersionsInfo()
|
|||||||
width: 185px;
|
width: 185px;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin: 4px 15px;
|
margin: 4px 15px;
|
||||||
padding: 0 8px !important;
|
padding: 0 8px !important;
|
||||||
@ -230,11 +232,11 @@ getVersionsInfo()
|
|||||||
// color: var(--el-color-primary);
|
// color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
width: 185px;
|
width: 185px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
// margin-bottom: 8px;
|
// margin-bottom: 8px;
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@ -251,11 +253,11 @@ getVersionsInfo()
|
|||||||
// background-color: var(--el-color-primary-light-9) !important;
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
// color: var(--el-color-primary);
|
// color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
.el-icon:deep(.el-sub-menu__icon-arrow){
|
.el-icon.el-sub-menu__icon-arrow{
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 25px !important;
|
padding-left: 25px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,7 +286,7 @@ getVersionsInfo()
|
|||||||
// :deep(.el-scrollbar__bar){
|
// :deep(.el-scrollbar__bar){
|
||||||
// display: none !important;
|
// display: none !important;
|
||||||
// }
|
// }
|
||||||
// .layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside :deep(.el-scrollbar){
|
// .layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside .el-scrollbar{
|
||||||
// overflow: inherit !important;
|
// overflow: inherit !important;
|
||||||
// }
|
// }
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
|
|||||||
@ -45,8 +45,8 @@ const props = defineProps({
|
|||||||
const meta = computed(() => props.routes.meta)
|
const meta = computed(() => props.routes.meta)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
.el-icon{
|
.el-icon{
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
// })
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.logo-wrap {
|
.logo-wrap {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -84,7 +84,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
flex: 1 !important;
|
flex: 1 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border-right: 0 !important;
|
border-right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,15 +100,15 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
border-bottom: 2px solid #101117;
|
border-bottom: 2px solid #101117;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
background-color: #191a23;
|
background-color: #191a23;
|
||||||
|
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu__title),
|
.el-sub-menu__title,
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
color: #B7B7ba;
|
color: #B7B7ba;
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item).is-active {
|
.el-menu-item.is-active {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
background-color: var(--el-color-primary) !important;
|
background-color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
//--side-dark-color: #141414;
|
//--side-dark-color: #141414;
|
||||||
//background-color: var(--side-dark-color, var(--el-bg-color));
|
//background-color: var(--side-dark-color, var(--el-bg-color));
|
||||||
|
|||||||
@ -85,22 +85,22 @@ watch(route, () => {
|
|||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
.el-icon{
|
.el-icon{
|
||||||
// width: auto;
|
// width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
:deep(.el-sub-menu__title),
|
.el-sub-menu__title,
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
&:hover {
|
&:hover {
|
||||||
// background-color: #F1F5FF !important;
|
// background-color: #F1F5FF !important;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-icon:deep(.el-sub-menu__icon-arrow){
|
.el-icon.el-sub-menu__icon-arrow{
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,7 +75,7 @@ if (siteInfo?.apps.length > 1) {
|
|||||||
defaultOpeneds.value = menuData.value.map(item => item.name)
|
defaultOpeneds.value = menuData.value.map(item => item.name)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.logo-wrap {
|
.logo-wrap {
|
||||||
// background: #1f2531;
|
// background: #1f2531;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@ -91,22 +91,22 @@ defaultOpeneds.value = menuData.value.map(item => item.name)
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border-right: 0!important;
|
border-right: 0!important;
|
||||||
|
|
||||||
&:not(.el-menu--collapse) {
|
&:not(.el-menu--collapse) {
|
||||||
width: var(--aside-width);
|
width: var(--aside-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item), :deep(.el-sub-menu__title) {
|
.el-menu-item, .el-sub-menu__title {
|
||||||
--el-menu-item-height: 40px;
|
--el-menu-item-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu) :deep(.el-menu-item) {
|
.el-sub-menu .el-menu-item {
|
||||||
--el-menu-sub-item-height: 40px;
|
--el-menu-sub-item-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item).is-active {
|
.el-menu-item.is-active {
|
||||||
background: var(--el-color-primary) !important;
|
background: var(--el-color-primary) !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
@ -115,14 +115,14 @@ defaultOpeneds.value = menuData.value.map(item => item.name)
|
|||||||
// background: #282e3a;
|
// background: #282e3a;
|
||||||
// background: #fff;
|
// background: #fff;
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item) .el-menu-tooltip__trigger{
|
.el-menu-item .el-menu-tooltip__trigger{
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.el-menu--collapse {
|
&.el-menu--collapse {
|
||||||
:deep(.el-menu-item), :deep(.el-sub-menu__title) {
|
.el-menu-item, .el-sub-menu__title {
|
||||||
--el-menu-item-height: 60px;
|
--el-menu-item-height: 60px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
background-color: var(--side-dark-color, var(--el-bg-color));
|
background-color: var(--side-dark-color, var(--el-bg-color));
|
||||||
border-right: 1px solid var(--el-border-color-lighter);
|
border-right: 1px solid var(--el-border-color-lighter);
|
||||||
|
|||||||
@ -83,8 +83,8 @@ const handleJump = (routeName: string) => {
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
.el-icon{
|
.el-icon{
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -355,11 +355,11 @@ watch(route, () => {
|
|||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.one-menu{
|
.one-menu{
|
||||||
.aside-menu:not(.el-menu--collapse) {
|
.aside-menu:not(.el-menu--collapse) {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -395,10 +395,10 @@ watch(route, () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu){
|
.el-menu{
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
:deep(.el-scrollbar){
|
.el-scrollbar{
|
||||||
height: calc(100vh - 65px);
|
height: calc(100vh - 65px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,7 +407,7 @@ watch(route, () => {
|
|||||||
width: 190px;
|
width: 190px;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
padding: 0 10px !important;
|
padding: 0 10px !important;
|
||||||
@ -425,8 +425,8 @@ watch(route, () => {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@ -442,11 +442,11 @@ watch(route, () => {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 20px !important;
|
padding-left: 20px !important;
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
@ -462,7 +462,7 @@ watch(route, () => {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 30px !important;
|
padding-left: 30px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
--side-dark-color: #141414;
|
--side-dark-color: #141414;
|
||||||
background-color: var(--side-dark-color, var(--el-bg-color));
|
background-color: var(--side-dark-color, var(--el-bg-color));
|
||||||
|
|||||||
@ -285,21 +285,10 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu{
|
||||||
.el-icon{
|
.el-icon{
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item) {
|
|
||||||
height: 40px!important;
|
|
||||||
line-height: 40px!important;
|
|
||||||
&.is-active {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.el-sub-menu__title) {
|
|
||||||
height: 40px!important;
|
|
||||||
line-height: 40px!important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -133,22 +133,22 @@ if (siteInfo?.apps.length > 1) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.menu-wrap {
|
.menu-wrap {
|
||||||
padding: 0!important;
|
padding: 0!important;
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border-right: 0!important;
|
border-right: 0!important;
|
||||||
|
|
||||||
:deep(.el-menu-item), :deep(.el-sub-menu__title) {
|
.el-menu-item, .el-sub-menu__title {
|
||||||
--el-menu-item-height: 40px;
|
--el-menu-item-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu) :deep(.el-menu-item) {
|
.el-sub-menu .el-menu-item {
|
||||||
--el-menu-sub-item-height: 40px;
|
--el-menu-sub-item-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item).is-active {
|
.el-menu-item.is-active {
|
||||||
background-color: var(--el-color-primary)
|
background-color: var(--el-color-primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
&.bright {
|
&.bright {
|
||||||
background-color: #F5F7F9;
|
background-color: #F5F7F9;
|
||||||
|
|||||||
@ -273,18 +273,10 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
.el-icon {
|
.el-icon {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item) {
|
|
||||||
height: 40px!important;
|
|
||||||
line-height: 40px!important;
|
|
||||||
}
|
|
||||||
:deep(.el-sub-menu__title) {
|
|
||||||
height: 40px!important;
|
|
||||||
line-height: 40px!important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -135,18 +135,18 @@ if (siteInfo?.apps.length > 1) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.menu-wrap {
|
.menu-wrap {
|
||||||
padding: 0!important;
|
padding: 0!important;
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border-right: 0!important;
|
border-right: 0!important;
|
||||||
|
|
||||||
:deep(.el-menu-item), :deep(.el-sub-menu__title) {
|
.el-menu-item, .el-sub-menu__title {
|
||||||
--el-menu-item-height: 40px;
|
--el-menu-item-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu) :deep(.el-menu-item) {
|
.el-sub-menu .el-menu-item {
|
||||||
--el-menu-sub-item-height: 40px;
|
--el-menu-sub-item-height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,7 +255,7 @@ getVersionsInfo()
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.one-menu{
|
.one-menu{
|
||||||
padding: 20px 10px 10px;
|
padding: 20px 10px 10px;
|
||||||
width: 78px;
|
width: 78px;
|
||||||
@ -315,10 +315,10 @@ getVersionsInfo()
|
|||||||
&.expanded .menu-item .text-center {
|
&.expanded .menu-item .text-center {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
:deep(.el-menu){
|
.el-menu{
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
:deep(.el-scrollbar){
|
.el-scrollbar{
|
||||||
height: calc(100vh - 65px);
|
height: calc(100vh - 65px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,7 +327,7 @@ getVersionsInfo()
|
|||||||
width: 185px;
|
width: 185px;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin: 4px 15px;
|
margin: 4px 15px;
|
||||||
padding: 0 8px !important;
|
padding: 0 8px !important;
|
||||||
@ -349,11 +349,11 @@ getVersionsInfo()
|
|||||||
// color: var(--el-color-primary);
|
// color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
width: 185px;
|
width: 185px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
// margin-bottom: 8px;
|
// margin-bottom: 8px;
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
@ -370,11 +370,11 @@ getVersionsInfo()
|
|||||||
// background-color: var(--el-color-primary-light-9) !important;
|
// background-color: var(--el-color-primary-light-9) !important;
|
||||||
// color: var(--el-color-primary);
|
// color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
.el-icon:deep(.el-sub-menu__icon-arrow){
|
.el-icon.el-sub-menu__icon-arrow{
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 25px !important;
|
padding-left: 25px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,7 +403,7 @@ getVersionsInfo()
|
|||||||
// :deep(.el-scrollbar__bar){
|
// :deep(.el-scrollbar__bar){
|
||||||
// display: none !important;
|
// display: none !important;
|
||||||
// }
|
// }
|
||||||
// .layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside :deep(.el-scrollbar){
|
// .layout-aside .el-scrollbar__wrap--hidden-default, .layout-aside .el-scrollbar{
|
||||||
// overflow: inherit !important;
|
// overflow: inherit !important;
|
||||||
// }
|
// }
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
|
|||||||
@ -45,8 +45,8 @@ const props = defineProps({
|
|||||||
const meta = computed(() => props.routes.meta)
|
const meta = computed(() => props.routes.meta)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
.el-icon{
|
.el-icon{
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
// })
|
// })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.logo-wrap {
|
.logo-wrap {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -84,7 +84,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
flex: 1 !important;
|
flex: 1 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border-right: 0 !important;
|
border-right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,15 +100,15 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
border-bottom: 2px solid #101117;
|
border-bottom: 2px solid #101117;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
background-color: #191a23;
|
background-color: #191a23;
|
||||||
|
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu__title),
|
.el-sub-menu__title,
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
color: #B7B7ba;
|
color: #B7B7ba;
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ userStore.routers = userStore.routers.filter((item, index) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item).is-active {
|
.el-menu-item.is-active {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
background-color: var(--el-color-primary) !important;
|
background-color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
border-right: 1px solid var(--el-border-color-lighter);
|
border-right: 1px solid var(--el-border-color-lighter);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,8 +80,8 @@ const handleJump = (routeName: string) => {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|||||||
@ -377,13 +377,13 @@ watch(route, () => {
|
|||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.one-menu {
|
.one-menu {
|
||||||
|
|
||||||
.aside-menu:not(.el-menu--collapse) {
|
.aside-menu:not(.el-menu--collapse) {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
@ -408,11 +408,11 @@ watch(route, () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu) {
|
.el-menu {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-scrollbar) {
|
.el-scrollbar {
|
||||||
height: calc(100vh - 65px);
|
height: calc(100vh - 65px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,7 +424,7 @@ watch(route, () => {
|
|||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 0 12px 4px;
|
margin: 0 12px 4px;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@ -445,10 +445,10 @@ watch(route, () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
:deep(.el-sub-menu__title) {
|
.el-sub-menu__title {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 0 8px 4px;
|
margin: 0 8px 4px;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
@ -465,19 +465,19 @@ watch(route, () => {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
background-color: var(--el-color-primary-light-9) !important;
|
background-color: var(--el-color-primary-light-9) !important;
|
||||||
}
|
}
|
||||||
.el-icon:deep(.el-sub-menu__icon-arrow) {
|
.el-icon.el-sub-menu__icon-arrow {
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
padding-left: 20px !important;
|
padding-left: 20px !important;
|
||||||
span{
|
span{
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
@ -493,7 +493,7 @@ watch(route, () => {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 40px !important;
|
padding-left: 40px !important;
|
||||||
span{
|
span{
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
|
|||||||
@ -20,7 +20,7 @@ watch(route, () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
background-color: var(--side-dark-color, var(--el-bg-color));
|
background-color: var(--side-dark-color, var(--el-bg-color));
|
||||||
border-right: 1px solid var(--el-border-color-lighter);
|
border-right: 1px solid var(--el-border-color-lighter);
|
||||||
|
|||||||
@ -82,5 +82,5 @@ const handleJump = (routeName: string) => {
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -202,7 +202,7 @@ const collectSpecialMenuNamesLevel1 = (menus: any[]) =>{
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
:root,
|
:root,
|
||||||
body {
|
body {
|
||||||
--layout-side-hover-bg: #f7f8fa;
|
--layout-side-hover-bg: #f7f8fa;
|
||||||
@ -210,7 +210,7 @@ body {
|
|||||||
--layout-side-active-text: var(--el-color-primary);
|
--layout-side-active-text: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.two-menu {
|
.two-menu {
|
||||||
|
|
||||||
.aside-menu:not(.el-menu--collapse) {
|
.aside-menu:not(.el-menu--collapse) {
|
||||||
@ -218,7 +218,7 @@ body {
|
|||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 0 8px 4px;
|
margin: 0 8px 4px;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@ -242,10 +242,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu) {
|
.el-sub-menu {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
:deep(.el-sub-menu__title) {
|
.el-sub-menu__title {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin: 0 8px 4px;
|
margin: 0 8px 4px;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
@ -262,19 +262,19 @@ body {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
}
|
}
|
||||||
.el-icon:deep(.el-sub-menu__icon-arrow) {
|
.el-icon.el-sub-menu__icon-arrow {
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
.el-menu-item {
|
||||||
padding-left: 25px !important;
|
padding-left: 25px !important;
|
||||||
span{
|
span{
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-sub-menu){
|
.el-sub-menu{
|
||||||
:deep(.el-sub-menu__title){
|
.el-sub-menu__title{
|
||||||
margin: 0 8px 2px;
|
margin: 0 8px 2px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
@ -290,7 +290,7 @@ body {
|
|||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item){
|
.el-menu-item{
|
||||||
padding-left: 40px !important;
|
padding-left: 40px !important;
|
||||||
span{
|
span{
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
@ -306,7 +306,7 @@ body {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
}
|
}
|
||||||
:deep(.el-menu-item), :deep(.el-sub-menu), :deep(.el-sub-menu){
|
.el-menu-item, .el-sub-menu, .el-sub-menu{
|
||||||
height: auto!important;
|
height: auto!important;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|||||||
@ -397,7 +397,7 @@ const handleRouteSelect = (name:any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style>
|
||||||
:root,
|
:root,
|
||||||
body {
|
body {
|
||||||
--layout-header-bg: #fff;
|
--layout-header-bg: #fff;
|
||||||
|
|||||||
@ -14,13 +14,7 @@ import VueUeditorWrap from 'vue-ueditor-wrap'
|
|||||||
|
|
||||||
window.hl = hljs
|
window.hl = hljs
|
||||||
|
|
||||||
import { initAddonManifests } from '@/utils/addon-lang'
|
|
||||||
import { language } from '@/lang'
|
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
if (!import.meta.env.DEV) {
|
|
||||||
await initAddonManifests()
|
|
||||||
}
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.use(lang)
|
app.use(lang)
|
||||||
@ -31,12 +25,6 @@ async function run() {
|
|||||||
app.use(VueUeditorWrap)
|
app.use(VueUeditorWrap)
|
||||||
useElementIcon(app)
|
useElementIcon(app)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
// 插件语言包延迟加载:挂载后异步预加载,不阻塞首屏渲染
|
|
||||||
// 路由守卫会按需补加载当前页面缺失的语言包
|
|
||||||
if (!import.meta.env.DEV) {
|
|
||||||
language.preloadAddonLangs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@ -6,18 +6,15 @@ import { language } from '@/lang'
|
|||||||
import useSystemStore from '@/stores/modules/system'
|
import useSystemStore from '@/stores/modules/system'
|
||||||
import useUserStore from '@/stores/modules/user'
|
import useUserStore from '@/stores/modules/user'
|
||||||
import { setWindowTitle, getAppType, urlToRouteRaw } from '@/utils/common'
|
import { setWindowTitle, getAppType, urlToRouteRaw } from '@/utils/common'
|
||||||
import { resolveRouteAddon, resolveRouteView } from '@/utils/addon-loader'
|
|
||||||
|
|
||||||
// 加载插件中定义的router(仅开发环境从源码加载)
|
// 加载插件中定义的router
|
||||||
const ADDON_ROUTE: any[] = []
|
const ADDON_ROUTE = []
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
const addonRoutes = import.meta.globEager('@/addon/**/router/index.ts')
|
const addonRoutes = import.meta.globEager('@/addon/**/router/index.ts')
|
||||||
for (const key in addonRoutes) {
|
for (const key in addonRoutes) {
|
||||||
const addon: any = addonRoutes[key]
|
const addon: any = addonRoutes[key]
|
||||||
addon.ROUTE && ADDON_ROUTE.push(...addon.ROUTE)
|
addon.ROUTE && ADDON_ROUTE.push(...addon.ROUTE)
|
||||||
addon.NO_LOGIN_ROUTES && NO_LOGIN_ROUTES.push(...addon.NO_LOGIN_ROUTES)
|
addon.NO_LOGIN_ROUTES && NO_LOGIN_ROUTES.push(...addon.NO_LOGIN_ROUTES)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -69,11 +66,7 @@ router.beforeEach(async (to: any, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载语言包
|
// 加载语言包
|
||||||
await language.loadLocaleMessages(
|
await language.loadLocaleMessages(to.meta.addon || '', (to.meta.view || to.path), systemStore.lang);
|
||||||
resolveRouteAddon(to),
|
|
||||||
resolveRouteView(to),
|
|
||||||
systemStore.lang
|
|
||||||
);
|
|
||||||
|
|
||||||
let matched: any = to.matched;
|
let matched: any = to.matched;
|
||||||
if (matched && matched.length && matched[0].path != '/:pathMatch(.*)*') {
|
if (matched && matched.length && matched[0].path != '/:pathMatch(.*)*') {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { RouteRecordRaw, RouterView } from 'vue-router'
|
import { RouteRecordRaw, RouterView } from 'vue-router'
|
||||||
import Default from '@/layout/index.vue'
|
import Default from '@/layout/index.vue'
|
||||||
import Decorate from '@/layout/decorate/index.vue'
|
import Decorate from '@/layout/decorate/index.vue'
|
||||||
import { loadAddonView } from '@/utils/addon-loader'
|
|
||||||
|
|
||||||
// 静态路由
|
// 静态路由
|
||||||
export const STATIC_ROUTES: Array<RouteRecordRaw> = [
|
export const STATIC_ROUTES: Array<RouteRecordRaw> = [
|
||||||
@ -105,9 +104,8 @@ export const DECORATE_ROUTER: RouteRecordRaw = {
|
|||||||
children: []
|
children: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules = import.meta.glob('../app/views/**/*.vue')
|
const modules = import.meta.glob('@/app/views/**/*.vue')
|
||||||
// 开发环境:直接用 import.meta.glob 从源码加载所有插件视图(无需 manifest)
|
const addonModules = import.meta.glob('@/addon/**/views/**/*.vue')
|
||||||
const addonModules = import.meta.glob('../addon/**/views/**/*.vue')
|
|
||||||
|
|
||||||
interface Route {
|
interface Route {
|
||||||
menu_name: string,
|
menu_name: string,
|
||||||
@ -153,26 +151,7 @@ const createRoute = function (route: Route, parentRoute: RouteRecordRaw | null =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (route.menu_type == 1) {
|
if (route.menu_type == 1) {
|
||||||
if (route.addon) {
|
record.component = route.addon ? addonModules[`/src/addon/${ route.addon }/views/${ route.view_path }.vue`] : modules[`/src/app/views/${ route.view_path }.vue`]
|
||||||
// view_path 优先,为空时 fallback 用 router_path
|
|
||||||
let viewPath = route.view_path
|
|
||||||
if (!viewPath && route.router_path) {
|
|
||||||
viewPath = route.router_path
|
|
||||||
}
|
|
||||||
if (viewPath) {
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
// 开发环境:直接用 import.meta.glob 从源码加载(无需 addon-loader/manifest)
|
|
||||||
record.component = addonModules[`../addon/${route.addon}/views/${viewPath}.vue`]
|
|
||||||
} else {
|
|
||||||
// 生产环境:通过编译产物的 addon-loader 加载
|
|
||||||
record.component = loadAddonView(route.addon, viewPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 无 viewPath 且无 router_path → 不设 component,作为容器路由,
|
|
||||||
// 其 children(由 formatRouters 递归创建)通过父级 <RouterView> 渲染
|
|
||||||
} else {
|
|
||||||
record.component = modules[`../app/views/${ route.view_path }.vue`]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return record
|
return record
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,643 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'hi_iconfont'; /* Project id 4494655 */
|
|
||||||
src: url('//at.alicdn.com/t/c/font_4494655_tdxdqgcaf5r.woff2?t=1737198657789') format('woff2'),
|
|
||||||
url('//at.alicdn.com/t/c/font_4494655_tdxdqgcaf5r.woff?t=1737198657789') format('woff'),
|
|
||||||
url('//at.alicdn.com/t/c/font_4494655_tdxdqgcaf5r.ttf?t=1737198657789') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_iconfont {
|
|
||||||
font-family: "hi_iconfont" !important;
|
|
||||||
font-size: 16px;
|
|
||||||
font-style: normal;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-biaodan:before {
|
|
||||||
content: "\e669";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dianhua:before {
|
|
||||||
content: "\e665";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shengao:before {
|
|
||||||
content: "\e662";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-edu-line:before {
|
|
||||||
content: "\e663";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-edu-s:before {
|
|
||||||
content: "\e664";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-sousuo:before {
|
|
||||||
content: "\e661";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fenxiang:before {
|
|
||||||
content: "\e660";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-nv1:before {
|
|
||||||
content: "\e668";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xuanzhong:before {
|
|
||||||
content: "\e65f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-nan:before {
|
|
||||||
content: "\e65d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-nv:before {
|
|
||||||
content: "\e8b3";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shezhi1:before {
|
|
||||||
content: "\e65c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jifenshuoming:before {
|
|
||||||
content: "\e7ae";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tuiguangpaihang:before {
|
|
||||||
content: "\e65b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingdan1:before {
|
|
||||||
content: "\e6ad";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dengjixunzhang:before {
|
|
||||||
content: "\1012e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yongjin:before {
|
|
||||||
content: "\e708";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-paihangbang:before {
|
|
||||||
content: "\e66e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-icon:before {
|
|
||||||
content: "\e658";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingdan:before {
|
|
||||||
content: "\e655";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-top:before {
|
|
||||||
content: "\e659";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tuandui:before {
|
|
||||||
content: "\e65a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shuoming:before {
|
|
||||||
content: "\f4f9";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fenxiao-fenxiaojilu:before {
|
|
||||||
content: "\e653";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-RectangleCopy:before {
|
|
||||||
content: "\e6b1";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-pc-fenxiaoshangfenxiao:before {
|
|
||||||
content: "\e786";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-woyaofenxiao:before {
|
|
||||||
content: "\e66f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fenxiao-fenxiaoshuju:before {
|
|
||||||
content: "\e8d1";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fenxiaoyeji:before {
|
|
||||||
content: "\e76c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jiantou1:before {
|
|
||||||
content: "\e64e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jiantoushang:before {
|
|
||||||
content: "\e652";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-erweima:before {
|
|
||||||
content: "\e64b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yanse:before {
|
|
||||||
content: "\e679";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-youhuiquan2:before {
|
|
||||||
content: "\e884";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shouye:before {
|
|
||||||
content: "\e649";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-weixin:before {
|
|
||||||
content: "\e64a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dashiruzhu:before {
|
|
||||||
content: "\e645";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-hehuoren:before {
|
|
||||||
content: "\e646";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daili:before {
|
|
||||||
content: "\e647";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-amazon-onestore:before {
|
|
||||||
content: "\e75f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-a-Property1yewuyuanruzhu:before {
|
|
||||||
content: "\e648";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-mendianruzhu:before {
|
|
||||||
content: "\e66b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-mendian1:before {
|
|
||||||
content: "\e682";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shijian:before {
|
|
||||||
content: "\e63f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shezhi:before {
|
|
||||||
content: "\e656";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jishiguanli:before {
|
|
||||||
content: "\e63e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jujue1:before {
|
|
||||||
content: "\e639";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tuikuanshouhou:before {
|
|
||||||
content: "\e6bb";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zhuandan:before {
|
|
||||||
content: "\e677";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tongyi:before {
|
|
||||||
content: "\e636";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jujue:before {
|
|
||||||
content: "\e638";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-wancheng1:before {
|
|
||||||
content: "\e633";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daijiedan1:before {
|
|
||||||
content: "\e62b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chufa:before {
|
|
||||||
content: "\e62c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-bangdingqudaoshang:before {
|
|
||||||
content: "\e629";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xiangmu:before {
|
|
||||||
content: "\e614";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-kehuC:before {
|
|
||||||
content: "\e60d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-huiyuan2:before {
|
|
||||||
content: "\e644";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-qianbao1:before {
|
|
||||||
content: "\e651";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dapinglunxun:before {
|
|
||||||
content: "\e627";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yingshoubaobiao:before {
|
|
||||||
content: "\e6b2";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xuanzekuangmoren:before {
|
|
||||||
content: "\e6c1";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xuanzekuangxuanzhong:before {
|
|
||||||
content: "\e678";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-icon-test1:before {
|
|
||||||
content: "\e666";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zan1:before {
|
|
||||||
content: "\e872";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tubiaozhizuo-:before {
|
|
||||||
content: "\e60f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jia:before {
|
|
||||||
content: "\e625";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shipin1:before {
|
|
||||||
content: "\e622";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tuwenzixun:before {
|
|
||||||
content: "\e683";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shipin:before {
|
|
||||||
content: "\e642";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zan:before {
|
|
||||||
content: "\e870";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-pinglun3:before {
|
|
||||||
content: "\e641";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xiangji1fill:before {
|
|
||||||
content: "\e77e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xiangji:before {
|
|
||||||
content: "\e621";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xuanze:before {
|
|
||||||
content: "\e62f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xuanze-weixuanze:before {
|
|
||||||
content: "\e615";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-gou:before {
|
|
||||||
content: "\e66c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dacha:before {
|
|
||||||
content: "\e711";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chanpin1:before {
|
|
||||||
content: "\e61f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xiaoxi:before {
|
|
||||||
content: "\e635";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chanpin:before {
|
|
||||||
content: "\e650";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-airudiantubiaohuizhi-zhuanqu_zixundongtai:before {
|
|
||||||
content: "\e69d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dongtai:before {
|
|
||||||
content: "\e64f";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-ziyuan:before {
|
|
||||||
content: "\e61d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-youhuiquan1:before {
|
|
||||||
content: "\e61e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shengyin08-mianxing:before {
|
|
||||||
content: "\e6eb";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-pinglun2:before {
|
|
||||||
content: "\e63a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zanfill:before {
|
|
||||||
content: "\e6ce";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fensiguanli:before {
|
|
||||||
content: "\e62a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-basesalesupgradeSet:before {
|
|
||||||
content: "\e632";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tixian:before {
|
|
||||||
content: "\e67c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tixian1:before {
|
|
||||||
content: "\e619";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingdanwuliaocaigouguanli:before {
|
|
||||||
content: "\e63b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chefeijilu:before {
|
|
||||||
content: "\e613";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zhongchaping:before {
|
|
||||||
content: "\e634";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-addBlack:before {
|
|
||||||
content: "\e630";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yidongfanyong:before {
|
|
||||||
content: "\eb68";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fanyong:before {
|
|
||||||
content: "\e67d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fanyongjilu:before {
|
|
||||||
content: "\e605";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daifuwu2:before {
|
|
||||||
content: "\e7e5";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daijiedan:before {
|
|
||||||
content: "\e60c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jiantou:before {
|
|
||||||
content: "\e65e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-icon-test:before {
|
|
||||||
content: "\e60b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-show_more:before {
|
|
||||||
content: "\e637";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jiantoukongxin_up:before {
|
|
||||||
content: "\e612";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jiantou-zuoxia:before {
|
|
||||||
content: "\e643";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zuojiantou:before {
|
|
||||||
content: "\e61b";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-xiajiantou:before {
|
|
||||||
content: "\e6b3";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-alarm-full:before {
|
|
||||||
content: "\e871";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-weizhigengxin:before {
|
|
||||||
content: "\e929";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jihuatingjishijianshezhi_:before {
|
|
||||||
content: "\e60a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-ditu:before {
|
|
||||||
content: "\e72d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jinjiqiuzhu-:before {
|
|
||||||
content: "\e6be";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-qianbao:before {
|
|
||||||
content: "\e829";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zhifubaozhifu:before {
|
|
||||||
content: "\e654";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-weixinzhifu:before {
|
|
||||||
content: "\e607";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei:before {
|
|
||||||
content: "\e63c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei1:before {
|
|
||||||
content: "\e603";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei2:before {
|
|
||||||
content: "\e604";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei3:before {
|
|
||||||
content: "\e717";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei4:before {
|
|
||||||
content: "\e8c4";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dingwei5:before {
|
|
||||||
content: "\e62d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zaixianjiedan2mian:before {
|
|
||||||
content: "\e64d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-wancheng:before {
|
|
||||||
content: "\e60e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chufadi:before {
|
|
||||||
content: "\e6ff";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-chufagangkou:before {
|
|
||||||
content: "\e626";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daodamudedi:before {
|
|
||||||
content: "\e657";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-woyaofankui:before {
|
|
||||||
content: "\e631";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-pinglun1:before {
|
|
||||||
content: "\e8b4";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-tousutiwen:before {
|
|
||||||
content: "\e624";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-quanqudaofuwujiankong:before {
|
|
||||||
content: "\e6cf";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-mendian:before {
|
|
||||||
content: "\e61a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-fenxianglaxin:before {
|
|
||||||
content: "\e617";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dituibang1-09:before {
|
|
||||||
content: "\e618";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-laxinliebian:before {
|
|
||||||
content: "\e74c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-quan:before {
|
|
||||||
content: "\e602";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shoucang:before {
|
|
||||||
content: "\e623";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-bangzhu:before {
|
|
||||||
content: "\e8a3";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-youhuiquan:before {
|
|
||||||
content: "\e61c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daifuwu1:before {
|
|
||||||
content: "\e610";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-pinglun:before {
|
|
||||||
content: "\e609";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daipingjia20:before {
|
|
||||||
content: "\e63d";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yiwancheng:before {
|
|
||||||
content: "\e6a6";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yiquxiao:before {
|
|
||||||
content: "\e69c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daizhifu:before {
|
|
||||||
content: "\e694";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-icon_xinyong_xianxing_jijin-:before {
|
|
||||||
content: "\e640";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-licai:before {
|
|
||||||
content: "\e600";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-diwup12:before {
|
|
||||||
content: "\e62e";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shenqingdailishang:before {
|
|
||||||
content: "\e608";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-dailishang:before {
|
|
||||||
content: "\e628";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yewuyuan:before {
|
|
||||||
content: "\e601";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-zu176:before {
|
|
||||||
content: "\e606";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-shenpitongguo:before {
|
|
||||||
content: "\e611";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-daifuwu:before {
|
|
||||||
content: "\e64c";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-jishi:before {
|
|
||||||
content: "\e616";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yonghu:before {
|
|
||||||
content: "\e620";
|
|
||||||
}
|
|
||||||
|
|
||||||
.hi_icon-yonghu1:before {
|
|
||||||
content: "\e667";
|
|
||||||
}
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,272 +0,0 @@
|
|||||||
/**
|
|
||||||
* 插件语言与 manifest 初始化(供 core shared admin-lang 与 addon-loader 共用)
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface AddonManifest {
|
|
||||||
key: string
|
|
||||||
version?: string
|
|
||||||
sharedVersion?: string
|
|
||||||
views?: Record<string, string>
|
|
||||||
components?: Record<string, string>
|
|
||||||
layout?: string
|
|
||||||
langBase?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
let prodManifests: Record<string, AddonManifest> = {}
|
|
||||||
let installedAddonKeys: string[] = []
|
|
||||||
let prodReady = false
|
|
||||||
let prodInitPromise: Promise<void> | null = null
|
|
||||||
const addonModuleCache: Record<string, Promise<Record<string, unknown> | null>> = {}
|
|
||||||
|
|
||||||
const ADMIN_PREFIX = '/admin'
|
|
||||||
|
|
||||||
export function adminUrl(relative: string) {
|
|
||||||
if (!relative) return relative
|
|
||||||
if (relative.startsWith('http') || relative.startsWith('/admin/')) return relative
|
|
||||||
if (relative.startsWith('/assets/')) return `${ADMIN_PREFIX}${relative}`
|
|
||||||
if (relative.startsWith('./')) return `${ADMIN_PREFIX}/assets/addons/${relative.slice(2)}`
|
|
||||||
return `${ADMIN_PREFIX}/${relative.replace(/^\//, '')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function addonAssetUrl(addon: string, ...segments: string[]) {
|
|
||||||
const rel = ['assets', 'addons', addon, ...segments].join('/').replace(/\/+/g, '/')
|
|
||||||
return `${ADMIN_PREFIX}/${rel}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveLangFile(app: string, path: string): string {
|
|
||||||
if (path === '/') return 'index'
|
|
||||||
let view = path.replace(/^(\/admin\/|\/site\/|\/)/, '').replace(/\.vue$/, '')
|
|
||||||
if (view.startsWith('views/')) view = view.slice('views/'.length)
|
|
||||||
if (app) {
|
|
||||||
if (view.startsWith(`${app}/`)) view = view.slice(app.length + 1)
|
|
||||||
else if (view === app) view = 'index'
|
|
||||||
}
|
|
||||||
return view.replaceAll('/', '.')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveRouteAddon(route: { meta: Record<string, unknown>; matched: Array<{ meta: Record<string, unknown> }> }): string {
|
|
||||||
if (route.meta.addon) return String(route.meta.addon)
|
|
||||||
for (let i = route.matched.length - 1; i >= 0; i--) {
|
|
||||||
const addon = route.matched[i].meta.addon
|
|
||||||
if (addon) return String(addon)
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveRouteView(route: { meta: Record<string, unknown>; matched: Array<{ meta: Record<string, unknown> }>; path: string }): string {
|
|
||||||
if (route.meta.view) return String(route.meta.view)
|
|
||||||
for (let i = route.matched.length - 1; i >= 0; i--) {
|
|
||||||
const view = route.matched[i].meta.view
|
|
||||||
if (view) return String(view)
|
|
||||||
}
|
|
||||||
return route.path
|
|
||||||
}
|
|
||||||
|
|
||||||
function unwrapLangPack(pack: unknown): Record<string, string> {
|
|
||||||
if (!pack || typeof pack !== 'object') return {}
|
|
||||||
const obj = pack as Record<string, unknown>
|
|
||||||
if (obj.default && typeof obj.default === 'object') {
|
|
||||||
return unwrapLangPack(obj.default)
|
|
||||||
}
|
|
||||||
const result: Record<string, string> = {}
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
|
||||||
if (key === 'default' || key === '__esModule') continue
|
|
||||||
if (typeof value === 'string') result[key] = value
|
|
||||||
}
|
|
||||||
return Object.keys(result).length ? result : (obj as Record<string, string>)
|
|
||||||
}
|
|
||||||
|
|
||||||
function flattenLangPack(file: string, pack: Record<string, string>): Record<string, string> {
|
|
||||||
const data: Record<string, string> = {}
|
|
||||||
for (const [key, value] of Object.entries(pack)) {
|
|
||||||
if (typeof value !== 'string') continue
|
|
||||||
data[`${file}.${key}`] = value
|
|
||||||
if (file === 'common') data[key] = value
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inferAddonFromPath(path: string, knownAddons?: string[]): string {
|
|
||||||
const view = path.replace(/^(\/admin\/|\/site\/|\/)/, '')
|
|
||||||
const first = view.split('/').filter(Boolean)[0]
|
|
||||||
if (!first) return ''
|
|
||||||
const keys = knownAddons?.length ? knownAddons : getInstalledAddonKeys()
|
|
||||||
return keys.includes(first) ? first : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isAddonEntryAvailable(key: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const res = await fetch(adminUrl(`/assets/addons/${key}/index.js`), { method: 'HEAD' })
|
|
||||||
if (!res.ok) return false
|
|
||||||
const ct = (res.headers.get('content-type') || '').toLowerCase()
|
|
||||||
return !ct.includes('text/html')
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAddonInstalled(addon: string): boolean {
|
|
||||||
return !!addon && getInstalledAddonKeys().includes(addon)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadAddonModule(addon: string): Promise<Record<string, unknown> | null> {
|
|
||||||
if (!(addon in addonModuleCache)) {
|
|
||||||
addonModuleCache[addon] = (async () => {
|
|
||||||
try {
|
|
||||||
const url = adminUrl(`/assets/addons/${addon}/index.js`)
|
|
||||||
return await import(/* @vite-ignore */ url) as Record<string, unknown>
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
return addonModuleCache[addon]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 清除插件模块缓存(布局切换时调用,确保样式重新注入) */
|
|
||||||
export function clearAddonModuleCache() {
|
|
||||||
for (const key of Object.keys(addonModuleCache)) {
|
|
||||||
delete addonModuleCache[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAddonProdReady() {
|
|
||||||
return prodReady
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ensureAddonProdReady() {
|
|
||||||
if (!prodReady) await initAddonManifests()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initAddonManifests(keys?: string[]) {
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
prodReady = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (prodInitPromise) return prodInitPromise
|
|
||||||
|
|
||||||
prodInitPromise = (async () => {
|
|
||||||
prodManifests = {}
|
|
||||||
installedAddonKeys = []
|
|
||||||
let addonKeys = keys
|
|
||||||
if (!addonKeys?.length) {
|
|
||||||
try {
|
|
||||||
const indexUrl = adminUrl('/assets/addons/index.json')
|
|
||||||
const res = await fetch(indexUrl)
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json()
|
|
||||||
addonKeys = Array.isArray(data.keys) ? data.keys : []
|
|
||||||
// 读取合并后的 manifests(PHP 端已打包进 index.json),避免 N 次 manifest.json 请求
|
|
||||||
const bundled = data.manifests as Record<string, AddonManifest> | undefined
|
|
||||||
if (bundled) {
|
|
||||||
for (const [k, m] of Object.entries(bundled)) {
|
|
||||||
prodManifests[k] = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
addonKeys = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const verifiedKeys: string[] = []
|
|
||||||
for (const key of addonKeys || []) {
|
|
||||||
// 如果 manifest 已从 index.json 获取,直接验证
|
|
||||||
if (prodManifests[key]) {
|
|
||||||
if (await isAddonEntryAvailable(key)) {
|
|
||||||
verifiedKeys.push(key)
|
|
||||||
} else {
|
|
||||||
delete prodManifests[key]
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 兼容旧版:单独请求 manifest.json
|
|
||||||
if (!(await isAddonEntryAvailable(key))) continue
|
|
||||||
verifiedKeys.push(key)
|
|
||||||
try {
|
|
||||||
const url = adminUrl(`/assets/addons/${key}/manifest.json`)
|
|
||||||
const res = await fetch(url)
|
|
||||||
if (res.ok) {
|
|
||||||
prodManifests[key] = await res.json()
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// skip missing manifest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
installedAddonKeys = verifiedKeys
|
|
||||||
prodReady = true
|
|
||||||
})()
|
|
||||||
|
|
||||||
return prodInitPromise
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getInstalledAddonKeys(): string[] {
|
|
||||||
if (installedAddonKeys.length) return installedAddonKeys
|
|
||||||
return Object.keys(prodManifests)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getProdManifests(): Record<string, AddonManifest> {
|
|
||||||
return prodManifests
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preloadAllAddonLangs(
|
|
||||||
merge: (locale: string, messages: Record<string, string>) => void
|
|
||||||
) {
|
|
||||||
if (import.meta.env.DEV) return
|
|
||||||
if (!prodReady) await initAddonManifests()
|
|
||||||
|
|
||||||
for (const addon of getInstalledAddonKeys()) {
|
|
||||||
try {
|
|
||||||
const mod = await loadAddonModule(addon)
|
|
||||||
if (!mod) continue
|
|
||||||
const langs = mod.langs as Record<string, Record<string, unknown>> | undefined
|
|
||||||
if (!langs) continue
|
|
||||||
for (const [locale, files] of Object.entries(langs)) {
|
|
||||||
const merged: Record<string, string> = {}
|
|
||||||
for (const [file, pack] of Object.entries(files || {})) {
|
|
||||||
Object.assign(merged, flattenLangPack(file, unwrapLangPack(pack)))
|
|
||||||
}
|
|
||||||
if (Object.keys(merged).length) merge(locale, merged)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// skip broken addon module
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerAddonManifest(manifest: AddonManifest) {
|
|
||||||
prodManifests[manifest.key] = manifest
|
|
||||||
prodReady = true
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadAddonLang(addon: string, locale: string, file: string): Promise<Record<string, string>> {
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
try {
|
|
||||||
const messages = await import(/* @vite-ignore */ `@/addon/${addon}/lang/${locale}/${file}.json`)
|
|
||||||
return messages.default || {}
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!prodReady) await initAddonManifests()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const mod = await loadAddonModule(addon)
|
|
||||||
if (mod) {
|
|
||||||
const langs = mod.langs as Record<string, Record<string, Record<string, unknown>>> | undefined
|
|
||||||
const pack = langs?.[locale]?.[file]
|
|
||||||
const messages = unwrapLangPack(pack)
|
|
||||||
if (Object.keys(messages).length) return messages
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// fallback to static lang files
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = addonAssetUrl(addon, 'lang', locale, `${file}.json`)
|
|
||||||
try {
|
|
||||||
const res = await fetch(url)
|
|
||||||
if (res.ok) return await res.json()
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
@ -1,204 +0,0 @@
|
|||||||
/**
|
|
||||||
* 插件运行时加载:dev 用 import.meta.glob 直读源码,prod 用 drop-in 编译产物
|
|
||||||
*/
|
|
||||||
import type { Component } from 'vue'
|
|
||||||
import { defineAsyncComponent } from 'vue'
|
|
||||||
import {
|
|
||||||
type AddonManifest,
|
|
||||||
adminUrl,
|
|
||||||
clearAddonModuleCache,
|
|
||||||
ensureAddonProdReady,
|
|
||||||
getProdManifests,
|
|
||||||
initAddonManifests,
|
|
||||||
loadAddonLang,
|
|
||||||
loadAddonModule,
|
|
||||||
preloadAllAddonLangs,
|
|
||||||
registerAddonManifest,
|
|
||||||
resolveLangFile,
|
|
||||||
resolveRouteAddon,
|
|
||||||
resolveRouteView,
|
|
||||||
inferAddonFromPath,
|
|
||||||
getInstalledAddonKeys,
|
|
||||||
isAddonInstalled
|
|
||||||
} from '@/utils/addon-lang'
|
|
||||||
|
|
||||||
export type { AddonManifest }
|
|
||||||
export {
|
|
||||||
adminUrl,
|
|
||||||
initAddonManifests,
|
|
||||||
loadAddonLang,
|
|
||||||
preloadAllAddonLangs,
|
|
||||||
registerAddonManifest,
|
|
||||||
resolveLangFile,
|
|
||||||
resolveRouteAddon,
|
|
||||||
resolveRouteView,
|
|
||||||
inferAddonFromPath,
|
|
||||||
getInstalledAddonKeys,
|
|
||||||
loadAddonModule
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 清除插件样式 DOM 节点和模块缓存(布局切换时用) */
|
|
||||||
export function clearAddonStyleCache() {
|
|
||||||
document.querySelectorAll('style[data-addon-style]').forEach(el => el.remove())
|
|
||||||
clearAddonModuleCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ViewLoader = () => Promise<{ default: Component }>
|
|
||||||
|
|
||||||
const load404 = (): Promise<{ default: Component }> =>
|
|
||||||
import('@/app/views/error/404.vue') as Promise<{ default: Component }>
|
|
||||||
|
|
||||||
// 开发环境:用 import.meta.glob 直读所有插件源码(无需 addon-manifest.dev.ts)
|
|
||||||
const allAddonVueModules = import.meta.glob('../addon/**/views/**/*.vue')
|
|
||||||
const addonLayoutModules = import.meta.glob('../addon/*/layout/index.vue')
|
|
||||||
|
|
||||||
async function importProdModule(url: string): Promise<Component | null> {
|
|
||||||
try {
|
|
||||||
const full = adminUrl(url.startsWith('./') ? url : url)
|
|
||||||
const mod = await import(/* @vite-ignore */ full)
|
|
||||||
return mod.default ?? mod
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadAddonView(addon: string, viewPath: string): ViewLoader {
|
|
||||||
if (!addon) {
|
|
||||||
return () => load404()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开发环境:glob 直读源码(routers.ts 已优先走 glob,此处为兜底)
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
return async () => {
|
|
||||||
const loader = allAddonVueModules[`../addon/${addon}/views/${viewPath}.vue`] as ViewLoader | undefined
|
|
||||||
if (loader) return loader()
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return async () => {
|
|
||||||
await ensureAddonProdReady()
|
|
||||||
if (!isAddonInstalled(addon)) {
|
|
||||||
console.error(`[loadAddonView] addon "${addon}" not installed. Installed:`, getInstalledAddonKeys())
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const mod = await loadAddonModule(addon)
|
|
||||||
if (!mod) {
|
|
||||||
console.error(`[loadAddonView] addon "${addon}" module loaded as null/undefined`)
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
const loader = mod.views?.[viewPath] as ViewLoader | undefined
|
|
||||||
if (!loader) {
|
|
||||||
const available = Object.keys(mod.views || {})
|
|
||||||
console.error(`[loadAddonView] addon="${addon}" viewPath="${viewPath}" NOT FOUND. Available (${available.length}):`, available.slice(0, 10), available.length > 10 ? `... and ${available.length - 10} more` : '')
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return await loader()
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[loadAddonView] addon="${addon}" viewPath="${viewPath}" loader threw:`, e)
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[loadAddonView] addon="${addon}" module import failed:`, e)
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadAddonComponent(addon: string, subPath: string, name: string): ViewLoader {
|
|
||||||
const key = `${subPath}/${name}`.replace(/\/+/g, '/')
|
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
return async () => {
|
|
||||||
const loader = allAddonVueModules[`../addon/${addon}/views/${subPath}/${name}.vue`] as ViewLoader | undefined
|
|
||||||
if (loader) return loader()
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (async () => {
|
|
||||||
await ensureAddonProdReady()
|
|
||||||
if (!isAddonInstalled(addon)) return null as any
|
|
||||||
try {
|
|
||||||
const mod = await loadAddonModule(addon)
|
|
||||||
if (!mod) return null as any
|
|
||||||
const components = (mod as Record<string, unknown>).components as Record<string, ViewLoader> | undefined
|
|
||||||
if (!components) return null as any
|
|
||||||
const loader = components[key] as ViewLoader | undefined
|
|
||||||
if (!loader) return null as any
|
|
||||||
return await loader()
|
|
||||||
} catch {
|
|
||||||
return null as any
|
|
||||||
}
|
|
||||||
}) as unknown as ViewLoader
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadAddonLayout(addon: string): ViewLoader | null {
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
return async () => {
|
|
||||||
const loader = addonLayoutModules[`../addon/${addon}/layout/index.vue`] as ViewLoader | undefined
|
|
||||||
if (loader) return loader()
|
|
||||||
return load404()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return async () => {
|
|
||||||
await ensureAddonProdReady()
|
|
||||||
if (!isAddonInstalled(addon)) return load404()
|
|
||||||
const rel = getProdManifests()[addon]?.layout
|
|
||||||
if (!rel) return load404()
|
|
||||||
const mod = await importProdModule(rel)
|
|
||||||
if (!mod) return load404()
|
|
||||||
return { default: mod as Component }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Core 内动态组件(pay/diy-link 等),不含 addon 部分 */
|
|
||||||
export const coreVueModules = {
|
|
||||||
...import.meta.glob('../app/**/*.vue'),
|
|
||||||
...import.meta.glob('../components/**/*.vue'),
|
|
||||||
...import.meta.glob('../layout/**/*.vue')
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeComponentKey(p: string): string {
|
|
||||||
return p
|
|
||||||
.replace(/^@\//, '')
|
|
||||||
.replace(/^\/src\//, '')
|
|
||||||
.replace(/^\.\.\//, '')
|
|
||||||
.replace(/\\/g, '/')
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCoreModuleLoader(componentPath: string): ViewLoader | undefined {
|
|
||||||
const map = coreVueModules as Record<string, ViewLoader>
|
|
||||||
if (map[componentPath]) return map[componentPath]
|
|
||||||
const target = normalizeComponentKey(componentPath)
|
|
||||||
const key = Object.keys(map).find((k) => normalizeComponentKey(k) === target || normalizeComponentKey(k).endsWith(`/${target}`))
|
|
||||||
return key ? map[key] : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveAsyncComponent(componentPath: string) {
|
|
||||||
return defineAsyncComponent(async () => {
|
|
||||||
const loader = findCoreModuleLoader(componentPath)
|
|
||||||
if (loader) return (await loader()).default as Component
|
|
||||||
const mod = await loadCoreVueModule(componentPath)
|
|
||||||
return mod.default as Component
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadCoreVueModule(componentPath: string) {
|
|
||||||
const loader = findCoreModuleLoader(componentPath)
|
|
||||||
if (loader) return loader()
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
if (componentPath.includes('/addon/')) {
|
|
||||||
const m = componentPath.match(/\/addon\/([^/]+)\/views\/(.+)\.vue$/)
|
|
||||||
if (m) {
|
|
||||||
const [, addon, rest] = m
|
|
||||||
const viewLoader = allAddonVueModules[`../addon/${addon}/views/${rest}.vue`] as ViewLoader | undefined
|
|
||||||
if (viewLoader) return viewLoader()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Component not found: ${componentPath}`)
|
|
||||||
}
|
|
||||||
@ -3,7 +3,6 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|||||||
import { useCssVar, useTitle } from '@vueuse/core'
|
import { useCssVar, useTitle } from '@vueuse/core'
|
||||||
import colorFunction from 'css-color-function'
|
import colorFunction from 'css-color-function'
|
||||||
import storage from './storage'
|
import storage from './storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局注册element-icon
|
* 全局注册element-icon
|
||||||
@ -66,7 +65,7 @@ export function getAppType() {
|
|||||||
*/
|
*/
|
||||||
export function setWindowTitle(value: string = ''): void {
|
export function setWindowTitle(value: string = ''): void {
|
||||||
const title = useTitle()
|
const title = useTitle()
|
||||||
title.value = value ? value : envConfig.VITE_DETAULT_TITLE
|
title.value = value ? value : import.meta.env.VITE_DETAULT_TITLE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,7 +129,7 @@ export function isUrl(str: string): boolean {
|
|||||||
export function img(path: string): string {
|
export function img(path: string): string {
|
||||||
if (!path) return ''
|
if (!path) return ''
|
||||||
|
|
||||||
let imgDomain = envConfig.VITE_IMG_DOMAIN || location.origin
|
let imgDomain = import.meta.env.VITE_IMG_DOMAIN || location.origin
|
||||||
|
|
||||||
if (path.startsWith('/')) path = path.replace(/^\//, '')
|
if (path.startsWith('/')) path = path.replace(/^\//, '')
|
||||||
if (imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
|
if (imgDomain.endsWith('/')) imgDomain = imgDomain.slice(0, -1)
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
// 优先读取外挂 window.__ENV__,部署后可直接修改,无需重编译
|
|
||||||
const env = (typeof window !== 'undefined' ? (window as any).__ENV__ : null) || {}
|
|
||||||
|
|
||||||
// import.meta.env 在 lib 构建模式下可能不被替换,此处确保每一行都有硬编码兜底
|
|
||||||
const APP_BASE_URL = env.VITE_APP_BASE_URL || import.meta.env.VITE_APP_BASE_URL || '/adminapi/'
|
|
||||||
const IMG_DOMAIN = env.VITE_IMG_DOMAIN || import.meta.env.VITE_IMG_DOMAIN || ''
|
|
||||||
const HEADER_TOKEN_KEY = env.VITE_REQUEST_HEADER_TOKEN_KEY || import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY || 'token'
|
|
||||||
const HEADER_SITEID_KEY = env.VITE_REQUEST_HEADER_SITEID_KEY || import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY || 'site-id'
|
|
||||||
const DEFAULT_TITLE = env.VITE_DETAULT_TITLE || import.meta.env.VITE_DETAULT_TITLE || ''
|
|
||||||
|
|
||||||
export default {
|
|
||||||
VITE_APP_BASE_URL: APP_BASE_URL,
|
|
||||||
VITE_IMG_DOMAIN: IMG_DOMAIN,
|
|
||||||
VITE_REQUEST_HEADER_TOKEN_KEY: HEADER_TOKEN_KEY,
|
|
||||||
VITE_REQUEST_HEADER_SITEID_KEY: HEADER_SITEID_KEY,
|
|
||||||
VITE_DETAULT_TITLE: DEFAULT_TITLE
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
* Core 内动态组件解析(pay/diy-link 等),仅 core 使用,不可打进 addon 包
|
|
||||||
*/
|
|
||||||
import type { Component } from 'vue'
|
|
||||||
import { defineAsyncComponent } from 'vue'
|
|
||||||
|
|
||||||
export const coreVueModules = {
|
|
||||||
...import.meta.glob('../app/**/*.vue'),
|
|
||||||
...import.meta.glob('../components/**/*.vue'),
|
|
||||||
...import.meta.glob('../layout/**/*.vue')
|
|
||||||
}
|
|
||||||
|
|
||||||
type ViewLoader = () => Promise<{ default: Component }>
|
|
||||||
|
|
||||||
function normalizeComponentKey(p: string): string {
|
|
||||||
return p
|
|
||||||
.replace(/^@\//, '')
|
|
||||||
.replace(/^\/src\//, '')
|
|
||||||
.replace(/^\.\.\//, '')
|
|
||||||
.replace(/\\/g, '/')
|
|
||||||
}
|
|
||||||
|
|
||||||
function findCoreModuleLoader(componentPath: string): ViewLoader | undefined {
|
|
||||||
const map = coreVueModules as Record<string, ViewLoader>
|
|
||||||
if (map[componentPath]) return map[componentPath]
|
|
||||||
const target = normalizeComponentKey(componentPath)
|
|
||||||
const key = Object.keys(map).find((k) => normalizeComponentKey(k) === target || normalizeComponentKey(k).endsWith(`/${target}`))
|
|
||||||
return key ? map[key] : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveAsyncComponent(componentPath: string) {
|
|
||||||
return defineAsyncComponent(async () => {
|
|
||||||
const loader = findCoreModuleLoader(componentPath)
|
|
||||||
if (loader) return (await loader()).default as Component
|
|
||||||
const mod = await loadCoreVueModule(componentPath)
|
|
||||||
return mod.default as Component
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开发环境:glob 直读插件源码(无需 addon-manifest.dev.ts)
|
|
||||||
const allAddonVueModules = import.meta.glob('../addon/**/views/**/*.vue')
|
|
||||||
|
|
||||||
export async function loadCoreVueModule(componentPath: string) {
|
|
||||||
const loader = findCoreModuleLoader(componentPath)
|
|
||||||
if (loader) return loader()
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
if (componentPath.includes('/addon/')) {
|
|
||||||
const m = componentPath.match(/\/addon\/([^/]+)\/views\/(.+)\.vue$/)
|
|
||||||
if (m) {
|
|
||||||
const [, addon, rest] = m
|
|
||||||
const viewLoader = (allAddonVueModules as Record<string, ViewLoader>)[`../addon/${addon}/views/${rest}.vue`]
|
|
||||||
if (viewLoader) return viewLoader()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Component not found: ${componentPath}`)
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@ import type { MessageParams } from 'element-plus'
|
|||||||
import { t } from '@/lang'
|
import { t } from '@/lang'
|
||||||
import useUserStore from '@/stores/modules/user'
|
import useUserStore from '@/stores/modules/user'
|
||||||
import storage from '@/utils/storage'
|
import storage from '@/utils/storage'
|
||||||
import envConfig from '@/utils/config'
|
|
||||||
|
|
||||||
interface RequestConfig extends AxiosRequestConfig {
|
interface RequestConfig extends AxiosRequestConfig {
|
||||||
showErrorMessage?: boolean
|
showErrorMessage?: boolean
|
||||||
@ -47,9 +46,8 @@ class Request {
|
|||||||
private instance: AxiosInstance;
|
private instance: AxiosInstance;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const appBaseUrl = envConfig.VITE_APP_BASE_URL || '/adminapi/'
|
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: appBaseUrl.endsWith('/') ? appBaseUrl : `${appBaseUrl}/`,
|
baseURL: import.meta.env.VITE_APP_BASE_URL.substr(-1) == '/' ? import.meta.env.VITE_APP_BASE_URL : `${import.meta.env.VITE_APP_BASE_URL}/`,
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -62,9 +60,9 @@ class Request {
|
|||||||
(config: InternalRequestConfig) => {
|
(config: InternalRequestConfig) => {
|
||||||
// 携带token site-id
|
// 携带token site-id
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
config.headers[envConfig.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
config.headers[import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY] = getToken()
|
||||||
}
|
}
|
||||||
config.headers[envConfig.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
config.headers[import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY] = storage.get('siteId') || 0
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
(err: any) => {
|
(err: any) => {
|
||||||
|
|||||||
@ -1,152 +0,0 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
|
||||||
const { isSharedExternal, isAdminLangExternal, adminLangExternalPath, isCoreExternal, coreExternalPath } = require('./scripts/shared-external.cjs')
|
|
||||||
const { stripElementPlusStylePlugin } = require('./scripts/vite-plugin-strip-element-plus-style.cjs')
|
|
||||||
|
|
||||||
const addonKey = process.env.ADDON_KEY || ''
|
|
||||||
if (!addonKey) {
|
|
||||||
throw new Error('ADDON_KEY is required for addon build')
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = fileURLToPath(new URL(`./.build/addons/${addonKey}/entry.ts`, import.meta.url))
|
|
||||||
|
|
||||||
const elementPlusResolver = ElementPlusResolver({ importStyle: false })
|
|
||||||
|
|
||||||
/** 单插件生产构建 */
|
|
||||||
export default defineConfig({
|
|
||||||
base: '',
|
|
||||||
publicDir: false,
|
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
||||||
__VUE_OPTIONS_API__: true,
|
|
||||||
__VUE_PROD_DEVTOOLS__: false,
|
|
||||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
|
|
||||||
// 显式注入 VITE_* 环境变量:lib 构建模式下 import.meta.env 替换可能不完整
|
|
||||||
'import.meta.env.VITE_APP_BASE_URL': JSON.stringify('/adminapi/'),
|
|
||||||
'import.meta.env.VITE_IMG_DOMAIN': JSON.stringify(''),
|
|
||||||
'import.meta.env.VITE_REQUEST_HEADER_TOKEN_KEY': JSON.stringify('token'),
|
|
||||||
'import.meta.env.VITE_REQUEST_HEADER_SITEID_KEY': JSON.stringify('site-id'),
|
|
||||||
'import.meta.env.VITE_DETAULT_TITLE': JSON.stringify('')
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
// 修复:addon 中 import X from '@/utils/storage' / '@/utils/config' 的 default import
|
|
||||||
// 会被错误地 external 到 core-shared.js 的 default 导出(即 request 实例)。
|
|
||||||
// core-shared 的 export * 不转发 default,因此需要改写为命名导入。
|
|
||||||
{
|
|
||||||
name: 'fix-addon-storage-config-default-import',
|
|
||||||
enforce: 'pre',
|
|
||||||
transform(code, id) {
|
|
||||||
// 只在 addon 构建中使用(vite.config.addon.ts 专用),无需 id 过滤
|
|
||||||
// import xxx from '@/utils/storage' → import { storage as xxx } from '@/utils/storage'
|
|
||||||
const orig = code
|
|
||||||
code = code.replace(
|
|
||||||
/import\s+(\w+)\s+from\s+['"]@\/utils\/storage['"]/g,
|
|
||||||
"import { storage as $1 } from '@/utils/storage'"
|
|
||||||
)
|
|
||||||
// import xxx from '@/utils/config' → import { config as xxx } from '@/utils/config'
|
|
||||||
code = code.replace(
|
|
||||||
/import\s+(\w+)\s+from\s+['"]@\/utils\/config['"]/g,
|
|
||||||
"import { config as $1 } from '@/utils/config'"
|
|
||||||
)
|
|
||||||
// import xxx from '@/utils/test' → import { test as xxx } from '@/utils/test'
|
|
||||||
code = code.replace(
|
|
||||||
/import\s+(\w+)\s+from\s+['"]@\/utils\/test['"]/g,
|
|
||||||
"import { test as $1 } from '@/utils/test'"
|
|
||||||
)
|
|
||||||
// 处理导出同名冲突:import { X } from '@/app/api/mod' / '@/utils/mod'
|
|
||||||
// 优先 export-conflicts.json,兼容旧版 api-conflicts.json
|
|
||||||
let conflicts = {}
|
|
||||||
const sources = [
|
|
||||||
fileURLToPath(new URL('./.build/export-conflicts.json', import.meta.url)),
|
|
||||||
fileURLToPath(new URL('./.build/api-conflicts.json', import.meta.url))
|
|
||||||
]
|
|
||||||
for (const p of sources) { try { conflicts = { ...conflicts, ...require(p) } } catch (_) {} }
|
|
||||||
if (Object.keys(conflicts).length) {
|
|
||||||
code = code.replace(
|
|
||||||
/import\s+\{([^}]+)\}\s+from\s+['"]@\/(?:app\/api|utils)\/(\w+)['"]/g,
|
|
||||||
(match: string, names: string, mod: string) => {
|
|
||||||
const remapped = names.split(',').map(n => {
|
|
||||||
const trimmed = n.trim()
|
|
||||||
const [name, alias] = trimmed.includes(' as ') ? trimmed.split(' as ').map(s => s.trim()) : [trimmed, trimmed]
|
|
||||||
const key = mod + ':' + name
|
|
||||||
if (conflicts[key]) return conflicts[key] + ' as ' + alias
|
|
||||||
return trimmed
|
|
||||||
})
|
|
||||||
const ns = match.includes('@/app/api') ? '@/app/api' : '@/utils'
|
|
||||||
return 'import { ' + remapped.join(', ') + ' } from \'' + ns + '/' + mod + '\''
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 处理 export { default as ... } from '@/utils/storage|config|test'
|
|
||||||
code = code.replace(
|
|
||||||
/export\s+\{\s*default\s+as\s+(\w+)\s*\}\s+from\s+['"]@\/utils\/(storage|config|test)['"]/g,
|
|
||||||
"export { $2 as $1 } from '@/utils/$2'"
|
|
||||||
)
|
|
||||||
if (code !== orig) {
|
|
||||||
return { code, map: null }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stripElementPlusStylePlugin(),
|
|
||||||
vue(),
|
|
||||||
AutoImport({ resolvers: [elementPlusResolver] }),
|
|
||||||
Components({ resolvers: [elementPlusResolver] }),
|
|
||||||
// 注入 CSS:lib 模式 CSS 统一提取到 style.css,运行时注入为 <style> 标签
|
|
||||||
{
|
|
||||||
name: 'addon-css-inject',
|
|
||||||
enforce: 'post',
|
|
||||||
generateBundle(_opts, bundle) {
|
|
||||||
const entry = bundle['index.js']
|
|
||||||
const cssAsset = bundle['style.css']
|
|
||||||
if (entry && entry.type === 'chunk' && cssAsset && cssAsset.type === 'asset') {
|
|
||||||
// 过滤掉第三方库中污染全局的选择器(如 Vditor 的 body, html 样式)
|
|
||||||
let rawCss = (cssAsset.source as string).toString()
|
|
||||||
// 移除以 body 或 html 开头且后面紧跟 { 或空格的选择器块(如 body{...})
|
|
||||||
// 但不影响 .xxx__body, body.class, #body 等复合选择器
|
|
||||||
rawCss = rawCss.replace(/(?:^|})\s*(?:body|html)\s*(?=\{)/g, (m) => m.startsWith('}') ? '}/* removed-global-' + m.substring(1).trim() + ' */' : '/* removed-global-' + m.trim() + ' */')
|
|
||||||
// 移除逗号分隔的全局选择器中的 body/html(如 :root,body{...})
|
|
||||||
rawCss = rawCss.replace(/,\s*(?:body|html)\s*(?=\{)/g, '/* removed-global */')
|
|
||||||
const css = JSON.stringify(rawCss)
|
|
||||||
entry.code = `(function(){var s=document.createElement('style');s.textContent=${css};s.setAttribute('data-addon-style','${addonKey}');document.head.insertBefore(s,document.head.firstChild)})();\n${entry.code}`
|
|
||||||
delete bundle['style.css']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
||||||
assets: fileURLToPath(new URL('./src/assets', import.meta.url)),
|
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
cssCodeSplit: false,
|
|
||||||
outDir: `dist/.addons/${addonKey}`,
|
|
||||||
emptyOutDir: true,
|
|
||||||
lib: {
|
|
||||||
entry,
|
|
||||||
formats: ['es'],
|
|
||||||
fileName: () => 'index.js'
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: (id) => isAdminLangExternal(id) || isSharedExternal(id) || isCoreExternal(id),
|
|
||||||
output: {
|
|
||||||
inlineDynamicImports: false,
|
|
||||||
chunkFileNames: '[name]-[hash].js',
|
|
||||||
entryFileNames: 'index.js',
|
|
||||||
paths(id) {
|
|
||||||
if (isAdminLangExternal(id)) return adminLangExternalPath()
|
|
||||||
if (isCoreExternal(id)) return coreExternalPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
|
||||||
const { isSharedExternal, isAdminLangExternal, adminLangExternalPath } = require('./scripts/shared-external.cjs')
|
|
||||||
const { stripElementPlusStylePlugin } = require('./scripts/vite-plugin-strip-element-plus-style.cjs')
|
|
||||||
|
|
||||||
const rootDir = fileURLToPath(new URL('.', import.meta.url))
|
|
||||||
|
|
||||||
const elementPlusResolver = ElementPlusResolver({ importStyle: false })
|
|
||||||
|
|
||||||
/** Core 生产构建:不包含 src/addon 源码(运行时通过 manifest 加载) */
|
|
||||||
export default defineConfig({
|
|
||||||
base: '',
|
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
||||||
__VUE_OPTIONS_API__: true,
|
|
||||||
__VUE_PROD_DEVTOOLS__: false,
|
|
||||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
stripElementPlusStylePlugin(),
|
|
||||||
vue(),
|
|
||||||
AutoImport({ resolvers: [elementPlusResolver] }),
|
|
||||||
Components({ resolvers: [elementPlusResolver] }),
|
|
||||||
{
|
|
||||||
name: 'exclude-addon-source',
|
|
||||||
enforce: 'pre',
|
|
||||||
resolveId(source, importer) {
|
|
||||||
if (source.includes('*')) return null
|
|
||||||
const norm = source.replace(/\\/g, '/')
|
|
||||||
if (importer && importer.includes('/src/addon/')) return null
|
|
||||||
if (norm.startsWith('@/addon/') || norm.includes('/src/addon/')) {
|
|
||||||
// 虚拟 id 勿以 .json 结尾,否则 vite:json 会尝试解析
|
|
||||||
return '\0addon-external:' + norm.replace(/\.json$/i, '.langdata')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (id.startsWith('\0addon-external:')) {
|
|
||||||
return 'export default {}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
||||||
assets: fileURLToPath(new URL('./src/assets', import.meta.url)),
|
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
outDir: 'dist/.core',
|
|
||||||
emptyOutDir: true,
|
|
||||||
manifest: true,
|
|
||||||
rollupOptions: {
|
|
||||||
external: (id) => {
|
|
||||||
const norm = id.replace(/\\/g, '/')
|
|
||||||
if (norm.includes('/style/css') || norm.includes('/style/index')) return false
|
|
||||||
if (norm.includes('element-plus/dist/')) return false
|
|
||||||
if (norm.includes('element-plus/theme-chalk')) return false
|
|
||||||
if (isAdminLangExternal(id)) return true
|
|
||||||
return isSharedExternal(id)
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
paths(id) {
|
|
||||||
if (isAdminLangExternal(id)) return adminLangExternalPath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url)
|
|
||||||
const { sharedExternalForBuild } = require('./scripts/shared-external.cjs')
|
|
||||||
|
|
||||||
const pkgKey = process.env.SHARED_PKG || 'vue'
|
|
||||||
const emptyOutDir = process.env.SHARED_EMPTY === '1'
|
|
||||||
|
|
||||||
const entry = fileURLToPath(new URL(`./.build/shared/${pkgKey}.ts`, import.meta.url))
|
|
||||||
const outName = pkgKey === 'icons-vue' ? 'icons-vue' : pkgKey
|
|
||||||
|
|
||||||
const needsCore = pkgKey === 'admin-lang' || pkgKey === 'core-shared'
|
|
||||||
|
|
||||||
/** 单包 shared ESM 构建,peer 依赖保持 external 由 import map 解析 */
|
|
||||||
export default defineConfig({
|
|
||||||
publicDir: false,
|
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
||||||
__VUE_OPTIONS_API__: true,
|
|
||||||
__VUE_PROD_DEVTOOLS__: false,
|
|
||||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false
|
|
||||||
},
|
|
||||||
resolve: needsCore
|
|
||||||
? {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
plugins: needsCore
|
|
||||||
? [
|
|
||||||
vue(),
|
|
||||||
{
|
|
||||||
name: 'shared-exclude-addon',
|
|
||||||
enforce: 'pre',
|
|
||||||
resolveId(source) {
|
|
||||||
if (source.includes('*')) return null
|
|
||||||
const norm = source.replace(/\\?vue.*$/, '').replace(/\\/g, '/')
|
|
||||||
if (norm.startsWith('@/addon/') || norm.includes('/src/addon/')) {
|
|
||||||
return { id: 'virtual:addon-empty', moduleSideEffects: false }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
load(id) {
|
|
||||||
if (id === 'virtual:addon-empty') return 'export default {}'
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
build: {
|
|
||||||
outDir: 'dist/.shared',
|
|
||||||
emptyOutDir,
|
|
||||||
lib: {
|
|
||||||
entry,
|
|
||||||
formats: ['es'],
|
|
||||||
fileName: () => `${outName}.js`
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: sharedExternalForBuild(pkgKey),
|
|
||||||
// core-shared 是共享包,addon 依赖其全部导出,禁止 tree-shaking
|
|
||||||
treeshake: pkgKey !== 'core-shared'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
插件源码
|
||||||
@ -123,13 +123,10 @@ class Generator extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function del(int $id)
|
public function del(int $id)
|
||||||
{
|
{
|
||||||
$removeCode = $this->request->param('remove_code', 0);
|
(new GenerateService())->del($id);
|
||||||
(new GenerateService())->del($id, (bool)$removeCode);
|
|
||||||
return success('DELETE_SUCCESS');
|
return success('DELETE_SUCCESS');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成代码
|
* 生成代码
|
||||||
* @description 生成代码
|
* @description 生成代码
|
||||||
|
|||||||
@ -59,16 +59,4 @@ class Area extends BaseApiController
|
|||||||
]);
|
]);
|
||||||
return success((new AreaService())->getAddressByLatlng($data));
|
return success((new AreaService())->getAddressByLatlng($data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过地址查询经纬度(地理编码)
|
|
||||||
* @return Response
|
|
||||||
*/
|
|
||||||
public function getLatlngByAddress()
|
|
||||||
{
|
|
||||||
$data = $this->request->params([
|
|
||||||
['address', '']
|
|
||||||
]);
|
|
||||||
return success((new AreaService())->getLatlngByAddress($data));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,7 +128,6 @@ Route::group(function () {
|
|||||||
|
|
||||||
// 通过经纬度查询地址
|
// 通过经纬度查询地址
|
||||||
Route::get('area/address_by_latlng', 'sys.Area/getAddressByLatlng');
|
Route::get('area/address_by_latlng', 'sys.Area/getAddressByLatlng');
|
||||||
Route::get('area/latlng_by_address', 'sys.Area/getLatlngByAddress');
|
|
||||||
|
|
||||||
/***************************************************** 海报管理 ****************************************************/
|
/***************************************************** 海报管理 ****************************************************/
|
||||||
//获取海报
|
//获取海报
|
||||||
|
|||||||
@ -182,7 +182,7 @@ class Workerman extends Command
|
|||||||
$addon_path = $addon_dir . $v[ 'key' ] . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'job';
|
$addon_path = $addon_dir . $v[ 'key' ] . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'job';
|
||||||
if (is_dir($addon_path)) {
|
if (is_dir($addon_path)) {
|
||||||
search_dir($addon_path, $addon_data, root_path());
|
search_dir($addon_path, $addon_data, root_path());
|
||||||
$class_list = array_merge($class_list, array_filter($addon_data ?? []));
|
$class_list = array_merge($class_list, array_filter($addon_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1020,6 +1020,20 @@ return [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'menu_name' => '云编译',
|
||||||
|
'menu_key' => 'cloud_compile',
|
||||||
|
'menu_short_name' => '云编译',
|
||||||
|
'menu_type' => '1',
|
||||||
|
'icon' => 'iconfont iconyunbianyi',
|
||||||
|
'api_url' => '',
|
||||||
|
'router_path' => 'tools/cloud_compile',
|
||||||
|
'view_path' => 'tools/cloud_compile',
|
||||||
|
'methods' => 'post',
|
||||||
|
'sort' => '0',
|
||||||
|
'status' => '1',
|
||||||
|
'is_show' => '1',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'menu_name' => '应用开发',
|
'menu_name' => '应用开发',
|
||||||
'menu_key' => 'tool',
|
'menu_key' => 'tool',
|
||||||
@ -1237,7 +1251,7 @@ return [
|
|||||||
'router_path' => 'tools/update_cache',
|
'router_path' => 'tools/update_cache',
|
||||||
'view_path' => 'tools/updatecache',
|
'view_path' => 'tools/updatecache',
|
||||||
'methods' => 'post',
|
'methods' => 'post',
|
||||||
'sort' => '97',
|
'sort' => '108',
|
||||||
'status' => '1',
|
'status' => '1',
|
||||||
'is_show' => '1',
|
'is_show' => '1',
|
||||||
],
|
],
|
||||||
@ -1645,20 +1659,6 @@ return [
|
|||||||
],
|
],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
[
|
|
||||||
'menu_name' => '云编译',
|
|
||||||
'menu_key' => 'cloud_compile',
|
|
||||||
'menu_short_name' => '云编译',
|
|
||||||
'menu_type' => '1',
|
|
||||||
'icon' => 'iconfont iconyunbianyi',
|
|
||||||
'api_url' => '',
|
|
||||||
'router_path' => 'tools/cloud_compile',
|
|
||||||
'view_path' => 'tools/cloud_compile',
|
|
||||||
'methods' => 'post',
|
|
||||||
'sort' => '98',
|
|
||||||
'status' => '1',
|
|
||||||
'is_show' => '1',
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
'menu_name' => '授权信息',
|
'menu_name' => '授权信息',
|
||||||
'menu_key' => 'app_auth',
|
'menu_key' => 'app_auth',
|
||||||
@ -1669,7 +1669,7 @@ return [
|
|||||||
'router_path' => 'tools/authorize',
|
'router_path' => 'tools/authorize',
|
||||||
'view_path' => 'app/authorize',
|
'view_path' => 'app/authorize',
|
||||||
'methods' => '',
|
'methods' => '',
|
||||||
'sort' => '97',
|
'sort' => '98',
|
||||||
'status' => '1',
|
'status' => '1',
|
||||||
'is_show' => '1',
|
'is_show' => '1',
|
||||||
'children' => [
|
'children' => [
|
||||||
|
|||||||
@ -141,7 +141,6 @@ class NoticeTypeDict
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PARAMS_TYPE_VALID_CODE = 'valid_code';
|
const PARAMS_TYPE_VALID_CODE = 'valid_code';
|
||||||
const PARAMS_TYPE_MOBILE_NUMBER = 'mobile_number';
|
|
||||||
const PARAMS_TYPE_OTHER_NUMBER = 'other_number';
|
const PARAMS_TYPE_OTHER_NUMBER = 'other_number';
|
||||||
const PARAMS_TYPE_AMOUNT = 'amount';
|
const PARAMS_TYPE_AMOUNT = 'amount';
|
||||||
const PARAMS_TYPE_DATE = 'date';
|
const PARAMS_TYPE_DATE = 'date';
|
||||||
@ -154,24 +153,16 @@ class NoticeTypeDict
|
|||||||
[
|
[
|
||||||
'name' => '验证码',
|
'name' => '验证码',
|
||||||
'type' => self::PARAMS_TYPE_VALID_CODE,
|
'type' => self::PARAMS_TYPE_VALID_CODE,
|
||||||
'desc' => '4-6位纯数字/纯英文,或数字英文组合,英文支持大小写',
|
'desc' => '4-6位纯数字',
|
||||||
'rule' => '/^[a-zA-Z0-9]+$/',
|
'rule' => '/^\d$/',
|
||||||
'min'=>4,
|
'min'=>4,
|
||||||
'max'=>6
|
'max'=>6
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '电话号码',
|
'name' => '其他号码(手机号)',
|
||||||
'type' => self::PARAMS_TYPE_MOBILE_NUMBER,
|
|
||||||
'desc' => '1-15位纯数字',
|
|
||||||
'rule'=>'/^\d+$/',
|
|
||||||
'min'=>1,
|
|
||||||
'max'=>15
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'name' => '其他号码',
|
|
||||||
'type' => self::PARAMS_TYPE_OTHER_NUMBER,
|
'type' => self::PARAMS_TYPE_OTHER_NUMBER,
|
||||||
'desc' => '1-20位英文+数字组合,支持中划线-',
|
'desc' => '1-20位字母+数字组合',
|
||||||
'rule'=>'/^[a-zA-Z0-9-]+$/',
|
'rule'=>'/^[a-zA-Z0-9]$/',
|
||||||
'min'=>1,
|
'min'=>1,
|
||||||
'max'=>20
|
'max'=>20
|
||||||
],
|
],
|
||||||
@ -179,20 +170,18 @@ class NoticeTypeDict
|
|||||||
'name' => '金额',
|
'name' => '金额',
|
||||||
'type' => self::PARAMS_TYPE_AMOUNT,
|
'type' => self::PARAMS_TYPE_AMOUNT,
|
||||||
'desc' => '支持数字或数字的中文 (壹贰叁肆伍陆柒捌玖拾佰仟万亿 圆元整角分厘毫)',
|
'desc' => '支持数字或数字的中文 (壹贰叁肆伍陆柒捌玖拾佰仟万亿 圆元整角分厘毫)',
|
||||||
'rule' => "/^(?:\d+(?:\.\d+)?|[零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆角分厘毫圆元整]+)$/u"
|
'rule' => "/^(?:\d+|(?:[零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆角分厘毫]+|圆|元|整)+)$/u"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '日期',
|
'name' => '日期',
|
||||||
'type' => self::PARAMS_TYPE_DATE,
|
'type' => self::PARAMS_TYPE_DATE,
|
||||||
'desc' => '符合时间的表达方式 也支持中文:2019年9月3日16时24分35秒,最多20位',
|
'desc' => '符合时间的表达方式 也支持中文:2019年9月3日16时24分35秒',
|
||||||
'rule' => '',
|
'rule' => ''
|
||||||
'min'=>1,
|
|
||||||
'max'=>20
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '中文',
|
'name' => '中文',
|
||||||
'type' => self::PARAMS_TYPE_CHINESE,
|
'type' => self::PARAMS_TYPE_CHINESE,
|
||||||
'desc' => '1-15个中文,支持中文圆括号()',
|
'desc' => '1-15中文,支持中文圆括号()',
|
||||||
'rule' => '/^[\p{Han}()]+$/u',
|
'rule' => '/^[\p{Han}()]+$/u',
|
||||||
'min'=>1,
|
'min'=>1,
|
||||||
'max'=>15
|
'max'=>15
|
||||||
@ -201,17 +190,17 @@ class NoticeTypeDict
|
|||||||
'name' => '纯数字',
|
'name' => '纯数字',
|
||||||
'type' => self::PARAMS_TYPE_INT_NUMBER,
|
'type' => self::PARAMS_TYPE_INT_NUMBER,
|
||||||
'desc' => ' 1-10个数字',
|
'desc' => ' 1-10个数字',
|
||||||
'rule' => '/^\d+$/',
|
'rule' => '/^\d$/',
|
||||||
'min'=>1,
|
'min'=>1,
|
||||||
'max'=>10
|
'max'=>10
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '其他',
|
'name' => '其他',
|
||||||
'type' => self::PARAMS_TYPE_OTHERS,
|
'type' => self::PARAMS_TYPE_OTHERS,
|
||||||
'desc' => '1-20个中文英文数字组合,支持中文符号和空格',
|
'desc' => ' 1-30个中文数字字母组合,支持中文符号。',
|
||||||
'rule' => '/^[\p{Han}\p{N}\p{L}\p{P}\p{S}\s]+$/u',
|
'rule' => '/^[\p{Han}\p{N}\p{L}\p{P}\p{S}\s]$/u',
|
||||||
'min'=>1,
|
'min'=>1,
|
||||||
'max'=>20
|
'max'=>30
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1281,7 +1281,7 @@ CREATE TABLE `weapp_version`
|
|||||||
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态',
|
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态',
|
||||||
`update_time` int(11) NOT NULL DEFAULT 0,
|
`update_time` int(11) NOT NULL DEFAULT 0,
|
||||||
`fail_reason` text DEFAULT NULL,
|
`fail_reason` text DEFAULT NULL,
|
||||||
`task_key` varchar(255) NOT NULL DEFAULT '' COMMENT '上传任务key',
|
`task_key` varchar(20) NOT NULL DEFAULT '' COMMENT '上传任务key',
|
||||||
`from_type` VARCHAR(255) NOT NULL DEFAULT 'cloud_build',
|
`from_type` VARCHAR(255) NOT NULL DEFAULT 'cloud_build',
|
||||||
`auditid` VARCHAR(255) NOT NULL DEFAULT '',
|
`auditid` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE
|
||||||
|
|||||||
@ -120,7 +120,7 @@ class AuthService extends BaseAdminService
|
|||||||
if (!$this->isCheckDomain()) return;
|
if (!$this->isCheckDomain()) return;
|
||||||
|
|
||||||
$site_address = $authinfo['site_address'] ?? '';
|
$site_address = $authinfo['site_address'] ?? '';
|
||||||
$domain = str_replace(['http://', 'https://'], '', request()->domain());
|
$domain = request()->domain();
|
||||||
|
|
||||||
// 如果是站点域名不进行验证
|
// 如果是站点域名不进行验证
|
||||||
$site_id = (new CoreSiteService())->getSiteIdByDomain($domain);
|
$site_id = (new CoreSiteService())->getSiteIdByDomain($domain);
|
||||||
|
|||||||
@ -59,9 +59,6 @@ class GenerateService extends BaseAdminService
|
|||||||
|
|
||||||
$info = (new GenerateTable())->field($field)->where([['id', '=', $id]])->findOrEmpty()->toArray();
|
$info = (new GenerateTable())->field($field)->where([['id', '=', $id]])->findOrEmpty()->toArray();
|
||||||
|
|
||||||
// 自动同步数据库表结构:合并实际字段与已存配置
|
|
||||||
$this->syncTableColumns($id);
|
|
||||||
|
|
||||||
$info['table_column'] = (new GenerateColumn())->where([['table_id', '=', $id]])->select()->toArray();
|
$info['table_column'] = (new GenerateColumn())->where([['table_id', '=', $id]])->select()->toArray();
|
||||||
|
|
||||||
$column = (new GenerateColumn())->where([['table_id', '=', $id],['is_delete','=',1]])->find();
|
$column = (new GenerateColumn())->where([['table_id', '=', $id],['is_delete','=',1]])->find();
|
||||||
@ -367,19 +364,10 @@ class GenerateService extends BaseAdminService
|
|||||||
/**
|
/**
|
||||||
* 删除代码生成
|
* 删除代码生成
|
||||||
* @param int $id
|
* @param int $id
|
||||||
* @param bool $removeCode 是否同时删除已同步到项目中的生成代码文件
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function del(int $id, bool $removeCode = false)
|
public function del(int $id)
|
||||||
{
|
{
|
||||||
if ($removeCode) {
|
|
||||||
// 删除前先获取表信息,以确定需要清理的文件路径
|
|
||||||
$table_info = (new GenerateTable())->where([['id', '=', $id]])->field('*')->findOrEmpty();
|
|
||||||
if (!$table_info->isEmpty()) {
|
|
||||||
$this->delSyncCode($id, $table_info->toArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
(new GenerateTable())->where([['id', '=', $id]])->delete();
|
(new GenerateTable())->where([['id', '=', $id]])->delete();
|
||||||
@ -392,97 +380,6 @@ class GenerateService extends BaseAdminService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除已同步到项目中的代码文件
|
|
||||||
* @param int $id
|
|
||||||
* @param array $tableInfo
|
|
||||||
*/
|
|
||||||
private function delSyncCode(int $id, array $tableInfo)
|
|
||||||
{
|
|
||||||
$tableInfo['fields'] = (new GenerateColumn())->where([['table_id', '=', $id]])->field('*')->select()->toArray();
|
|
||||||
$generator = new Generate();
|
|
||||||
// 传入 generate_type=4 表示删除模式,生成器根据此值决定删除文件而非创建
|
|
||||||
$tableInfo['generate_type'] = 4;
|
|
||||||
$generator->generate($tableInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动同步数据库表结构到列配置(在 info 接口中调用)
|
|
||||||
* 新增数据库字段 / 删除已不存在的字段 / 更新类型注释
|
|
||||||
* @param int $id
|
|
||||||
*/
|
|
||||||
private function syncTableColumns(int $id)
|
|
||||||
{
|
|
||||||
$table_info = (new GenerateTable())->where([['id', '=', $id]])->field('table_name')->findOrEmpty();
|
|
||||||
if ($table_info->isEmpty()) return;
|
|
||||||
|
|
||||||
$table_name = $table_info['table_name'];
|
|
||||||
try {
|
|
||||||
$fields = Db::name($table_name)->getFields();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return; // 表不存在则跳过
|
|
||||||
}
|
|
||||||
if (empty($fields)) return;
|
|
||||||
|
|
||||||
$existing_columns = (new GenerateColumn())->where([['table_id', '=', $id]])->column('*', 'column_name');
|
|
||||||
$default_column = ['id', 'create_time', 'update_time', 'site_id', 'delete_time'];
|
|
||||||
|
|
||||||
foreach ($fields as $k => $v) {
|
|
||||||
$required = 0;
|
|
||||||
if ($v['notnull'] && !$v['primary'] && !in_array($v['name'], $default_column)) {
|
|
||||||
$required = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$column_data = [
|
|
||||||
'table_id' => $id,
|
|
||||||
'column_name' => $v['name'],
|
|
||||||
'column_comment' => $v['comment'],
|
|
||||||
'column_type' => self::getDbFieldType($v['type']),
|
|
||||||
'is_required' => $required,
|
|
||||||
'is_pk' => $v['primary'] ? 1 : 0,
|
|
||||||
'is_insert' => !in_array($v['name'], $default_column) ? 1 : 0,
|
|
||||||
'is_update' => !in_array($v['name'], $default_column) ? 1 : 0,
|
|
||||||
'is_lists' => !in_array($v['name'], $default_column) ? 1 : 0,
|
|
||||||
'update_time' => time(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isset($existing_columns[$v['name']])) {
|
|
||||||
// 已有字段:保留用户前端配置,仅更新基础信息
|
|
||||||
$old = $existing_columns[$v['name']];
|
|
||||||
$column_data['view_type'] = $old['view_type'] ?? 'input';
|
|
||||||
$column_data['query_type'] = $old['query_type'] ?? '=';
|
|
||||||
$column_data['dict_type'] = $old['dict_type'] ?? '';
|
|
||||||
$column_data['model'] = $old['model'] ?? '';
|
|
||||||
$column_data['label_key'] = $old['label_key'] ?? '';
|
|
||||||
$column_data['value_key'] = $old['value_key'] ?? '';
|
|
||||||
$column_data['is_search'] = $old['is_search'] ?? 0;
|
|
||||||
$column_data['is_order'] = $old['is_order'] ?? 0;
|
|
||||||
$column_data['is_delete'] = $old['is_delete'] ?? 0;
|
|
||||||
$column_data['create_time'] = $old['create_time'];
|
|
||||||
unset($existing_columns[$v['name']]);
|
|
||||||
|
|
||||||
(new GenerateColumn())->where([
|
|
||||||
['table_id', '=', $id],
|
|
||||||
['column_name', '=', $v['name']]
|
|
||||||
])->save($column_data);
|
|
||||||
} else {
|
|
||||||
// 新字段
|
|
||||||
$column_data['view_type'] = 'input';
|
|
||||||
$column_data['query_type'] = '=';
|
|
||||||
$column_data['create_time'] = time();
|
|
||||||
(new GenerateColumn())->create($column_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除数据库中已不存在的字段
|
|
||||||
$removed = array_keys($existing_columns);
|
|
||||||
if (!empty($removed)) {
|
|
||||||
(new GenerateColumn())->where([['table_id', '=', $id]])
|
|
||||||
->whereIn('column_name', $removed)
|
|
||||||
->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成代码
|
* 生成代码
|
||||||
* @param array $params
|
* @param array $params
|
||||||
|
|||||||
@ -26,8 +26,6 @@ class AdminApiRouteGenerator extends BaseGenerator
|
|||||||
*/
|
*/
|
||||||
public function replaceText()
|
public function replaceText()
|
||||||
{
|
{
|
||||||
$routeContent = $this->getRoute();
|
|
||||||
|
|
||||||
$old = [
|
$old = [
|
||||||
'{ROUTE_GROUP_NAME}',
|
'{ROUTE_GROUP_NAME}',
|
||||||
'{CLASS_COMMENT}',
|
'{CLASS_COMMENT}',
|
||||||
@ -53,8 +51,8 @@ class AdminApiRouteGenerator extends BaseGenerator
|
|||||||
$this->moduleName,
|
$this->moduleName,
|
||||||
$this->getRouteName(),
|
$this->getRouteName(),
|
||||||
$this->getRoutePath(),
|
$this->getRoutePath(),
|
||||||
$this->getWithRoute($routeContent),
|
$this->getWithRoute(),
|
||||||
$routeContent,
|
$this->getRoute(),
|
||||||
$this->getBegin(),
|
$this->getBegin(),
|
||||||
$this->getEnd(),
|
$this->getEnd(),
|
||||||
];
|
];
|
||||||
@ -95,13 +93,12 @@ class AdminApiRouteGenerator extends BaseGenerator
|
|||||||
if(file_exists($file))
|
if(file_exists($file))
|
||||||
{
|
{
|
||||||
$content = file_get_contents($file);
|
$content = file_get_contents($file);
|
||||||
$code_begin = 'USER_CODE_BEGIN -- '.$this->getTableName();
|
$code_begin = 'USER_CODE_BEGIN -- '.$this->getTableName() . PHP_EOL;
|
||||||
$code_end = 'USER_CODE_END -- '.$this->getTableName();
|
$code_end = 'USER_CODE_END -- '.$this->getTableName() . PHP_EOL;
|
||||||
// 使用 \r?\n 替代 PHP_EOL,兼容不同系统的换行符
|
if(strpos($content,$code_begin) !== false && strpos($content,$code_end) !== false)
|
||||||
$pattern = '/\/\/\s+' . preg_quote($code_begin, '/') . '\r?\n[\S\s]+?\/\/\s+' . preg_quote($code_end, '/') . '\r?\n?/';
|
|
||||||
if(preg_match($pattern, $content))
|
|
||||||
{
|
{
|
||||||
// 非贪婪匹配 [?] 精确清除当前代码块,避免跨块误删
|
// 清除相应对应代码块
|
||||||
|
$pattern = "/\/\/\s+{$code_begin}[\S\s]+{$code_end}?/";
|
||||||
$route = preg_replace($pattern, '', $content);
|
$route = preg_replace($pattern, '', $content);
|
||||||
}else{
|
}else{
|
||||||
$route = $content;
|
$route = $content;
|
||||||
@ -305,7 +302,7 @@ use app\adminapi\middleware\AdminLog;";
|
|||||||
* 远程下拉Route
|
* 远程下拉Route
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getWithRoute(string $existingContent = '')
|
public function getWithRoute()
|
||||||
{
|
{
|
||||||
if(!empty($this->addonName))
|
if(!empty($this->addonName))
|
||||||
{
|
{
|
||||||
@ -315,19 +312,10 @@ use app\adminapi\middleware\AdminLog;";
|
|||||||
}
|
}
|
||||||
|
|
||||||
$content = '';
|
$content = '';
|
||||||
$exists = [];
|
|
||||||
foreach ($this->tableColumn as $column) {
|
foreach ($this->tableColumn as $column) {
|
||||||
if (!empty($column['model'])) {
|
if (!empty($column['model'])) {
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::snake(substr($column['model'],$str+1)) . '_all';
|
||||||
if (in_array($modelName, $exists)) continue; // 单表内去重
|
|
||||||
$exists[] = $modelName;
|
|
||||||
$with = Str::snake($modelName) . '_all';
|
|
||||||
// 跨表去重:检查已有路由文件中是否已定义该路由
|
|
||||||
$routeLine = "Route::get('".$with."'";
|
|
||||||
if (!empty($existingContent) && strpos($existingContent, $routeLine) !== false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$content.= PHP_EOL.' Route::get('."'".$with."'".','."'".$route_path.'get'.Str::studly($with)."'".');'.PHP_EOL;
|
$content.= PHP_EOL.' Route::get('."'".$with."'".','."'".$route_path.'get'.Str::studly($with)."'".');'.PHP_EOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,7 +156,6 @@ abstract class BaseGenerator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成文件到模块或runtime目录
|
* 生成文件到模块或runtime目录
|
||||||
* generate_type: 2=下载ZIP, 3=同步到项目, 4=删除已同步的文件
|
|
||||||
*/
|
*/
|
||||||
public function generate()
|
public function generate()
|
||||||
{
|
{
|
||||||
@ -170,63 +169,18 @@ abstract class BaseGenerator
|
|||||||
$paths[] = $this->getObjectOutDir() . $this->getFileName();
|
$paths[] = $this->getObjectOutDir() . $this->getFileName();
|
||||||
// 生成到插件中
|
// 生成到插件中
|
||||||
if ($this->addonName && method_exists($this, 'getAddonObjectOutDir')) $paths[] = $this->getAddonObjectOutDir() . $this->getFileName();
|
if ($this->addonName && method_exists($this, 'getAddonObjectOutDir')) $paths[] = $this->getAddonObjectOutDir() . $this->getFileName();
|
||||||
}else if($this->table['generate_type'] == 4){
|
|
||||||
// 删除模式下:对已存在且包含 USER_CODE_BEGIN 标记的文件,移除对应代码块后保存
|
|
||||||
// 对只包含生成代码的新建文件(如 .vue / .ts),直接删除
|
|
||||||
$paths[] = $this->getObjectOutDir() . $this->getFileName();
|
|
||||||
if ($this->addonName && method_exists($this, 'getAddonObjectOutDir')) $paths[] = $this->getAddonObjectOutDir() . $this->getFileName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入内容或删除
|
// 写入内容
|
||||||
if(!empty($this->getFileName()))
|
if(!empty($this->getFileName()))
|
||||||
{
|
{
|
||||||
foreach ($paths as $path) {
|
foreach ($paths as $path) {
|
||||||
if($this->table['generate_type'] == 4) {
|
|
||||||
// 删除已同步的代码
|
|
||||||
$this->removeGeneratedFile($path);
|
|
||||||
} else {
|
|
||||||
file_put_contents($path, $this->text);
|
file_put_contents($path, $this->text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除生成器产出的文件。
|
|
||||||
* 如果文件仅包含 USER_CODE_BEGIN/END 代码块且无其他业务逻辑,则直接删除文件;
|
|
||||||
* 否则仅移除 USER_CODE_BEGIN...USER_CODE_END 之间的代码块内容。
|
|
||||||
*/
|
|
||||||
protected function removeGeneratedFile(string $path)
|
|
||||||
{
|
|
||||||
if (!file_exists($path)) return;
|
|
||||||
|
|
||||||
$content = file_get_contents($path);
|
|
||||||
$code_begin = 'USER_CODE_BEGIN -- ';
|
|
||||||
$code_end = 'USER_CODE_END -- ';
|
|
||||||
|
|
||||||
if (strpos($content, $code_begin) === false || strpos($content, $code_end) === false) {
|
|
||||||
// 没有标记 → 可能是独立生成的文件,直接删除
|
|
||||||
@unlink($path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除所有 USER_CODE_BEGIN...USER_CODE_END 代码块
|
|
||||||
$pattern = '/\/\/\s+' . preg_quote($code_begin, '/') . '.*?\r?\n[\S\s]+?\/\/\s+' . preg_quote($code_end, '/') . '.*?\r?\n?/';
|
|
||||||
$content = preg_replace($pattern, '', $content);
|
|
||||||
|
|
||||||
// 去除多余空行
|
|
||||||
$content = preg_replace("/(\r?\n){3,}/", "\n\n", $content);
|
|
||||||
$content = trim($content);
|
|
||||||
|
|
||||||
if ($content === '' || preg_match('/^<\?php\s*$/', $content)) {
|
|
||||||
// 文件内容为空或只剩PHP开头标记 → 直接删除
|
|
||||||
@unlink($path);
|
|
||||||
} else {
|
|
||||||
file_put_contents($path, $content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件信息
|
* 文件信息
|
||||||
|
|||||||
@ -30,7 +30,6 @@ class WebApiGenerator extends BaseGenerator
|
|||||||
*/
|
*/
|
||||||
public function replaceText()
|
public function replaceText()
|
||||||
{
|
{
|
||||||
$existingContent = $this->getImport();
|
|
||||||
|
|
||||||
$old = [
|
$old = [
|
||||||
'{NOTES}',
|
'{NOTES}',
|
||||||
@ -56,9 +55,9 @@ class WebApiGenerator extends BaseGenerator
|
|||||||
$this->getUCaseClassName(),
|
$this->getUCaseClassName(),
|
||||||
$this->moduleName,
|
$this->moduleName,
|
||||||
$this->getRouteGroupName(),
|
$this->getRouteGroupName(),
|
||||||
$existingContent,
|
$this->getImport(),
|
||||||
$this->getBegin(),
|
$this->getBegin(),
|
||||||
$this->getWithRouteApi($existingContent),
|
$this->getWithRouteApi(),
|
||||||
$this->getEnd(),
|
$this->getEnd(),
|
||||||
];
|
];
|
||||||
$vmPath = $this->getvmPath('web_api');
|
$vmPath = $this->getvmPath('web_api');
|
||||||
@ -114,13 +113,12 @@ class WebApiGenerator extends BaseGenerator
|
|||||||
{
|
{
|
||||||
|
|
||||||
$content = file_get_contents($file);
|
$content = file_get_contents($file);
|
||||||
$code_begin = 'USER_CODE_BEGIN -- '.$this->getTableName();
|
$code_begin = 'USER_CODE_BEGIN -- '.$this->getTableName() . PHP_EOL;
|
||||||
$code_end = 'USER_CODE_END -- '.$this->getTableName();
|
$code_end = 'USER_CODE_END -- '.$this->getTableName() . PHP_EOL;
|
||||||
// 使用 \r?\n 替代 PHP_EOL,兼容不同系统的换行符
|
if(strpos($content,$code_begin) !== false && strpos($content,$code_end) !== false)
|
||||||
$pattern = '/\/\/\s+' . preg_quote($code_begin, '/') . '\r?\n[\S\s]+?\/\/\s+' . preg_quote($code_end, '/') . '\r?\n?/';
|
|
||||||
if(preg_match($pattern, $content))
|
|
||||||
{
|
{
|
||||||
// 非贪婪匹配 [?] 精确清除当前代码块,避免跨块误删
|
// 清除相应对应代码块
|
||||||
|
$pattern = "/\/\/\s+{$code_begin}[\S\s]+{$code_end}?/";
|
||||||
$import = preg_replace($pattern, '', $content);
|
$import = preg_replace($pattern, '', $content);
|
||||||
}else{
|
}else{
|
||||||
$import = $content;
|
$import = $content;
|
||||||
@ -242,7 +240,7 @@ class WebApiGenerator extends BaseGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWithRouteApi(string $existingContent = '')
|
public function getWithRouteApi()
|
||||||
{
|
{
|
||||||
if(!empty($this->addonName))
|
if(!empty($this->addonName))
|
||||||
{
|
{
|
||||||
@ -258,17 +256,11 @@ class WebApiGenerator extends BaseGenerator
|
|||||||
$with[] = Str::snake(substr($column['model'],$str+1));
|
$with[] = Str::snake(substr($column['model'],$str+1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$with = array_unique($with); // 单表内去重
|
|
||||||
if(!empty($with))
|
if(!empty($with))
|
||||||
{
|
{
|
||||||
foreach ($with as $value)
|
foreach ($with as $value)
|
||||||
{
|
{
|
||||||
// 跨表去重:检查已有文件内容中是否已定义该函数,避免不同表共用同一模块时重复生成
|
$content.= 'export function getWith'.Str::studly($value).'List(params: Record<string,any>){'.PHP_EOL." return request.get('".$moduleName.'/'.$value."_all', {params})".PHP_EOL.'}';
|
||||||
$funcName = 'getWith'.Str::studly($value).'List';
|
|
||||||
if (!empty($existingContent) && preg_match('/\b' . preg_quote($funcName, '/') . '\b/', $existingContent)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$content.= 'export function '.$funcName.'(params: Record<string,any>){'.PHP_EOL." return request.get('".$moduleName.'/'.$value."_all', {params})".PHP_EOL.'}';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $content;
|
return $content;
|
||||||
|
|||||||
@ -497,15 +497,12 @@ class WebEditGenerator extends BaseGenerator
|
|||||||
public function getDictList()
|
public function getDictList()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['dict_type']))
|
if(empty($column['dict_type']))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (in_array($column['dict_type'], $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $column['dict_type'];
|
|
||||||
$content.= 'let '.$column['column_name'].'List = ref([])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
$content.= 'let '.$column['column_name'].'List = ref([])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
||||||
if ($column['view_type'] == 'radio' || $column['view_type'] == 'select') {
|
if ($column['view_type'] == 'radio' || $column['view_type'] == 'select') {
|
||||||
$content .= 'watch(() => '.$column['column_name'].'List.value, () => { formData.'.$column['column_name'].' = '.$column['column_name'].'List.value[0].value })'.PHP_EOL;
|
$content .= 'watch(() => '.$column['column_name'].'List.value, () => { formData.'.$column['column_name'].' = '.$column['column_name'].'List.value[0].value })'.PHP_EOL;
|
||||||
@ -527,7 +524,6 @@ class WebEditGenerator extends BaseGenerator
|
|||||||
{
|
{
|
||||||
|
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['model']))
|
if(empty($column['model']))
|
||||||
@ -535,10 +531,7 @@ class WebEditGenerator extends BaseGenerator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([] as any[])'.PHP_EOL;
|
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([] as any[])'.PHP_EOL;
|
||||||
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
||||||
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
||||||
@ -554,14 +547,10 @@ class WebEditGenerator extends BaseGenerator
|
|||||||
public function getEditWithApiPath()
|
public function getEditWithApiPath()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column) {
|
foreach ($this->tableColumn as $column) {
|
||||||
if (!empty($column['model'])) {
|
if (!empty($column['model'])) {
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue;
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= ' get'.Str::studly($with).'List,';
|
$content.= ' get'.Str::studly($with).'List,';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -575,14 +564,10 @@ class WebEditGenerator extends BaseGenerator
|
|||||||
public function getWithApiPath()
|
public function getWithApiPath()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column) {
|
foreach ($this->tableColumn as $column) {
|
||||||
if (!empty($column['model'])) {
|
if (!empty($column['model'])) {
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue;
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= ', getWith'.Str::studly($with).'List';
|
$content.= ', getWith'.Str::studly($with).'List';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,15 +105,12 @@ class WebEditPageGenerator extends BaseGenerator
|
|||||||
public function getDictList()
|
public function getDictList()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['dict_type']))
|
if(empty($column['dict_type']))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (in_array($column['dict_type'], $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $column['dict_type'];
|
|
||||||
$content.= 'let '.$column['column_name'].'List = ref([])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
$content.= 'let '.$column['column_name'].'List = ref([])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
||||||
if ($column['view_type'] == 'radio' || $column['view_type'] == 'select') {
|
if ($column['view_type'] == 'radio' || $column['view_type'] == 'select') {
|
||||||
$content .= 'watch(() => '.$column['column_name'].'List.value, () => { formData.'.$column['column_name'].' = '.$column['column_name'].'List.value[0].value })'.PHP_EOL;
|
$content .= 'watch(() => '.$column['column_name'].'List.value, () => { formData.'.$column['column_name'].' = '.$column['column_name'].'List.value[0].value })'.PHP_EOL;
|
||||||
@ -513,7 +510,6 @@ class WebEditPageGenerator extends BaseGenerator
|
|||||||
public function getModelData()
|
public function getModelData()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['model']))
|
if(empty($column['model']))
|
||||||
@ -521,10 +517,7 @@ class WebEditPageGenerator extends BaseGenerator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([] as any[])'.PHP_EOL;
|
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([] as any[])'.PHP_EOL;
|
||||||
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
||||||
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
||||||
@ -540,14 +533,10 @@ class WebEditPageGenerator extends BaseGenerator
|
|||||||
public function getEditWithApiPath()
|
public function getEditWithApiPath()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column) {
|
foreach ($this->tableColumn as $column) {
|
||||||
if (!empty($column['model'])) {
|
if (!empty($column['model'])) {
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= ', getWith'.Str::studly($with).'List';
|
$content.= ', getWith'.Str::studly($with).'List';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -521,15 +521,12 @@ class WebIndexGenerator extends BaseGenerator
|
|||||||
public function getDictList()
|
public function getDictList()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['dict_type']))
|
if(empty($column['dict_type']))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (in_array($column['dict_type'], $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $column['dict_type'];
|
|
||||||
$content.= 'const '.$column['column_name'].'List = ref([] as any[])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
$content.= 'const '.$column['column_name'].'List = ref([] as any[])'.PHP_EOL.'const '.$column['column_name'].'DictList = async () => {'.PHP_EOL.$column['column_name'].'List.value = await (await useDictionary(' ."'".$column['dict_type']."'".')).data.dictionary'.PHP_EOL.'}'.PHP_EOL. $column['column_name'].'DictList();'.PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,14 +544,10 @@ class WebIndexGenerator extends BaseGenerator
|
|||||||
public function getWithApiPath()
|
public function getWithApiPath()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column) {
|
foreach ($this->tableColumn as $column) {
|
||||||
if (!empty($column['model'])) {
|
if (!empty($column['model'])) {
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= ', getWith'.Str::studly($with).'List';
|
$content.= ', getWith'.Str::studly($with).'List';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -568,7 +561,6 @@ class WebIndexGenerator extends BaseGenerator
|
|||||||
public function getModelData()
|
public function getModelData()
|
||||||
{
|
{
|
||||||
$content = '';
|
$content = '';
|
||||||
$isExist = [];
|
|
||||||
foreach ($this->tableColumn as $column)
|
foreach ($this->tableColumn as $column)
|
||||||
{
|
{
|
||||||
if(empty($column['model']))
|
if(empty($column['model']))
|
||||||
@ -576,10 +568,7 @@ class WebIndexGenerator extends BaseGenerator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$str = strripos($column['model'],'\\');
|
$str = strripos($column['model'],'\\');
|
||||||
$modelName = substr($column['model'], $str + 1);
|
$with = Str::camel(substr($column['model'],$str+1));
|
||||||
if (in_array($modelName, $isExist)) continue; // 去重
|
|
||||||
$isExist[] = $modelName;
|
|
||||||
$with = Str::camel($modelName);
|
|
||||||
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([])'.PHP_EOL;
|
$content.= PHP_EOL.'const '. Str::camel($column['column_name']).'List = ref([])'.PHP_EOL;
|
||||||
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
$content.= 'const set'.Str::studly($column['column_name']).'List = async () => {'.PHP_EOL.Str::camel($column['column_name']).'List.value = await (await getWith'.Str::studly($with).'List({})).data' .PHP_EOL.'}'
|
||||||
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
.PHP_EOL.'set'.Str::studly($column['column_name']).'List()';
|
||||||
|
|||||||
@ -63,25 +63,18 @@ class SiteService extends BaseAdminService
|
|||||||
*/
|
*/
|
||||||
public function getPage(array $where = [])
|
public function getPage(array $where = [])
|
||||||
{
|
{
|
||||||
$admin_site_info = $this->model->where([['site_id', '=', 0]])->field('logo, icon')->findOrEmpty()->toArray();
|
|
||||||
$field = 'site_id, site_name, front_end_name, front_end_logo, front_end_icon, app_type, keywords, logo, icon, `desc`, status, latitude, longitude, province_id, city_id,
|
$field = 'site_id, site_name, front_end_name, front_end_logo, front_end_icon, app_type, keywords, logo, icon, `desc`, status, latitude, longitude, province_id, city_id,
|
||||||
district_id, address, full_address, phone, business_hours, create_time, expire_time, group_id, app, addons, site_domain';
|
district_id, address, full_address, phone, business_hours, create_time, expire_time, group_id, app, addons, site_domain';
|
||||||
$condition = [
|
$condition = [
|
||||||
['app_type', '<>', 'admin']
|
['app_type', '<>', 'admin']
|
||||||
];
|
];
|
||||||
$search_model = $this->model->where($condition)->withSearch(['create_time', 'expire_time', 'keywords', 'status', 'group_id', 'app', 'site_domain'], $where)->with(['groupName'])->field($field)->append(['status_name'])->order('create_time desc');
|
$search_model = $this->model->where($condition)->withSearch(['create_time', 'expire_time', 'keywords', 'status', 'group_id', 'app', 'site_domain'], $where)->with(['groupName'])->field($field)->append(['status_name'])->order('create_time desc');
|
||||||
return $this->pageQuery($search_model, function ($item) use ($admin_site_info) {
|
return $this->pageQuery($search_model, function ($item) {
|
||||||
$item['admin'] = (new SysUserRole())->where([['site_id', '=', $item['site_id']], ['is_admin', '=', 1]])
|
$item['admin'] = (new SysUserRole())->where([['site_id', '=', $item['site_id']], ['is_admin', '=', 1]])
|
||||||
->field('uid')
|
->field('uid')
|
||||||
->with(['userinfo'])
|
->with(['userinfo'])
|
||||||
->findOrempty()->toArray();
|
->findOrempty()->toArray();
|
||||||
|
|
||||||
if (empty($item['logo'])) {
|
|
||||||
$item['logo'] = $admin_site_info['logo'] ?? 'static/resource/images/site/logo.png';
|
|
||||||
}
|
|
||||||
if (empty($item['icon'])) {
|
|
||||||
$item['icon'] = $admin_site_info['icon'] ?? 'static/resource/images/site/icon.jpg';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,15 +93,11 @@ class SiteService extends BaseAdminService
|
|||||||
$info['site_addons'] = (new Addon())->where([['key', 'in', $site_addons]])->field('key,title,desc,icon,type')->select()->toArray();
|
$info['site_addons'] = (new Addon())->where([['key', 'in', $site_addons]])->field('key,title,desc,icon,type')->select()->toArray();
|
||||||
$info['uid'] = (new SysUserRole())->where([['site_id', '=', $site_id], ['is_admin', '=', 1]])->value('uid');
|
$info['uid'] = (new SysUserRole())->where([['site_id', '=', $site_id], ['is_admin', '=', 1]])->value('uid');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($info['logo']) || empty($info['icon'])){
|
|
||||||
$admin_site_info = $this->model->where([['site_id', '=', 0]])->field('logo, icon')->findOrEmpty()->toArray();
|
|
||||||
}
|
|
||||||
if (empty($info['logo'])) {
|
if (empty($info['logo'])) {
|
||||||
$info['logo'] = $admin_site_info['logo'] ?? 'static/resource/images/site/logo.png';
|
$info['logo'] = 'static/resource/images/site/logo.png';
|
||||||
}
|
}
|
||||||
if (empty($info['icon'])) {
|
if (empty($info['icon'])) {
|
||||||
$info['icon'] = $admin_site_info['icon'] ?? 'static/resource/images/site/icon.png';
|
$info['icon'] = 'static/resource/images/site/icon.png';
|
||||||
}
|
}
|
||||||
return $info;
|
return $info;
|
||||||
}
|
}
|
||||||
@ -126,8 +115,6 @@ class SiteService extends BaseAdminService
|
|||||||
if ($site_group->isEmpty()) throw new CommonException('SITE_GROUP_NOT_EXIST');
|
if ($site_group->isEmpty()) throw new CommonException('SITE_GROUP_NOT_EXIST');
|
||||||
|
|
||||||
$data['app_type'] = 'site';
|
$data['app_type'] = 'site';
|
||||||
$admin_site_info = $this->model->where([['site_id', '=', 0]])->field('logo, icon')->findOrEmpty()->toArray();
|
|
||||||
|
|
||||||
//添加站点
|
//添加站点
|
||||||
$data_site = [
|
$data_site = [
|
||||||
'site_domain' => $data['site_domain'] ?? '',
|
'site_domain' => $data['site_domain'] ?? '',
|
||||||
@ -139,8 +126,7 @@ class SiteService extends BaseAdminService
|
|||||||
'app' => $site_group['app'],
|
'app' => $site_group['app'],
|
||||||
'addons' => '',
|
'addons' => '',
|
||||||
'status' => strtotime($data['expire_time']) > time() ? SiteDict::ON : SiteDict::EXPIRE,
|
'status' => strtotime($data['expire_time']) > time() ? SiteDict::ON : SiteDict::EXPIRE,
|
||||||
'icon' => $admin_site_info['icon'] ?? 'static/resource/images/site/icon.jpg',
|
'icon' => 'static/resource/images/site/site_icon.jpg'
|
||||||
'logo' => $admin_site_info['logo'] ?? 'static/resource/images/site/logo.png',
|
|
||||||
];
|
];
|
||||||
Db::startTrans();
|
Db::startTrans();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user