升级Vue3,iView替换ElementPlus

- 删除babel配置、更新依赖与入口初始化
- 全量替换UI组件、样式适配,新增迁移文档与标签/过滤器自动化替换脚本
This commit is contained in:
lifenlong
2026-06-05 17:49:43 +08:00
parent 615ee91511
commit 832fda813b
322 changed files with 25693 additions and 24453 deletions

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env node
/**
* Copy migrated manager Vue files to seller, preserving seller API imports where they differ.
*/
const fs = require("fs");
const path = require("path");
const SELLER = path.join(__dirname, "..");
const MANAGER = path.join(__dirname, "../../manager");
const FILE_MAP = [
["src/views/distribution/distributionOrder.vue", "src/views/distribution/distributionOrder.vue"],
["src/views/distribution/distributionGoods.vue", "src/views/distribution/distributionGoods.vue"],
["src/views/statistics/order.vue", "src/views/statistics/order.vue"],
["src/views/statistics/goods.vue", "src/views/statistics/goods.vue"],
["src/views/statistics/traffic.vue", "src/views/statistics/traffic.vue"],
["src/views/statistics/order/orderDetail.vue", "src/views/statistics/order/orderDetail.vue"],
["src/views/statistics/order/refundOrder.vue", "src/views/statistics/order/refundOrder.vue"],
["src/views/promotion/coupon/coupon.vue", "src/views/promotions/coupon/coupon.vue"],
["src/views/promotion/coupon/coupon-publish.vue", "src/views/promotions/coupon/coupon-publish.vue"],
["src/views/promotion/coupon/coupon-receive.vue", "src/views/promotions/coupon/coupon-receive.vue"],
["src/views/promotion/full-discount/full-discount.vue", "src/views/promotions/full-discount/full-discount.vue"],
["src/views/promotion/full-discount/full-discount-add.vue", "src/views/promotions/full-discount/full-discount-detail.vue"],
["src/views/promotion/pintuan/pintuan.vue", "src/views/promotions/pintuan/pintuan.vue"],
["src/views/promotion/pintuan/pintuan-goods.vue", "src/views/promotions/pintuan/pintuan-goods.vue"],
["src/views/promotion/pintuan/pintuan-edit.vue", "src/views/promotions/pintuan/pintuan-goods.vue"],
["src/views/promotion/seckill/seckill.vue", "src/views/promotions/seckill/seckill.vue"],
["src/views/promotion/seckill/seckill-goods.vue", "src/views/promotions/seckill/seckill-goods.vue"],
["src/views/promotion/live/live.vue", "src/views/promotions/live/live.vue"],
["src/views/promotion/live/liveGoods.vue", "src/views/promotions/live/live-detail.vue"],
["src/views/goods/goods-seller/goods.vue", "src/views/goods/goods-info/goods.vue"],
["src/views/goods/goods-manage/category.vue", "src/views/goods/goods-manage/category.vue"],
["src/views/sys/oss-manage/ossManage.vue", "src/views/sys/oss-manage/ossManage.vue"],
["src/views/order/order/orderList.vue", "src/views/order/order/orderList.vue"],
["src/views/order/order/orderDetail.vue", "src/views/order/order/orderDetail.vue"],
["src/views/order/order/virtualOrderList.vue", "src/views/order/order/fictitiousOrderList.vue"],
["src/views/order/after-order/orderComplaint.vue", "src/views/order/after-order/orderComplaint.vue"],
["src/views/order/after-order/orderComplaintDetail.vue", "src/views/order/after-order/orderComplaintDetail.vue"],
["src/views/order/after-order/returnGoodsOrder.vue", "src/views/order/after-order/afterSaleOrder.vue"],
["src/views/order/after-order/returnMoneyOrder.vue", "src/views/order/after-order/afterSale.vue"],
["src/views/order/after-order/reurnGoodsOrderDetail.vue", "src/views/order/after-order/afterSaleOrderDetail.vue"],
["src/views/shop/ossManage.vue", "src/views/sys/oss-manage/ossManage.vue"],
];
function extractImports(content) {
const imports = [];
const re = /^import\s+.+from\s+["']@\/api\/[^"']+["'];?\s*$/gm;
let m;
while ((m = re.exec(content)) !== null) imports.push(m[0]);
return imports;
}
function adaptForSeller(content, sellerRel, sellerOriginal) {
let c = content;
// Restore seller API imports from original file
if (sellerOriginal) {
const sellerImports = extractImports(sellerOriginal);
const managerImports = extractImports(content);
for (const mi of managerImports) {
const apiPath = mi.match(/from\s+["'](@\/api\/[^"']+)["']/);
if (!apiPath) continue;
const sellerMatch = sellerImports.find((si) => si.includes(apiPath[1]) || si.includes(path.basename(apiPath[1])));
if (sellerMatch && sellerMatch !== mi) {
c = c.replace(mi, sellerMatch);
}
}
// Also restore seller-specific import paths that differ
sellerImports.forEach((si) => {
const from = si.match(/from\s+["']([^"']+)["']/);
if (from && from[1].includes("@/api/")) {
const apiName = from[1].split("/").pop();
const managerImport = managerImports.find((mi) => mi.includes(apiName));
if (!managerImport) {
// seller-only import - append if missing
if (!c.includes(si)) {
const scriptIdx = c.indexOf("<script>");
if (scriptIdx >= 0) {
const insertAt = c.indexOf("\n", scriptIdx) + 1;
c = c.slice(0, insertAt) + si + "\n" + c.slice(insertAt);
}
}
}
}
});
}
// Seller path adjustments
c = c.replace(/@\/views\/page-decoration\//g, "@/views/shop/");
c = c.replace(/@\/components\/affix-time/g, "@/views/lili-components/affix-time");
c = c.replace(/admin-setting/g, "seller-setting");
c = c.replace(/adminPCPageCache/g, "sellerPCPageCache");
c = c.replace(/userInfoManager/g, "userInfoSeller");
c = c.replace(/JSON\.parse\(Cookies\.get\("userInfoManager"\)\)/g, 'JSON.parse(Cookies.get("userInfoSeller"))');
// ossManage seller variant - hide role tabs, use STORE only
if (sellerRel.includes("shop/ossManage.vue")) {
c = c.replace(/<el-tab-pane label="管理员" name="MANAGER" \/>[\s\S]*?<el-tab-pane label="客服" name="CUSTOMER" \/>/,
'<el-tab-pane label="商家" name="STORE" />');
c = c.replace(/activeRoleTab: "MANAGER"/, 'activeRoleTab: "STORE"');
}
return c;
}
const copied = [];
const missing = [];
const skipped = [];
for (const [sellerRel, managerRel] of FILE_MAP) {
const managerPath = path.join(MANAGER, managerRel);
const sellerPath = path.join(SELLER, sellerRel);
if (!fs.existsSync(managerPath)) {
missing.push({ seller: sellerRel, manager: managerRel });
continue;
}
const sellerOriginal = fs.existsSync(sellerPath) ? fs.readFileSync(sellerPath, "utf8") : null;
let content = fs.readFileSync(managerPath, "utf8");
content = adaptForSeller(content, sellerRel, sellerOriginal);
fs.mkdirSync(path.dirname(sellerPath), { recursive: true });
fs.writeFileSync(sellerPath, content);
copied.push(sellerRel);
}
console.log(`Copied ${copied.length} files from manager`);
if (missing.length) {
console.log("Missing manager files:");
missing.forEach((m) => console.log(` ${m.seller} <- ${m.manager}`));
}

View File

@@ -0,0 +1,229 @@
#!/usr/bin/env node
/**
* Migrate seller Vue files from iView to Element Plus.
* Usage: node scripts/migrate-iview-to-element.js
*/
const fs = require("fs");
const path = require("path");
const SELLER_ROOT = path.join(__dirname, "..");
const MANAGER_ROOT = path.join(__dirname, "../../manager");
const SHOP_COPY_MAP = [
["src/views/shop/modelForm.vue", "src/views/page-decoration/modelForm.vue"],
["src/views/shop/modelFormItem.vue", "src/views/page-decoration/modelFormItem.vue"],
["src/views/shop/renovation.vue", "src/views/page-decoration/renovation.vue"],
["src/views/shop/floorList.vue", "src/views/page-decoration/floorList.vue"],
["src/views/shop/wap/index.vue", "src/views/page-decoration/wap/index.vue"],
["src/views/shop/wap/main.vue", "src/views/page-decoration/wap/main.vue"],
["src/views/shop/wap/decorate.vue", "src/views/page-decoration/wap/decorate.vue"],
["src/views/shop/wap/navbar.vue", "src/views/page-decoration/wap/navbar.vue"],
["src/views/shop/wap/wapList.vue", "src/views/page-decoration/wap/wapList.vue"],
["src/views/shop/wap/advertising.vue", "src/views/page-decoration/wap/advertising.vue"],
["src/views/shop/wap/alertAdvertising.vue", "src/views/page-decoration/wap/alertAdvertising.vue"],
];
const WAP_TEMPLATES = [
"tpl_banner.vue", "tpl_flex_five.vue", "tpl_flex_four.vue", "tpl_flex_one.vue",
"tpl_flex_three.vue", "tpl_flex_two.vue", "tpl_goods.vue", "tpl_group.vue",
"tpl_integral.vue", "tpl_join_group.vue", "tpl_left_one_right_two.vue",
"tpl_left_two_right_one.vue", "tpl_menu.vue", "tpl_search.vue", "tpl_spike.vue",
"tpl_text_picture.vue", "tpl_title.vue", "tpl_top_one_bottom_two.vue",
"tpl_top_two_bottom_one.vue", "tpl_view_list.vue",
];
const MODEL_LIST = [
"carousel.vue", "carousel1.vue", "carousel2.vue", "firstPageAdvert.vue",
"newGoodsSort.vue", "notEnough.vue", "recommend.vue", "seckill.vue",
];
WAP_TEMPLATES.forEach((f) => {
SHOP_COPY_MAP.push([
`src/views/shop/wap/template/${f}`,
`src/views/page-decoration/wap/template/${f}`,
]);
});
MODEL_LIST.forEach((f) => {
SHOP_COPY_MAP.push([
`src/views/shop/modelList/${f}`,
`src/views/page-decoration/modelList/${f}`,
]);
});
const TARGET_DIRS = [
"src/views/order",
"src/views/distribution",
"src/views/statistics",
"src/views/promotion",
"src/views/goods",
"src/views/shop",
"src/views/sys",
"src/views/member",
"src/views/message",
];
function adaptSellerContent(content, sellerRelPath) {
let c = content;
// Seller-specific API/path adjustments for copied decoration files
if (sellerRelPath.includes("shop/")) {
c = c.replace(/@\/views\/page-decoration\//g, "@/views/shop/");
c = c.replace(/admin-setting/g, "seller-setting");
c = c.replace(/adminPCPageCache/g, "sellerPCPageCache");
c = c.replace(/getStore\('adminPCPageCache'\)/g, "getStore('sellerPCPageCache')");
c = c.replace(/setStore\('adminPCPageCache'/g, "setStore('sellerPCPageCache'");
c = c.replace(/@\/api\/other\.js/g, "@/api/other.js");
}
return c;
}
function applyScriptTransforms(content) {
let c = content;
c = c.replace(/\$options\.filters\./g, "$filters.");
c = c.replace(/\bbeforeDestroy\b/g, "beforeUnmount");
c = c.replace(/this\.\$set\(([^,]+),\s*([^,]+),\s*([^)]+)\)/g, "$1[$2] = $3");
c = c.replace(/::v-deep/g, ":deep");
c = c.replace(/\/deep\//g, ":deep");
c = c.replace(/@on-change/g, "@change");
c = c.replace(/@on-click/g, "@click");
c = c.replace(/@on-ok/g, "@ok");
c = c.replace(/@on-cancel/g, "@cancel");
c = c.replace(/@on-clear/g, "@clear");
c = c.replace(/@on-open-change/g, "@visible-change");
c = c.replace(/@on-page-size-change/g, "@size-change");
c = c.replace(/@keydown\.enter\.native/g, "@keyup.enter");
c = c.replace(/slot-scope="([^"]+)"/g, '#default="$1"');
c = c.replace(/slot="([^"]+)"/g, '#$1');
c = c.replace(/\|\s*unitPrice\(/g, "{{ $filters.unitPrice(");
return c;
}
function applyTemplateTransforms(content) {
let c = content;
const replacements = [
[/<\/?Card>/g, (m) => m.replace("Card", "el-card")],
[/<Form-item/g, "<el-form-item"],
[/<\/Form-item>/g, "</el-form-item>"],
[/<FormItem/g, "<el-form-item"],
[/<\/FormItem>/g, "</el-form-item>"],
[/<Form(\s|>)/g, "<el-form$1"],
[/<\/Form>/g, "</el-form>"],
[/<Input(\s|>)/g, "<el-input$1"],
[/<\/Input>/g, "</el-input>"],
[/<Button(\s|>)/g, "<el-button$1"],
[/<\/Button>/g, "</el-button>"],
[/<Select(\s|>)/g, "<el-select$1"],
[/<\/Select>/g, "</el-select>"],
[/<Option(\s)/g, "<el-option "],
[/<\/Option>/g, "</el-option>"],
[/<DatePicker/g, "<el-date-picker"],
[/<\/DatePicker>/g, "</el-date-picker>"],
[/<Modal/g, "<el-dialog"],
[/<\/Modal>/g, "</el-dialog>"],
[/<Page(\s)/g, "<el-pagination "],
[/<\/Page>/g, "</el-pagination>"],
[/<Tabs(\s|>)/g, "<el-tabs$1"],
[/<\/Tabs>/g, "</el-tabs>"],
[/<TabPane/g, "<el-tab-pane"],
[/<\/TabPane>/g, "</el-tab-pane>"],
[/<Row(\s|>)/g, "<div$1"],
[/<\/Row>/g, "</div>"],
[/<Col(\s|>)/g, "<div$1"],
[/<\/Col>/g, "</div>"],
[/<Tag(\s|>)/g, "<el-tag$1"],
[/<\/Tag>/g, "</el-tag>"],
[/<Switch(\s|>)/g, "<el-switch$1"],
[/<\/Switch>/g, "</el-switch>"],
[/<RadioGroup/g, "<el-radio-group"],
[/<\/RadioGroup>/g, "</el-radio-group>"],
[/<Radio(\s)/g, "<el-radio "],
[/<\/Radio>/g, "</el-radio>"],
[/<Checkbox/g, "<el-checkbox"],
[/<\/Checkbox>/g, "</el-checkbox>"],
[/<Poptip/g, "<el-popover"],
[/<\/Poptip>/g, "</el-popover>"],
[/<Tooltip/g, "<el-tooltip"],
[/<\/Tooltip>/g, "</el-tooltip>"],
[/:label-width="(\d+)"/g, 'label-width="$1px"'],
[/:mask-closable="false"/g, ':close-on-click-modal="false"'],
[/:width="(\d+)"/g, 'width="$1"'],
[/\bv-model="modalVisible"/g, 'v-model="modalVisible"'],
];
for (const [pattern, replacement] of replacements) {
c = c.replace(pattern, replacement);
}
return c;
}
function walkDir(dir, files = []) {
if (!fs.existsSync(dir)) return files;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkDir(full, files);
} else if (entry.name.endsWith(".vue") && entry.name !== "readme.vue") {
files.push(full);
}
}
return files;
}
const copied = [];
const transformed = [];
const skipped = [];
// Copy decoration files from manager
for (const [sellerRel, managerRel] of SHOP_COPY_MAP) {
const managerPath = path.join(MANAGER_ROOT, managerRel);
const sellerPath = path.join(SELLER_ROOT, sellerRel);
if (!fs.existsSync(managerPath)) {
skipped.push({ file: sellerRel, reason: "manager counterpart missing" });
continue;
}
let content = fs.readFileSync(managerPath, "utf8");
content = adaptSellerContent(content, sellerRel);
fs.mkdirSync(path.dirname(sellerPath), { recursive: true });
fs.writeFileSync(sellerPath, content);
copied.push(sellerRel);
}
// Transform remaining target files (skip already copied)
const copiedSet = new Set(copied);
for (const dir of TARGET_DIRS) {
const absDir = path.join(SELLER_ROOT, dir);
for (const filePath of walkDir(absDir)) {
const rel = path.relative(SELLER_ROOT, filePath).replace(/\\/g, "/");
if (copiedSet.has(rel)) continue;
let content = fs.readFileSync(filePath, "utf8");
const original = content;
// Skip if already fully migrated (has el-table and no iView Table)
const hasIView =
/<(Card|Form|Form-item|FormItem|Table|Button|Modal|Page|Tabs|TabPane|Input|Select|Option|DatePicker|Row|Poptip)\b/.test(
content
);
if (!hasIView) {
skipped.push({ file: rel, reason: "already migrated or no iView" });
continue;
}
content = applyTemplateTransforms(content);
content = applyScriptTransforms(content);
fs.writeFileSync(filePath, content);
transformed.push(rel);
}
}
console.log("=== Migration Summary ===");
console.log(`Copied from manager: ${copied.length}`);
console.log(`Script-transformed: ${transformed.length}`);
console.log(`Skipped: ${skipped.length}`);
console.log("\nCopied files:");
copied.forEach((f) => console.log(" " + f));
console.log("\nTransformed files:");
transformed.forEach((f) => console.log(" " + f));
console.log("\nSkipped:");
skipped.forEach(({ file, reason }) => console.log(` ${file}: ${reason}`));