mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2025-12-11 04:22:54 +00:00
Compare commits
8 Commits
6bd4aef03b
...
e66d64d83e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e66d64d83e | ||
|
|
9330acc33c | ||
|
|
e125957ef5 | ||
|
|
798ecc7304 | ||
|
|
83360678c2 | ||
|
|
928848877b | ||
|
|
09d4697629 | ||
|
|
79fd3b9093 |
1
packages/vite-plugin/dist/config.d.ts
vendored
1
packages/vite-plugin/dist/config.d.ts
vendored
@ -31,4 +31,5 @@ export declare const config: {
|
|||||||
rpxRatio: number;
|
rpxRatio: number;
|
||||||
darkTextClass: string;
|
darkTextClass: string;
|
||||||
};
|
};
|
||||||
|
clean: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
383
packages/vite-plugin/dist/index.js
vendored
383
packages/vite-plugin/dist/index.js
vendored
@ -56,6 +56,7 @@
|
|||||||
rpxRatio: 2,
|
rpxRatio: 2,
|
||||||
darkTextClass: "dark:text-surface-50",
|
darkTextClass: "dark:text-surface-50",
|
||||||
},
|
},
|
||||||
|
clean: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根目录
|
// 根目录
|
||||||
@ -92,13 +93,69 @@
|
|||||||
function readFile(path, json) {
|
function readFile(path, json) {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(path, "utf8");
|
const content = fs.readFileSync(path, "utf8");
|
||||||
return json
|
return json ? JSON.parse(removeJsonComments(content)) : content;
|
||||||
? JSON.parse(content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""))
|
|
||||||
: content;
|
|
||||||
}
|
}
|
||||||
catch (err) { }
|
catch (err) { }
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
// 安全地移除JSON中的注释
|
||||||
|
function removeJsonComments(content) {
|
||||||
|
let result = "";
|
||||||
|
let inString = false;
|
||||||
|
let stringChar = "";
|
||||||
|
let escaped = false;
|
||||||
|
let i = 0;
|
||||||
|
while (i < content.length) {
|
||||||
|
const char = content[i];
|
||||||
|
const nextChar = content[i + 1];
|
||||||
|
// 处理字符串状态
|
||||||
|
if (!inString && (char === '"' || char === "'")) {
|
||||||
|
inString = true;
|
||||||
|
stringChar = char;
|
||||||
|
result += char;
|
||||||
|
}
|
||||||
|
else if (inString && char === stringChar && !escaped) {
|
||||||
|
inString = false;
|
||||||
|
stringChar = "";
|
||||||
|
result += char;
|
||||||
|
}
|
||||||
|
else if (inString) {
|
||||||
|
// 在字符串内,直接添加字符
|
||||||
|
result += char;
|
||||||
|
escaped = char === "\\" && !escaped;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 不在字符串内,检查注释
|
||||||
|
if (char === "/" && nextChar === "/") {
|
||||||
|
// 单行注释,跳过到行尾
|
||||||
|
while (i < content.length && content[i] !== "\n") {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i < content.length) {
|
||||||
|
result += content[i]; // 保留换行符
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (char === "/" && nextChar === "*") {
|
||||||
|
// 多行注释,跳过到 */
|
||||||
|
i += 2;
|
||||||
|
while (i < content.length - 1) {
|
||||||
|
if (content[i] === "*" && content[i + 1] === "/") {
|
||||||
|
i += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result += char;
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
// 写入文件
|
// 写入文件
|
||||||
function writeFile(path, data) {
|
function writeFile(path, data) {
|
||||||
try {
|
try {
|
||||||
@ -138,6 +195,26 @@
|
|||||||
function error(message) {
|
function error(message) {
|
||||||
console.log("\x1B[31m%s\x1B[0m", message);
|
console.log("\x1B[31m%s\x1B[0m", message);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 比较两个版本号
|
||||||
|
* @param version1 版本号1 (如: "1.2.3")
|
||||||
|
* @param version2 版本号2 (如: "1.2.4")
|
||||||
|
* @returns 1: version1 > version2, 0: 相等, -1: version1 < version2
|
||||||
|
*/
|
||||||
|
function compareVersion(version1, version2) {
|
||||||
|
const v1Parts = version1.split(".").map(Number);
|
||||||
|
const v2Parts = version2.split(".").map(Number);
|
||||||
|
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const v1Part = v1Parts[i] || 0;
|
||||||
|
const v2Part = v2Parts[i] || 0;
|
||||||
|
if (v1Part > v2Part)
|
||||||
|
return 1;
|
||||||
|
if (v1Part < v2Part)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将模板字符串扁平化处理,转换为 Service 类型定义
|
* 将模板字符串扁平化处理,转换为 Service 类型定义
|
||||||
@ -151,15 +228,25 @@
|
|||||||
// 保留 Service 类型定义前的内容
|
// 保留 Service 类型定义前的内容
|
||||||
let header = template.substring(0, startIndex);
|
let header = template.substring(0, startIndex);
|
||||||
// 获取 Service 类型定义及其内容,去除换行和制表符
|
// 获取 Service 类型定义及其内容,去除换行和制表符
|
||||||
const serviceContent = template.substring(startIndex).replace(/\n|\t/g, "");
|
const serviceTemplateContent = template.substring(startIndex).replace(/\n|\t/g, "");
|
||||||
|
// 找到 Service 的内容部分
|
||||||
|
const serviceStartIndex = serviceTemplateContent.indexOf("{") + 1;
|
||||||
|
const serviceEndIndex = findClosingBrace(serviceTemplateContent, serviceStartIndex);
|
||||||
|
const serviceInnerContent = serviceTemplateContent
|
||||||
|
.substring(serviceStartIndex, serviceEndIndex)
|
||||||
|
.trim();
|
||||||
|
// 存储所有接口定义
|
||||||
|
const allInterfaces = new Map();
|
||||||
|
// 处理 Service 内容,保持原有结构但替换嵌套对象为接口引用
|
||||||
|
const serviceContent = buildCurrentLevelContent(serviceInnerContent);
|
||||||
|
// 递归收集所有需要生成的接口
|
||||||
|
flattenContent(serviceInnerContent, allInterfaces);
|
||||||
|
// 生成所有接口定义
|
||||||
let interfaces = "";
|
let interfaces = "";
|
||||||
let serviceFields = "";
|
allInterfaces.forEach((content, key) => {
|
||||||
// 解析内容并生成接口定义
|
interfaces += `\nexport interface ${firstUpperCase(key)}Interface { ${content} }\n`;
|
||||||
parse(serviceContent).forEach(({ key, content, level }) => {
|
|
||||||
interfaces += `\nexport interface ${firstUpperCase(key)}Interface {${content}}\n`;
|
|
||||||
serviceFields += `${key}: ${firstUpperCase(key)}Interface;`;
|
|
||||||
});
|
});
|
||||||
return `${header}${interfaces}\nexport type Service = {${serviceFields}}`;
|
return `${header}${interfaces}\nexport type Service = { ${serviceContent} }`;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 查找匹配的右花括号位置
|
* 查找匹配的右花括号位置
|
||||||
@ -184,40 +271,51 @@
|
|||||||
return currentIndex - 1;
|
return currentIndex - 1;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 解析内容中的嵌套结构
|
* 递归收集所有需要生成的接口
|
||||||
* @param content - 要解析的内容字符串
|
* @param content - 要处理的内容
|
||||||
* @returns 解析结果数组,包含解析出的键值对
|
* @param allInterfaces - 存储所有接口定义的 Map
|
||||||
|
* @param parentFields - 父级字段数组(暂未使用)
|
||||||
*/
|
*/
|
||||||
function parse(content, level = 0) {
|
function flattenContent(content, allInterfaces, parentFields) {
|
||||||
// 匹配形如 xxx: { ... } 的结构
|
|
||||||
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
||||||
const result = [];
|
|
||||||
let match;
|
let match;
|
||||||
while ((match = interfacePattern.exec(content)) !== null) {
|
while ((match = interfacePattern.exec(content)) !== null) {
|
||||||
|
const key = match[1];
|
||||||
const startIndex = match.index + match[0].length;
|
const startIndex = match.index + match[0].length;
|
||||||
const endIndex = findClosingBrace(content, startIndex);
|
const endIndex = findClosingBrace(content, startIndex);
|
||||||
if (endIndex > startIndex) {
|
if (endIndex > startIndex) {
|
||||||
let parsedContent = content.substring(startIndex, endIndex).trim();
|
const innerContent = content.substring(startIndex, endIndex).trim();
|
||||||
// 处理嵌套结构
|
// 构建当前接口的内容,将嵌套对象替换为接口引用
|
||||||
if (parsedContent.includes("{") && parsedContent.includes("}")) {
|
const currentLevelContent = buildCurrentLevelContent(innerContent);
|
||||||
const nestedInterfaces = parse(parsedContent, level + 1);
|
allInterfaces.set(key, currentLevelContent);
|
||||||
// 替换嵌套的内容为接口引用
|
// 递归处理嵌套内容
|
||||||
if (nestedInterfaces.length > 0) {
|
flattenContent(innerContent, allInterfaces);
|
||||||
nestedInterfaces.forEach((nestedInterface) => {
|
|
||||||
const pattern = `${nestedInterface.key}: {${nestedInterface.content}};`;
|
|
||||||
const replacement = `${nestedInterface.key}: ${firstUpperCase(nestedInterface.key)}Interface`;
|
|
||||||
parsedContent = parsedContent.replace(pattern, replacement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 将解析结果添加到数组开头
|
|
||||||
result.unshift({
|
|
||||||
key: match[1],
|
|
||||||
level,
|
|
||||||
content: parsedContent,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 构建当前级别的内容,将嵌套对象替换为接口引用
|
||||||
|
* @param content - 内容字符串
|
||||||
|
* @returns 处理后的内容
|
||||||
|
*/
|
||||||
|
function buildCurrentLevelContent(content) {
|
||||||
|
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
||||||
|
let result = content;
|
||||||
|
let match;
|
||||||
|
// 重置正则表达式的 lastIndex
|
||||||
|
interfacePattern.lastIndex = 0;
|
||||||
|
while ((match = interfacePattern.exec(content)) !== null) {
|
||||||
|
const key = match[1];
|
||||||
|
const startIndex = match.index + match[0].length;
|
||||||
|
const endIndex = findClosingBrace(content, startIndex);
|
||||||
|
if (endIndex > startIndex) {
|
||||||
|
const fullMatch = content.substring(match.index, endIndex + 1);
|
||||||
|
const replacement = `${key}: ${firstUpperCase(key)}Interface;`;
|
||||||
|
result = result.replace(fullMatch, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 清理多余的分号和空格
|
||||||
|
result = result.replace(/;+/g, ";").replace(/\s+/g, " ").trim();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,8 +895,9 @@
|
|||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
trailingComma: "none",
|
trailingComma: "none",
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
error(`[cool-eps] Failed to format /build/cool/eps.d.ts. Please delete the file and try again`);
|
console.log(err);
|
||||||
|
error(`[cool-eps] File format error, please try again`);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -846,7 +945,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (config.type == "uniapp-x" || config.type == "app") {
|
if (config.type == "uniapp-x" || config.type == "app") {
|
||||||
list = list.filter((e) => e.prefix.startsWith("/app"));
|
list = list.filter((e) => e.prefix.startsWith("/app") || e.prefix.startsWith("/admin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -854,22 +953,25 @@
|
|||||||
* @returns {boolean} 是否有更新
|
* @returns {boolean} 是否有更新
|
||||||
*/
|
*/
|
||||||
function createJson() {
|
function createJson() {
|
||||||
if (config.type == "uniapp-x") {
|
let data = [];
|
||||||
return false;
|
if (config.type != "uniapp-x") {
|
||||||
|
data = list.map((e) => {
|
||||||
|
return {
|
||||||
|
prefix: e.prefix,
|
||||||
|
name: e.name || "",
|
||||||
|
api: e.api.map((apiItem) => ({
|
||||||
|
name: apiItem.name,
|
||||||
|
method: apiItem.method,
|
||||||
|
path: apiItem.path,
|
||||||
|
})),
|
||||||
|
search: e.search,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const arr = list.map((e) => {
|
else {
|
||||||
return {
|
data = list;
|
||||||
prefix: e.prefix,
|
}
|
||||||
name: e.name || "",
|
const content = JSON.stringify(data);
|
||||||
api: e.api.map((apiItem) => ({
|
|
||||||
name: apiItem.name,
|
|
||||||
method: apiItem.method,
|
|
||||||
path: apiItem.path,
|
|
||||||
})),
|
|
||||||
search: e.search,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const content = JSON.stringify(arr);
|
|
||||||
const local_content = readFile(getEpsPath("eps.json"));
|
const local_content = readFile(getEpsPath("eps.json"));
|
||||||
// 判断是否需要更新
|
// 判断是否需要更新
|
||||||
const isUpdate = content != local_content;
|
const isUpdate = content != local_content;
|
||||||
@ -894,6 +996,9 @@
|
|||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
if (!checkName(item.name))
|
if (!checkName(item.name))
|
||||||
continue;
|
continue;
|
||||||
|
if (formatName(item.name) == "BusinessInterface") {
|
||||||
|
console.log(111);
|
||||||
|
}
|
||||||
let t = `interface ${formatName(item.name)} {`;
|
let t = `interface ${formatName(item.name)} {`;
|
||||||
// 合并 columns 和 pageColumns,去重
|
// 合并 columns 和 pageColumns,去重
|
||||||
const columns = lodash.uniqBy(lodash.compact([...(item.columns || []), ...(item.pageColumns || [])]), "source");
|
const columns = lodash.uniqBy(lodash.compact([...(item.columns || []), ...(item.pageColumns || [])]), "source");
|
||||||
@ -1007,12 +1112,22 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// 方法描述
|
// 方法描述
|
||||||
t += `
|
if (config.type == "uniapp-x") {
|
||||||
/**
|
t += `
|
||||||
* ${a.summary || n}
|
/**
|
||||||
*/
|
* ${a.summary || n}
|
||||||
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
|
*/
|
||||||
`;
|
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<any>;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
t += `
|
||||||
|
/**
|
||||||
|
* ${a.summary || n}
|
||||||
|
*/
|
||||||
|
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (!permission.includes(n)) {
|
if (!permission.includes(n)) {
|
||||||
permission.push(n);
|
permission.push(n);
|
||||||
}
|
}
|
||||||
@ -1054,6 +1169,8 @@
|
|||||||
return `
|
return `
|
||||||
type json = any;
|
type json = any;
|
||||||
|
|
||||||
|
${await createDict()}
|
||||||
|
|
||||||
interface PagePagination {
|
interface PagePagination {
|
||||||
size: number;
|
size: number;
|
||||||
page: number;
|
page: number;
|
||||||
@ -1083,8 +1200,6 @@
|
|||||||
|
|
||||||
${noUniappX("type Request = (options: RequestOptions) => Promise<any>;")}
|
${noUniappX("type Request = (options: RequestOptions) => Promise<any>;")}
|
||||||
|
|
||||||
${await createDict()}
|
|
||||||
|
|
||||||
type Service = {
|
type Service = {
|
||||||
${noUniappX("request: Request;")}
|
${noUniappX("request: Request;")}
|
||||||
|
|
||||||
@ -1249,32 +1364,13 @@
|
|||||||
if (item.name) {
|
if (item.name) {
|
||||||
types.push(item.name);
|
types.push(item.name);
|
||||||
}
|
}
|
||||||
// 返回类型
|
|
||||||
let res = "";
|
|
||||||
// 实体名
|
|
||||||
const en = item.name || "any";
|
|
||||||
switch (a.path) {
|
|
||||||
case "/page":
|
|
||||||
res = `${name}PageResponse`;
|
|
||||||
types.push(res);
|
|
||||||
break;
|
|
||||||
case "/list":
|
|
||||||
res = `${en}[]`;
|
|
||||||
break;
|
|
||||||
case "/info":
|
|
||||||
res = en;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res = "any";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 方法描述
|
// 方法描述
|
||||||
t += `
|
t += `
|
||||||
/**
|
/**
|
||||||
* ${a.summary || n}
|
* ${a.summary || n}
|
||||||
*/
|
*/
|
||||||
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")})${noUniappX(`: Promise<${res}>`)} {
|
${n}(data?: any): Promise<any> {
|
||||||
return request<${res}>({
|
return request({
|
||||||
url: "/${d[i].namespace}${a.path}",
|
url: "/${d[i].namespace}${a.path}",
|
||||||
method: "${(a.method || "get").toLocaleUpperCase()}",
|
method: "${(a.method || "get").toLocaleUpperCase()}",
|
||||||
data,
|
data,
|
||||||
@ -2176,6 +2272,70 @@ if (typeof window !== 'undefined') {
|
|||||||
return [postcssPlugin(), transformPlugin()];
|
return [postcssPlugin(), transformPlugin()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取 tailwind.config.ts 中的颜色
|
||||||
|
function getTailwindColor() {
|
||||||
|
const config = readFile(rootDir("tailwind.config.ts"));
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 从配置文件中动态提取主色和表面色
|
||||||
|
const colorResult = {};
|
||||||
|
// 提取 getPrimary 调用中的颜色名称
|
||||||
|
const primaryMatch = config.match(/getPrimary\(["']([^"']+)["']\)/);
|
||||||
|
const primaryColorName = primaryMatch?.[1];
|
||||||
|
// 提取 getSurface 调用中的颜色名称
|
||||||
|
const surfaceMatch = config.match(/getSurface\(["']([^"']+)["']\)/);
|
||||||
|
const surfaceColorName = surfaceMatch?.[1];
|
||||||
|
if (primaryColorName) {
|
||||||
|
// 提取 PRIMARY_COLOR_PALETTES 中对应的调色板
|
||||||
|
const primaryPaletteMatch = config.match(new RegExp(`{\\s*name:\\s*["']${primaryColorName}["'],\\s*palette:\\s*({[^}]+})`, "s"));
|
||||||
|
if (primaryPaletteMatch) {
|
||||||
|
// 解析调色板对象
|
||||||
|
const paletteStr = primaryPaletteMatch[1];
|
||||||
|
const paletteEntries = paletteStr.match(/(\d+):\s*["']([^"']+)["']/g);
|
||||||
|
if (paletteEntries) {
|
||||||
|
paletteEntries.forEach((entry) => {
|
||||||
|
const match = entry.match(/(\d+):\s*["']([^"']+)["']/);
|
||||||
|
if (match) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
colorResult[`primary-${key}`] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (surfaceColorName) {
|
||||||
|
// 提取 SURFACE_PALETTES 中对应的调色板
|
||||||
|
const surfacePaletteMatch = config.match(new RegExp(`{\\s*name:\\s*["']${surfaceColorName}["'],\\s*palette:\\s*({[^}]+})`, "s"));
|
||||||
|
if (surfacePaletteMatch) {
|
||||||
|
// 解析调色板对象
|
||||||
|
const paletteStr = surfacePaletteMatch[1];
|
||||||
|
const paletteEntries = paletteStr.match(/(\d+):\s*["']([^"']+)["']/g);
|
||||||
|
if (paletteEntries) {
|
||||||
|
paletteEntries.forEach((entry) => {
|
||||||
|
const match = entry.match(/(\d+):\s*["']([^"']+)["']/);
|
||||||
|
if (match) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
// 0 对应 surface,其他对应 surface-*
|
||||||
|
const colorKey = key === "0" ? "surface" : `surface-${key}`;
|
||||||
|
colorResult[colorKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return colorResult;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取版本号
|
||||||
|
function getVersion() {
|
||||||
|
const pkg = readFile(rootDir("package.json"), true);
|
||||||
|
return pkg?.version || "0.0.0";
|
||||||
|
}
|
||||||
function codePlugin() {
|
function codePlugin() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -2184,25 +2344,34 @@ if (typeof window !== 'undefined') {
|
|||||||
async transform(code, id) {
|
async transform(code, id) {
|
||||||
if (id.includes("/cool/ctx/index.ts")) {
|
if (id.includes("/cool/ctx/index.ts")) {
|
||||||
const ctx = await createCtx();
|
const ctx = await createCtx();
|
||||||
const theme = await readFile(rootDir("theme.json"), true);
|
// 版本
|
||||||
|
const version = getVersion();
|
||||||
|
// 主题配置
|
||||||
|
const theme = readFile(rootDir("theme.json"), true);
|
||||||
|
// 主题配置
|
||||||
|
ctx["theme"] = theme;
|
||||||
|
if (compareVersion(version, "8.0.2") >= 0) {
|
||||||
|
// 颜色值
|
||||||
|
ctx["color"] = getTailwindColor();
|
||||||
|
}
|
||||||
|
// 安全字符映射
|
||||||
ctx["SAFE_CHAR_MAP_LOCALE"] = [];
|
ctx["SAFE_CHAR_MAP_LOCALE"] = [];
|
||||||
for (const i in SAFE_CHAR_MAP_LOCALE) {
|
for (const i in SAFE_CHAR_MAP_LOCALE) {
|
||||||
ctx["SAFE_CHAR_MAP_LOCALE"].push([i, SAFE_CHAR_MAP_LOCALE[i]]);
|
ctx["SAFE_CHAR_MAP_LOCALE"].push([i, SAFE_CHAR_MAP_LOCALE[i]]);
|
||||||
}
|
}
|
||||||
ctx["theme"] = theme;
|
|
||||||
code = code.replace("const ctx = {}", `const ctx = ${JSON.stringify(ctx, null, 4)}`);
|
code = code.replace("const ctx = {}", `const ctx = ${JSON.stringify(ctx, null, 4)}`);
|
||||||
}
|
}
|
||||||
if (id.includes("/cool/service/index.ts")) {
|
// if (id.includes("/cool/service/index.ts")) {
|
||||||
const eps = await createEps();
|
// const eps = await createEps();
|
||||||
if (eps.serviceCode) {
|
// if (eps.serviceCode) {
|
||||||
const { content, types } = eps.serviceCode;
|
// const { content, types } = eps.serviceCode;
|
||||||
const typeCode = `import type { ${lodash.uniq(types).join(", ")} } from '../types';`;
|
// const typeCode = `import type { ${uniq(types).join(", ")} } from '../types';`;
|
||||||
code =
|
// code =
|
||||||
typeCode +
|
// typeCode +
|
||||||
"\n\n" +
|
// "\n\n" +
|
||||||
code.replace("const service = {}", `const service = ${content}`);
|
// code.replace("const service = {}", `const service = ${content}`);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (id.endsWith(".json")) {
|
if (id.endsWith(".json")) {
|
||||||
const d = JSON.parse(code);
|
const d = JSON.parse(code);
|
||||||
for (let i in d) {
|
for (let i in d) {
|
||||||
@ -2215,7 +2384,17 @@ if (typeof window !== 'undefined') {
|
|||||||
delete d[i];
|
delete d[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code = JSON.stringify(d);
|
// 转字符串,不然会报错:Method too large
|
||||||
|
if (id.includes("/locale/")) {
|
||||||
|
let t = [];
|
||||||
|
d.forEach(([a, b]) => {
|
||||||
|
t.push(`${a}<__=__>${b}`);
|
||||||
|
});
|
||||||
|
code = JSON.stringify([[t.join("<__&__>")]]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
code = JSON.stringify(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
@ -2258,6 +2437,14 @@ if (typeof window !== 'undefined') {
|
|||||||
config.type = options.type;
|
config.type = options.type;
|
||||||
// 请求地址
|
// 请求地址
|
||||||
config.reqUrl = getProxyTarget(options.proxy);
|
config.reqUrl = getProxyTarget(options.proxy);
|
||||||
|
if (config.type == "uniapp-x") {
|
||||||
|
// 是否纯净版
|
||||||
|
config.clean = options.clean ?? true;
|
||||||
|
if (config.clean) {
|
||||||
|
// 默认设置为测试地址
|
||||||
|
config.reqUrl = "https://show.cool-admin.com/api";
|
||||||
|
}
|
||||||
|
}
|
||||||
// 是否开启名称标签
|
// 是否开启名称标签
|
||||||
config.nameTag = options.nameTag ?? true;
|
config.nameTag = options.nameTag ?? true;
|
||||||
// svg
|
// svg
|
||||||
@ -2282,6 +2469,10 @@ if (typeof window !== 'undefined') {
|
|||||||
lodash.merge(config.eps.mapping, mapping);
|
lodash.merge(config.eps.mapping, mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 如果类型为 uniapp-x,则关闭 eps
|
||||||
|
if (config.type == "uniapp-x") {
|
||||||
|
config.eps.enable = false;
|
||||||
|
}
|
||||||
// tailwind
|
// tailwind
|
||||||
if (options.tailwind) {
|
if (options.tailwind) {
|
||||||
lodash.assign(config.tailwind, options.tailwind);
|
lodash.assign(config.tailwind, options.tailwind);
|
||||||
|
|||||||
7
packages/vite-plugin/dist/utils/index.d.ts
vendored
7
packages/vite-plugin/dist/utils/index.d.ts
vendored
@ -9,3 +9,10 @@ export declare function parseJson(req: any): Promise<any>;
|
|||||||
export declare function formatContent(content: string, options?: prettier.Options): Promise<string>;
|
export declare function formatContent(content: string, options?: prettier.Options): Promise<string>;
|
||||||
export declare function error(message: string): void;
|
export declare function error(message: string): void;
|
||||||
export declare function success(message: string): void;
|
export declare function success(message: string): void;
|
||||||
|
/**
|
||||||
|
* 比较两个版本号
|
||||||
|
* @param version1 版本号1 (如: "1.2.3")
|
||||||
|
* @param version2 版本号2 (如: "1.2.4")
|
||||||
|
* @returns 1: version1 > version2, 0: 相等, -1: version1 < version2
|
||||||
|
*/
|
||||||
|
export declare function compareVersion(version1: string, version2: string): number;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cool-vue/vite-plugin",
|
"name": "@cool-vue/vite-plugin",
|
||||||
"version": "8.2.3",
|
"version": "8.2.10",
|
||||||
"description": "cool-admin、cool-uni builder",
|
"description": "cool-admin、cool-uni builder",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"main": "/dist/index.js",
|
"main": "/dist/index.js",
|
||||||
|
|||||||
@ -53,4 +53,5 @@ export const config = {
|
|||||||
rpxRatio: 2,
|
rpxRatio: 2,
|
||||||
darkTextClass: "dark:text-surface-50",
|
darkTextClass: "dark:text-surface-50",
|
||||||
},
|
},
|
||||||
|
clean: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -130,10 +130,9 @@ async function formatCode(text: string): Promise<string | null> {
|
|||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
trailingComma: "none",
|
trailingComma: "none",
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
error(
|
console.log(err);
|
||||||
`[cool-eps] Failed to format /build/cool/eps.d.ts. Please delete the file and try again`,
|
error(`[cool-eps] File format error, please try again`);
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -182,7 +181,7 @@ async function getData() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (config.type == "uniapp-x" || config.type == "app") {
|
if (config.type == "uniapp-x" || config.type == "app") {
|
||||||
list = list.filter((e) => e.prefix.startsWith("/app"));
|
list = list.filter((e) => e.prefix.startsWith("/app") || e.prefix.startsWith("/admin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,24 +190,26 @@ async function getData() {
|
|||||||
* @returns {boolean} 是否有更新
|
* @returns {boolean} 是否有更新
|
||||||
*/
|
*/
|
||||||
function createJson(): boolean {
|
function createJson(): boolean {
|
||||||
if (config.type == "uniapp-x") {
|
let data: any[] = [];
|
||||||
return false;
|
|
||||||
|
if (config.type != "uniapp-x") {
|
||||||
|
data = list.map((e) => {
|
||||||
|
return {
|
||||||
|
prefix: e.prefix,
|
||||||
|
name: e.name || "",
|
||||||
|
api: e.api.map((apiItem) => ({
|
||||||
|
name: apiItem.name,
|
||||||
|
method: apiItem.method,
|
||||||
|
path: apiItem.path,
|
||||||
|
})),
|
||||||
|
search: e.search,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
const arr = list.map((e) => {
|
const content = JSON.stringify(data);
|
||||||
return {
|
|
||||||
prefix: e.prefix,
|
|
||||||
name: e.name || "",
|
|
||||||
api: e.api.map((apiItem) => ({
|
|
||||||
name: apiItem.name,
|
|
||||||
method: apiItem.method,
|
|
||||||
path: apiItem.path,
|
|
||||||
})),
|
|
||||||
search: e.search,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const content = JSON.stringify(arr);
|
|
||||||
const local_content = readFile(getEpsPath("eps.json"));
|
const local_content = readFile(getEpsPath("eps.json"));
|
||||||
|
|
||||||
// 判断是否需要更新
|
// 判断是否需要更新
|
||||||
@ -238,6 +239,10 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
|
|||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
if (!checkName(item.name)) continue;
|
if (!checkName(item.name)) continue;
|
||||||
|
|
||||||
|
if (formatName(item.name) == "BusinessInterface") {
|
||||||
|
console.log(111);
|
||||||
|
}
|
||||||
|
|
||||||
let t = `interface ${formatName(item.name)} {`;
|
let t = `interface ${formatName(item.name)} {`;
|
||||||
|
|
||||||
// 合并 columns 和 pageColumns,去重
|
// 合并 columns 和 pageColumns,去重
|
||||||
@ -377,12 +382,21 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 方法描述
|
// 方法描述
|
||||||
t += `
|
if (config.type == "uniapp-x") {
|
||||||
/**
|
t += `
|
||||||
* ${a.summary || n}
|
/**
|
||||||
*/
|
* ${a.summary || n}
|
||||||
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
|
*/
|
||||||
`;
|
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<any>;
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
t += `
|
||||||
|
/**
|
||||||
|
* ${a.summary || n}
|
||||||
|
*/
|
||||||
|
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!permission.includes(n)) {
|
if (!permission.includes(n)) {
|
||||||
permission.push(n);
|
permission.push(n);
|
||||||
@ -431,6 +445,8 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
|
|||||||
return `
|
return `
|
||||||
type json = any;
|
type json = any;
|
||||||
|
|
||||||
|
${await createDict()}
|
||||||
|
|
||||||
interface PagePagination {
|
interface PagePagination {
|
||||||
size: number;
|
size: number;
|
||||||
page: number;
|
page: number;
|
||||||
@ -460,8 +476,6 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
|
|||||||
|
|
||||||
${noUniappX("type Request = (options: RequestOptions) => Promise<any>;")}
|
${noUniappX("type Request = (options: RequestOptions) => Promise<any>;")}
|
||||||
|
|
||||||
${await createDict()}
|
|
||||||
|
|
||||||
type Service = {
|
type Service = {
|
||||||
${noUniappX("request: Request;")}
|
${noUniappX("request: Request;")}
|
||||||
|
|
||||||
@ -656,35 +670,13 @@ function createServiceCode(): { content: string; types: string[] } {
|
|||||||
types.push(item.name);
|
types.push(item.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回类型
|
|
||||||
let res = "";
|
|
||||||
|
|
||||||
// 实体名
|
|
||||||
const en = item.name || "any";
|
|
||||||
|
|
||||||
switch (a.path) {
|
|
||||||
case "/page":
|
|
||||||
res = `${name}PageResponse`;
|
|
||||||
types.push(res);
|
|
||||||
break;
|
|
||||||
case "/list":
|
|
||||||
res = `${en}[]`;
|
|
||||||
break;
|
|
||||||
case "/info":
|
|
||||||
res = en;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res = "any";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法描述
|
// 方法描述
|
||||||
t += `
|
t += `
|
||||||
/**
|
/**
|
||||||
* ${a.summary || n}
|
* ${a.summary || n}
|
||||||
*/
|
*/
|
||||||
${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")})${noUniappX(`: Promise<${res}>`)} {
|
${n}(data?: any): Promise<any> {
|
||||||
return request<${res}>({
|
return request({
|
||||||
url: "/${d[i].namespace}${a.path}",
|
url: "/${d[i].namespace}${a.path}",
|
||||||
method: "${(a.method || "get").toLocaleUpperCase()}",
|
method: "${(a.method || "get").toLocaleUpperCase()}",
|
||||||
data,
|
data,
|
||||||
|
|||||||
@ -14,6 +14,16 @@ export function cool(options: Config.Options) {
|
|||||||
// 请求地址
|
// 请求地址
|
||||||
config.reqUrl = getProxyTarget(options.proxy);
|
config.reqUrl = getProxyTarget(options.proxy);
|
||||||
|
|
||||||
|
if (config.type == "uniapp-x") {
|
||||||
|
// 是否纯净版
|
||||||
|
config.clean = options.clean ?? true;
|
||||||
|
|
||||||
|
if (config.clean) {
|
||||||
|
// 默认设置为测试地址
|
||||||
|
config.reqUrl = "https://show.cool-admin.com/api";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 是否开启名称标签
|
// 是否开启名称标签
|
||||||
config.nameTag = options.nameTag ?? true;
|
config.nameTag = options.nameTag ?? true;
|
||||||
|
|
||||||
@ -45,6 +55,11 @@ export function cool(options: Config.Options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果类型为 uniapp-x,则关闭 eps
|
||||||
|
if (config.type == "uniapp-x") {
|
||||||
|
config.eps.enable = false;
|
||||||
|
}
|
||||||
|
|
||||||
// tailwind
|
// tailwind
|
||||||
if (options.tailwind) {
|
if (options.tailwind) {
|
||||||
assign(config.tailwind, options.tailwind);
|
assign(config.tailwind, options.tailwind);
|
||||||
|
|||||||
@ -1,10 +1,96 @@
|
|||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import { SAFE_CHAR_MAP_LOCALE } from "./config";
|
import { SAFE_CHAR_MAP_LOCALE } from "./config";
|
||||||
import { createCtx } from "../ctx";
|
import { createCtx } from "../ctx";
|
||||||
import { readFile, rootDir } from "../utils";
|
import { compareVersion, readFile, rootDir } from "../utils";
|
||||||
import { createEps } from "../eps";
|
import { createEps } from "../eps";
|
||||||
import { uniq } from "lodash";
|
import { uniq } from "lodash";
|
||||||
|
|
||||||
|
// 获取 tailwind.config.ts 中的颜色
|
||||||
|
function getTailwindColor() {
|
||||||
|
const config = readFile(rootDir("tailwind.config.ts"));
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从配置文件中动态提取主色和表面色
|
||||||
|
const colorResult: Record<string, string> = {};
|
||||||
|
|
||||||
|
// 提取 getPrimary 调用中的颜色名称
|
||||||
|
const primaryMatch = config.match(/getPrimary\(["']([^"']+)["']\)/);
|
||||||
|
const primaryColorName = primaryMatch?.[1];
|
||||||
|
|
||||||
|
// 提取 getSurface 调用中的颜色名称
|
||||||
|
const surfaceMatch = config.match(/getSurface\(["']([^"']+)["']\)/);
|
||||||
|
const surfaceColorName = surfaceMatch?.[1];
|
||||||
|
|
||||||
|
if (primaryColorName) {
|
||||||
|
// 提取 PRIMARY_COLOR_PALETTES 中对应的调色板
|
||||||
|
const primaryPaletteMatch = config.match(
|
||||||
|
new RegExp(
|
||||||
|
`{\\s*name:\\s*["']${primaryColorName}["'],\\s*palette:\\s*({[^}]+})`,
|
||||||
|
"s",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (primaryPaletteMatch) {
|
||||||
|
// 解析调色板对象
|
||||||
|
const paletteStr = primaryPaletteMatch[1];
|
||||||
|
const paletteEntries = paletteStr.match(/(\d+):\s*["']([^"']+)["']/g);
|
||||||
|
|
||||||
|
if (paletteEntries) {
|
||||||
|
paletteEntries.forEach((entry: string) => {
|
||||||
|
const match = entry.match(/(\d+):\s*["']([^"']+)["']/);
|
||||||
|
if (match) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
colorResult[`primary-${key}`] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surfaceColorName) {
|
||||||
|
// 提取 SURFACE_PALETTES 中对应的调色板
|
||||||
|
const surfacePaletteMatch = config.match(
|
||||||
|
new RegExp(
|
||||||
|
`{\\s*name:\\s*["']${surfaceColorName}["'],\\s*palette:\\s*({[^}]+})`,
|
||||||
|
"s",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (surfacePaletteMatch) {
|
||||||
|
// 解析调色板对象
|
||||||
|
const paletteStr = surfacePaletteMatch[1];
|
||||||
|
const paletteEntries = paletteStr.match(/(\d+):\s*["']([^"']+)["']/g);
|
||||||
|
|
||||||
|
if (paletteEntries) {
|
||||||
|
paletteEntries.forEach((entry: string) => {
|
||||||
|
const match = entry.match(/(\d+):\s*["']([^"']+)["']/);
|
||||||
|
if (match) {
|
||||||
|
const [, key, value] = match;
|
||||||
|
// 0 对应 surface,其他对应 surface-*
|
||||||
|
const colorKey = key === "0" ? "surface" : `surface-${key}`;
|
||||||
|
colorResult[colorKey] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorResult;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取版本号
|
||||||
|
function getVersion() {
|
||||||
|
const pkg = readFile(rootDir("package.json"), true);
|
||||||
|
return pkg?.version || "0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
export function codePlugin(): Plugin[] {
|
export function codePlugin(): Plugin[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -13,34 +99,46 @@ export function codePlugin(): Plugin[] {
|
|||||||
async transform(code, id) {
|
async transform(code, id) {
|
||||||
if (id.includes("/cool/ctx/index.ts")) {
|
if (id.includes("/cool/ctx/index.ts")) {
|
||||||
const ctx = await createCtx();
|
const ctx = await createCtx();
|
||||||
const theme = await readFile(rootDir("theme.json"), true);
|
|
||||||
|
|
||||||
|
// 版本
|
||||||
|
const version = getVersion();
|
||||||
|
|
||||||
|
// 主题配置
|
||||||
|
const theme = readFile(rootDir("theme.json"), true);
|
||||||
|
|
||||||
|
// 主题配置
|
||||||
|
ctx["theme"] = theme;
|
||||||
|
|
||||||
|
if (compareVersion(version, "8.0.2") >= 0) {
|
||||||
|
// 颜色值
|
||||||
|
ctx["color"] = getTailwindColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全字符映射
|
||||||
ctx["SAFE_CHAR_MAP_LOCALE"] = [];
|
ctx["SAFE_CHAR_MAP_LOCALE"] = [];
|
||||||
for (const i in SAFE_CHAR_MAP_LOCALE) {
|
for (const i in SAFE_CHAR_MAP_LOCALE) {
|
||||||
ctx["SAFE_CHAR_MAP_LOCALE"].push([i, SAFE_CHAR_MAP_LOCALE[i]]);
|
ctx["SAFE_CHAR_MAP_LOCALE"].push([i, SAFE_CHAR_MAP_LOCALE[i]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx["theme"] = theme;
|
|
||||||
|
|
||||||
code = code.replace(
|
code = code.replace(
|
||||||
"const ctx = {}",
|
"const ctx = {}",
|
||||||
`const ctx = ${JSON.stringify(ctx, null, 4)}`,
|
`const ctx = ${JSON.stringify(ctx, null, 4)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.includes("/cool/service/index.ts")) {
|
// if (id.includes("/cool/service/index.ts")) {
|
||||||
const eps = await createEps();
|
// const eps = await createEps();
|
||||||
|
|
||||||
if (eps.serviceCode) {
|
// if (eps.serviceCode) {
|
||||||
const { content, types } = eps.serviceCode;
|
// const { content, types } = eps.serviceCode;
|
||||||
const typeCode = `import type { ${uniq(types).join(", ")} } from '../types';`;
|
// const typeCode = `import type { ${uniq(types).join(", ")} } from '../types';`;
|
||||||
|
|
||||||
code =
|
// code =
|
||||||
typeCode +
|
// typeCode +
|
||||||
"\n\n" +
|
// "\n\n" +
|
||||||
code.replace("const service = {}", `const service = ${content}`);
|
// code.replace("const service = {}", `const service = ${content}`);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (id.endsWith(".json")) {
|
if (id.endsWith(".json")) {
|
||||||
const d = JSON.parse(code);
|
const d = JSON.parse(code);
|
||||||
@ -58,7 +156,18 @@ export function codePlugin(): Plugin[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code = JSON.stringify(d);
|
// 转字符串,不然会报错:Method too large
|
||||||
|
if (id.includes("/locale/")) {
|
||||||
|
let t: string[] = [];
|
||||||
|
|
||||||
|
(d as string[][]).forEach(([a, b]) => {
|
||||||
|
t.push(`${a}<__=__>${b}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
code = JSON.stringify([[t.join("<__&__>")]]);
|
||||||
|
} else {
|
||||||
|
code = JSON.stringify(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -27,18 +27,31 @@ export function flatten(template: string): string {
|
|||||||
let header = template.substring(0, startIndex);
|
let header = template.substring(0, startIndex);
|
||||||
|
|
||||||
// 获取 Service 类型定义及其内容,去除换行和制表符
|
// 获取 Service 类型定义及其内容,去除换行和制表符
|
||||||
const serviceContent = template.substring(startIndex).replace(/\n|\t/g, "");
|
const serviceTemplateContent = template.substring(startIndex).replace(/\n|\t/g, "");
|
||||||
|
|
||||||
|
// 找到 Service 的内容部分
|
||||||
|
const serviceStartIndex = serviceTemplateContent.indexOf("{") + 1;
|
||||||
|
const serviceEndIndex = findClosingBrace(serviceTemplateContent, serviceStartIndex);
|
||||||
|
const serviceInnerContent = serviceTemplateContent
|
||||||
|
.substring(serviceStartIndex, serviceEndIndex)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 存储所有接口定义
|
||||||
|
const allInterfaces = new Map<string, string>();
|
||||||
|
|
||||||
|
// 处理 Service 内容,保持原有结构但替换嵌套对象为接口引用
|
||||||
|
const serviceContent = buildCurrentLevelContent(serviceInnerContent);
|
||||||
|
|
||||||
|
// 递归收集所有需要生成的接口
|
||||||
|
flattenContent(serviceInnerContent, allInterfaces, []);
|
||||||
|
|
||||||
|
// 生成所有接口定义
|
||||||
let interfaces = "";
|
let interfaces = "";
|
||||||
let serviceFields = "";
|
allInterfaces.forEach((content, key) => {
|
||||||
|
interfaces += `\nexport interface ${firstUpperCase(key)}Interface { ${content} }\n`;
|
||||||
// 解析内容并生成接口定义
|
|
||||||
parse(serviceContent).forEach(({ key, content, level }) => {
|
|
||||||
interfaces += `\nexport interface ${firstUpperCase(key)}Interface {${content}}\n`;
|
|
||||||
serviceFields += `${key}: ${firstUpperCase(key)}Interface;`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return `${header}${interfaces}\nexport type Service = {${serviceFields}}`;
|
return `${header}${interfaces}\nexport type Service = { ${serviceContent} }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,45 +79,64 @@ function findClosingBrace(str: string, startIndex: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析内容中的嵌套结构
|
* 递归收集所有需要生成的接口
|
||||||
* @param content - 要解析的内容字符串
|
* @param content - 要处理的内容
|
||||||
* @returns 解析结果数组,包含解析出的键值对
|
* @param allInterfaces - 存储所有接口定义的 Map
|
||||||
|
* @param parentFields - 父级字段数组(暂未使用)
|
||||||
*/
|
*/
|
||||||
function parse(content: string, level: number = 0): ParseResult[] {
|
function flattenContent(
|
||||||
// 匹配形如 xxx: { ... } 的结构
|
content: string,
|
||||||
|
allInterfaces: Map<string, string>,
|
||||||
|
parentFields: string[],
|
||||||
|
): void {
|
||||||
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
||||||
const result: ParseResult[] = [];
|
|
||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
while ((match = interfacePattern.exec(content)) !== null) {
|
while ((match = interfacePattern.exec(content)) !== null) {
|
||||||
|
const key = match[1];
|
||||||
const startIndex = match.index + match[0].length;
|
const startIndex = match.index + match[0].length;
|
||||||
const endIndex = findClosingBrace(content, startIndex);
|
const endIndex = findClosingBrace(content, startIndex);
|
||||||
|
|
||||||
if (endIndex > startIndex) {
|
if (endIndex > startIndex) {
|
||||||
let parsedContent = content.substring(startIndex, endIndex).trim();
|
const innerContent = content.substring(startIndex, endIndex).trim();
|
||||||
|
|
||||||
// 处理嵌套结构
|
// 构建当前接口的内容,将嵌套对象替换为接口引用
|
||||||
if (parsedContent.includes("{") && parsedContent.includes("}")) {
|
const currentLevelContent = buildCurrentLevelContent(innerContent);
|
||||||
const nestedInterfaces = parse(parsedContent, level + 1);
|
allInterfaces.set(key, currentLevelContent);
|
||||||
|
|
||||||
// 替换嵌套的内容为接口引用
|
// 递归处理嵌套内容
|
||||||
if (nestedInterfaces.length > 0) {
|
flattenContent(innerContent, allInterfaces, []);
|
||||||
nestedInterfaces.forEach((nestedInterface) => {
|
}
|
||||||
const pattern = `${nestedInterface.key}: {${nestedInterface.content}};`;
|
}
|
||||||
const replacement = `${nestedInterface.key}: ${firstUpperCase(nestedInterface.key)}Interface`;
|
}
|
||||||
parsedContent = parsedContent.replace(pattern, replacement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将解析结果添加到数组开头
|
/**
|
||||||
result.unshift({
|
* 构建当前级别的内容,将嵌套对象替换为接口引用
|
||||||
key: match[1],
|
* @param content - 内容字符串
|
||||||
level,
|
* @returns 处理后的内容
|
||||||
content: parsedContent,
|
*/
|
||||||
});
|
function buildCurrentLevelContent(content: string): string {
|
||||||
|
const interfacePattern = /(\w+)\s*:\s*\{/g;
|
||||||
|
let result = content;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
// 重置正则表达式的 lastIndex
|
||||||
|
interfacePattern.lastIndex = 0;
|
||||||
|
|
||||||
|
while ((match = interfacePattern.exec(content)) !== null) {
|
||||||
|
const key = match[1];
|
||||||
|
const startIndex = match.index + match[0].length;
|
||||||
|
const endIndex = findClosingBrace(content, startIndex);
|
||||||
|
|
||||||
|
if (endIndex > startIndex) {
|
||||||
|
const fullMatch = content.substring(match.index, endIndex + 1);
|
||||||
|
const replacement = `${key}: ${firstUpperCase(key)}Interface;`;
|
||||||
|
result = result.replace(fullMatch, replacement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清理多余的分号和空格
|
||||||
|
result = result.replace(/;+/g, ";").replace(/\s+/g, " ").trim();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,9 +94,6 @@ function postcssPlugin(): Plugin {
|
|||||||
{
|
{
|
||||||
postcssPlugin: "vite-cool-uniappx-class-mapping",
|
postcssPlugin: "vite-cool-uniappx-class-mapping",
|
||||||
prepare() {
|
prepare() {
|
||||||
// 存储 Tailwind 颜色值
|
|
||||||
const colorValues: Record<string, string> = {};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 处理选择器规则
|
// 处理选择器规则
|
||||||
Rule(rule: any) {
|
Rule(rule: any) {
|
||||||
|
|||||||
@ -40,14 +40,70 @@ export function createDir(path: string, recursive?: boolean) {
|
|||||||
export function readFile(path: string, json?: boolean) {
|
export function readFile(path: string, json?: boolean) {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(path, "utf8");
|
const content = fs.readFileSync(path, "utf8");
|
||||||
return json
|
return json ? JSON.parse(removeJsonComments(content)) : content;
|
||||||
? JSON.parse(content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""))
|
|
||||||
: content;
|
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 安全地移除JSON中的注释
|
||||||
|
function removeJsonComments(content: string): string {
|
||||||
|
let result = "";
|
||||||
|
let inString = false;
|
||||||
|
let stringChar = "";
|
||||||
|
let escaped = false;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < content.length) {
|
||||||
|
const char = content[i];
|
||||||
|
const nextChar = content[i + 1];
|
||||||
|
|
||||||
|
// 处理字符串状态
|
||||||
|
if (!inString && (char === '"' || char === "'")) {
|
||||||
|
inString = true;
|
||||||
|
stringChar = char;
|
||||||
|
result += char;
|
||||||
|
} else if (inString && char === stringChar && !escaped) {
|
||||||
|
inString = false;
|
||||||
|
stringChar = "";
|
||||||
|
result += char;
|
||||||
|
} else if (inString) {
|
||||||
|
// 在字符串内,直接添加字符
|
||||||
|
result += char;
|
||||||
|
escaped = char === "\\" && !escaped;
|
||||||
|
} else {
|
||||||
|
// 不在字符串内,检查注释
|
||||||
|
if (char === "/" && nextChar === "/") {
|
||||||
|
// 单行注释,跳过到行尾
|
||||||
|
while (i < content.length && content[i] !== "\n") {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i < content.length) {
|
||||||
|
result += content[i]; // 保留换行符
|
||||||
|
}
|
||||||
|
} else if (char === "/" && nextChar === "*") {
|
||||||
|
// 多行注释,跳过到 */
|
||||||
|
i += 2;
|
||||||
|
while (i < content.length - 1) {
|
||||||
|
if (content[i] === "*" && content[i + 1] === "/") {
|
||||||
|
i += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
result += char;
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// 写入文件
|
// 写入文件
|
||||||
export function writeFile(path: string, data: any) {
|
export function writeFile(path: string, data: any) {
|
||||||
try {
|
try {
|
||||||
@ -93,3 +149,26 @@ export function error(message: string) {
|
|||||||
export function success(message: string) {
|
export function success(message: string) {
|
||||||
console.log("\x1B[32m%s\x1B[0m", message);
|
console.log("\x1B[32m%s\x1B[0m", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个版本号
|
||||||
|
* @param version1 版本号1 (如: "1.2.3")
|
||||||
|
* @param version2 版本号2 (如: "1.2.4")
|
||||||
|
* @returns 1: version1 > version2, 0: 相等, -1: version1 < version2
|
||||||
|
*/
|
||||||
|
export function compareVersion(version1: string, version2: string): number {
|
||||||
|
const v1Parts = version1.split(".").map(Number);
|
||||||
|
const v2Parts = version2.split(".").map(Number);
|
||||||
|
|
||||||
|
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const v1Part = v1Parts[i] || 0;
|
||||||
|
const v2Part = v2Parts[i] || 0;
|
||||||
|
|
||||||
|
if (v1Part > v2Part) return 1;
|
||||||
|
if (v1Part < v2Part) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
2
packages/vite-plugin/types/index.d.ts
vendored
2
packages/vite-plugin/types/index.d.ts
vendored
@ -119,5 +119,7 @@ export declare namespace Config {
|
|||||||
// 暗黑模式文本类名
|
// 暗黑模式文本类名
|
||||||
darkTextClass?: string;
|
darkTextClass?: string;
|
||||||
};
|
};
|
||||||
|
// 是否纯净版
|
||||||
|
clean?: boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,642 +1,428 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cl-upload__wrap" :class="[customClass]">
|
<el-button :icon="icon" :disabled="disabled" :type="type" @click="open">
|
||||||
<div
|
{{ $t('导入') }}
|
||||||
class="cl-upload"
|
</el-button>
|
||||||
:class="[
|
|
||||||
`cl-upload--${type}`,
|
<cl-form ref="Form">
|
||||||
{
|
<template #slot-upload>
|
||||||
'is-disabled': disabled,
|
<div v-if="!upload.filename" class="upload">
|
||||||
'is-multiple': multiple,
|
<div class="tips" v-if="template">
|
||||||
'is-small': small
|
<span>{{ tips }}</span>
|
||||||
}
|
<el-button type="primary" text bg @click="download">{{
|
||||||
]"
|
$t('下载模版')
|
||||||
>
|
}}</el-button>
|
||||||
<template v-if="!drag">
|
|
||||||
<div v-if="type == 'file'" class="cl-upload__file-btn">
|
|
||||||
<el-upload
|
|
||||||
:ref="setRefs('upload')"
|
|
||||||
:drag="drag"
|
|
||||||
action=""
|
|
||||||
:accept="accept"
|
|
||||||
:show-file-list="false"
|
|
||||||
:before-upload="onBeforeUpload"
|
|
||||||
:http-request="httpRequest"
|
|
||||||
:headers="headers"
|
|
||||||
:multiple="multiple"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
<el-button type="success">{{ text }}</el-button>
|
|
||||||
</slot>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 列表 -->
|
<div class="inner">
|
||||||
<vue-draggable
|
<cl-upload
|
||||||
v-if="showList"
|
:ref="setRefs('upload')"
|
||||||
v-model="list"
|
drag
|
||||||
class="cl-upload__list"
|
:limit-size="limitSize"
|
||||||
tag="div"
|
|
||||||
ghost-class="Ghost"
|
|
||||||
drag-class="Drag"
|
|
||||||
item-key="uid"
|
|
||||||
:disabled="!draggable"
|
|
||||||
@end="update"
|
|
||||||
>
|
|
||||||
<!-- 触发器 -->
|
|
||||||
<template #footer>
|
|
||||||
<div v-if="(type == 'image' || drag) && isAdd" class="cl-upload__footer">
|
|
||||||
<el-upload
|
|
||||||
:ref="setRefs('upload')"
|
|
||||||
action=""
|
|
||||||
:drag="drag"
|
|
||||||
:accept="accept"
|
|
||||||
:show-file-list="false"
|
|
||||||
:before-upload="onBeforeUpload"
|
|
||||||
:http-request="httpRequest"
|
|
||||||
:headers="headers"
|
|
||||||
:multiple="multiple"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<slot>
|
|
||||||
<!-- 拖拽方式 -->
|
|
||||||
<div v-if="drag" class="cl-upload__demo is-dragger">
|
|
||||||
<el-icon :size="46">
|
|
||||||
<upload-filled />
|
|
||||||
</el-icon>
|
|
||||||
<div>
|
|
||||||
{{
|
|
||||||
t('点击上传或将文件拖动到此处,文件大小限制{n}M', {
|
|
||||||
n: limitSize
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 点击方式 -->
|
|
||||||
<div v-else class="cl-upload__demo">
|
|
||||||
<el-icon :size="36">
|
|
||||||
<component :is="icon" v-if="icon" />
|
|
||||||
<picture-filled v-else />
|
|
||||||
</el-icon>
|
|
||||||
<span v-if="text" class="text">{{ text }}</span>
|
|
||||||
</div>
|
|
||||||
</slot>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<template #item="{ element: item, index }">
|
|
||||||
<el-upload
|
|
||||||
action=""
|
|
||||||
:accept="accept"
|
:accept="accept"
|
||||||
:show-file-list="false"
|
|
||||||
:http-request="
|
|
||||||
req => {
|
|
||||||
return httpRequest(req, item);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:before-upload="
|
|
||||||
file => {
|
|
||||||
onBeforeUpload(file, item);
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:headers="headers"
|
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
:auto-upload="false"
|
||||||
<slot name="item" :item="item" :index="index">
|
:before-upload="onUpload"
|
||||||
<div class="cl-upload__item">
|
:size="[220, '100%']"
|
||||||
<upload-item
|
/>
|
||||||
:show-tag="showTag"
|
</div>
|
||||||
:item="item"
|
</div>
|
||||||
:list="list"
|
</template>
|
||||||
:disabled="disabled"
|
|
||||||
:deletable="deletable"
|
|
||||||
@remove="remove(index)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 小图模式 -->
|
<template #slot-list>
|
||||||
<el-icon
|
<div v-if="list.length" class="data-table">
|
||||||
v-if="small"
|
<div class="head">
|
||||||
class="cl-upload__item-remove"
|
<el-button type="success" @click="clear">{{ $t('重新上传') }}</el-button>
|
||||||
@click.stop="remove(index)"
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:disabled="table.selection.length == 0"
|
||||||
|
@click="table.del()"
|
||||||
|
>
|
||||||
|
{{ $t('批量删除') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cl-table">
|
||||||
|
<el-table
|
||||||
|
border
|
||||||
|
:data="list"
|
||||||
|
max-height="600px"
|
||||||
|
@selection-change="table.onSelectionChange"
|
||||||
|
@row-click="
|
||||||
|
row => {
|
||||||
|
row._edit = true;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
type="selection"
|
||||||
|
width="60px"
|
||||||
|
align="center"
|
||||||
|
fixed="left"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('序号')"
|
||||||
|
type="index"
|
||||||
|
width="80px"
|
||||||
|
align="center"
|
||||||
|
fixed="left"
|
||||||
|
:index="table.onIndex"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
v-for="item in table.header"
|
||||||
|
:key="item"
|
||||||
|
:prop="item"
|
||||||
|
:label="item"
|
||||||
|
min-width="160px"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="!scope.row._edit">{{ scope.row[item] }}</span>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row[item]"
|
||||||
|
type="textarea"
|
||||||
|
clearable
|
||||||
|
:placeholder="item"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
:label="$t('操作')"
|
||||||
|
width="100px"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
text
|
||||||
|
bg
|
||||||
|
type="danger"
|
||||||
|
@click.stop="table.del(scope.$index)"
|
||||||
>
|
>
|
||||||
<circle-close-filled />
|
{{ $t('删除') }}
|
||||||
</el-icon>
|
</el-button>
|
||||||
</div>
|
</template>
|
||||||
</slot>
|
</el-table-column>
|
||||||
</el-upload>
|
</el-table>
|
||||||
</template>
|
</div>
|
||||||
</vue-draggable>
|
|
||||||
</div>
|
<div class="pagination">
|
||||||
</div>
|
<el-pagination
|
||||||
|
v-model:current-page="pagination.page"
|
||||||
|
background
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:total="upload.list.length"
|
||||||
|
:page-size="pagination.size"
|
||||||
|
@current-change="pagination.onCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</cl-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'cl-upload'
|
name: 'cl-import-btn'
|
||||||
});
|
});
|
||||||
|
|
||||||
import { computed, ref, watch, type PropType, nextTick } from 'vue';
|
|
||||||
import { assign, isArray, isEmpty, isNumber } from 'lodash-es';
|
|
||||||
import VueDraggable from 'vuedraggable';
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
import { PictureFilled, UploadFilled, CircleCloseFilled } from '@element-plus/icons-vue';
|
|
||||||
import { useForm } from '@cool-vue/crud';
|
import { useForm } from '@cool-vue/crud';
|
||||||
import { useCool } from '/@/cool';
|
import { ElMessage } from 'element-plus';
|
||||||
import { useBase } from '/$/base';
|
import { reactive, type PropType, computed } from 'vue';
|
||||||
import { uuid, isPromise } from '/@/cool/utils';
|
import * as XLSX from 'xlsx';
|
||||||
import { getUrls, getType } from '../utils';
|
import chardet from 'chardet';
|
||||||
import { useUpload } from '../hooks';
|
import { extname } from '/@/cool/utils';
|
||||||
import UploadItem from './upload-item/index.vue';
|
import { has } from 'lodash-es';
|
||||||
import { CrudProps } from '/#/crud';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useCool } from '/@/cool';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
...CrudProps,
|
onConfig: Function,
|
||||||
// 绑定值,单选时字符串,多选时字符串数组
|
onSubmit: Function,
|
||||||
modelValue: {
|
template: {
|
||||||
type: [String, Array],
|
type: String,
|
||||||
default: () => []
|
default: ''
|
||||||
|
},
|
||||||
|
tips: String,
|
||||||
|
limitSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
},
|
},
|
||||||
// 上传类型
|
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<'image' | 'file'>,
|
type: String as PropType<
|
||||||
default: 'image'
|
'default' | 'success' | 'warning' | 'info' | 'text' | 'primary' | 'danger'
|
||||||
|
>,
|
||||||
|
default: 'success'
|
||||||
},
|
},
|
||||||
// 允许上传的文件类型
|
icon: String,
|
||||||
accept: String,
|
|
||||||
// 是否多选
|
|
||||||
multiple: Boolean,
|
|
||||||
// 限制数量
|
|
||||||
limit: Number,
|
|
||||||
// 限制大小
|
|
||||||
limitSize: Number,
|
|
||||||
// 是否自动上传
|
|
||||||
autoUpload: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
// 元素大小
|
|
||||||
size: [String, Number, Array],
|
|
||||||
// 小图模式
|
|
||||||
small: Boolean,
|
|
||||||
// 显示图标
|
|
||||||
icon: null,
|
|
||||||
// 显示文案
|
|
||||||
text: String,
|
|
||||||
// 显示角标
|
|
||||||
showTag: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
// 是否显示上传列表
|
|
||||||
showFileList: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
// 列表是否可拖拽
|
|
||||||
draggable: Boolean,
|
|
||||||
// 是否拖拽到特定区域以进行上传
|
|
||||||
drag: Boolean,
|
|
||||||
// 是否禁用
|
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
// 是否可删除
|
accept: {
|
||||||
deletable: Boolean,
|
type: String,
|
||||||
// 自定义样式名
|
default:
|
||||||
customClass: String,
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel,text/csv'
|
||||||
// 上传前钩子
|
}
|
||||||
beforeUpload: Function,
|
|
||||||
// 云端上传路径前缀
|
|
||||||
prefixPath: String
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'change', 'upload', 'success', 'error', 'progress']);
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
const { refs, setRefs } = useCool();
|
|
||||||
const { user } = useBase();
|
|
||||||
const Form = useForm();
|
const Form = useForm();
|
||||||
const { options, toUpload } = useUpload();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { refs, setRefs } = useCool();
|
||||||
|
|
||||||
// 元素尺寸
|
// 提示信息
|
||||||
const size = computed(() => {
|
const tips = computed(() => {
|
||||||
const d = props.size || options.size;
|
return props.tips || t('请按照模版填写信息');
|
||||||
return (isArray(d) ? d : [d, d]).map((e: string | number) => (isNumber(e) ? e + 'px' : e));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 是否禁用
|
// 上传信息
|
||||||
const disabled = computed(() => {
|
const upload = reactive({
|
||||||
return props.isDisabled || props.disabled;
|
filename: '',
|
||||||
|
file: null as File | null,
|
||||||
|
list: [] as any[]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 最大上传数量
|
// 分页信息
|
||||||
const limit = props.limit || options.limit.upload;
|
const pagination = reactive({
|
||||||
|
size: 20,
|
||||||
// 图片大小限制
|
page: 1,
|
||||||
const limitSize = props.limitSize || options.limit.size;
|
onCurrentChange(page: number) {
|
||||||
|
pagination.page = page;
|
||||||
// 文案
|
|
||||||
const text = computed(() => {
|
|
||||||
if (props.text !== undefined) {
|
|
||||||
return props.text;
|
|
||||||
} else {
|
|
||||||
switch (props.type) {
|
|
||||||
case 'file':
|
|
||||||
return t('选择文件');
|
|
||||||
|
|
||||||
case 'image':
|
|
||||||
return t('选择图片');
|
|
||||||
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 请求头
|
// 数据表格
|
||||||
const headers = computed(() => {
|
const table = reactive({
|
||||||
return {
|
// 表头
|
||||||
Authorization: user.token
|
header: [] as string[],
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 列表
|
// 选中列表
|
||||||
const list = ref<Upload.Item[]>([]);
|
selection: [] as any[],
|
||||||
|
|
||||||
// 显示上传列表
|
// 删除行
|
||||||
const showList = computed(() => {
|
del(index?: number) {
|
||||||
if (props.type == 'file') {
|
if (index !== undefined) {
|
||||||
return props.showFileList ? !isEmpty(list.value) : false;
|
upload.list.splice(index, 1);
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 文件格式
|
|
||||||
const accept = computed(() => {
|
|
||||||
return props.accept || (props.type == 'file' ? '' : 'image/*');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 能否添加
|
|
||||||
const isAdd = computed(() => {
|
|
||||||
const len = list.value.length;
|
|
||||||
|
|
||||||
if (props.multiple && !disabled.value) {
|
|
||||||
return limit - len > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len == 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 上传前
|
|
||||||
async function onBeforeUpload(file: any, item?: Upload.Item) {
|
|
||||||
function next() {
|
|
||||||
const d = {
|
|
||||||
uid: file.uid,
|
|
||||||
size: file.size,
|
|
||||||
name: file.name,
|
|
||||||
type: getType(file.name),
|
|
||||||
progress: props.autoUpload ? 0 : 100, // 非自动上传时默认100%
|
|
||||||
url: '',
|
|
||||||
preload: '',
|
|
||||||
error: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
// 图片预览地址
|
|
||||||
if (d.type == 'image') {
|
|
||||||
if (file instanceof File) {
|
|
||||||
d.preload = window.webkitURL.createObjectURL(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传事件
|
|
||||||
emit('upload', d, file);
|
|
||||||
|
|
||||||
// 赋值
|
|
||||||
if (item) {
|
|
||||||
assign(item, d);
|
|
||||||
} else {
|
} else {
|
||||||
if (props.multiple) {
|
upload.list = upload.list.filter(a => {
|
||||||
if (!isAdd.value) {
|
return !table.selection.includes(a._index);
|
||||||
ElMessage.warning(t('最多只能上传{n}个文件', { n: limit }));
|
});
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
list.value.push(d);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
list.value = [d];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
return true;
|
// 序号
|
||||||
|
onIndex(index: number) {
|
||||||
|
return index + 1 + (pagination.page - 1) * pagination.size;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
onSelectionChange(arr: any[]) {
|
||||||
|
table.selection = arr.map(e => e._index);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 文件大小限制
|
// 数据列表
|
||||||
if (file.size / 1024 / 1024 >= limitSize) {
|
const list = computed(() => {
|
||||||
ElMessage.error(t('上传文件大小不能超过 {n}MB!', { n: limitSize }));
|
return upload.list.filter((_, i) => {
|
||||||
return false;
|
return (
|
||||||
}
|
i >= (pagination.page - 1) * pagination.size && i < pagination.page * pagination.size
|
||||||
|
);
|
||||||
// 自定义上传事件
|
});
|
||||||
if (props.beforeUpload) {
|
});
|
||||||
let r = props.beforeUpload(file, item, { next });
|
|
||||||
|
|
||||||
if (isPromise(r)) {
|
|
||||||
r.then(next).catch(() => null);
|
|
||||||
} else {
|
|
||||||
if (r) {
|
|
||||||
r = next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
} else {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除
|
|
||||||
function remove(index: number) {
|
|
||||||
list.value.splice(index, 1);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空
|
// 清空
|
||||||
function clear() {
|
function clear() {
|
||||||
list.value = [];
|
upload.filename = '';
|
||||||
|
upload.file = null;
|
||||||
|
upload.list = [];
|
||||||
|
table.header = [];
|
||||||
|
table.selection = [];
|
||||||
|
refs.upload?.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件上传请求
|
// 打开
|
||||||
async function httpRequest(req: any, item?: Upload.Item) {
|
function open() {
|
||||||
if (!item) {
|
|
||||||
item = list.value.find(e => e.uid == req.file.uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传请求
|
|
||||||
toUpload(req.file, {
|
|
||||||
prefixPath: props.prefixPath,
|
|
||||||
onProgress(progress) {
|
|
||||||
item!.progress = progress;
|
|
||||||
emit('progress', item);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
assign(item!, res);
|
|
||||||
emit('success', item);
|
|
||||||
update();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
item!.error = err.message;
|
|
||||||
emit('error', item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测是否还有未上传的文件
|
|
||||||
function check() {
|
|
||||||
return list.value.find(e => !e.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新
|
|
||||||
function update() {
|
|
||||||
if (!check()) {
|
|
||||||
const urls = getUrls(list.value);
|
|
||||||
|
|
||||||
const val = props.multiple ? getUrls(list.value) : urls[0] || '';
|
|
||||||
|
|
||||||
// 更新绑定值
|
|
||||||
emit('update:modelValue', val);
|
|
||||||
emit('change', val);
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
if (props.prop) {
|
|
||||||
Form.value?.validateField(props.prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空
|
|
||||||
refs.upload?.clearFiles();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动上传
|
|
||||||
function upload(file: File) {
|
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
refs.upload?.clearFiles();
|
Form.value?.open({
|
||||||
|
title: t('导入'),
|
||||||
|
width: computed(() => (upload.filename ? '80%' : '800px')),
|
||||||
|
dialog: {
|
||||||
|
'close-on-press-escape': false
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
...(props.onConfig ? props.onConfig(Form) : []),
|
||||||
|
{
|
||||||
|
prop: 'file',
|
||||||
|
component: {
|
||||||
|
name: 'slot-upload'
|
||||||
|
},
|
||||||
|
hidden() {
|
||||||
|
return upload.filename;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: {
|
||||||
|
name: 'slot-list'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
op: {
|
||||||
|
saveButtonText: t('提交')
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
submit(_, { done, close }) {
|
||||||
|
if (!upload.filename) {
|
||||||
|
done();
|
||||||
|
return ElMessage.error(t('请选择文件'));
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
if (props.onSubmit) {
|
||||||
refs.upload?.handleStart(file);
|
props.onSubmit(
|
||||||
refs.upload?.submit();
|
{
|
||||||
|
...upload,
|
||||||
|
..._
|
||||||
|
},
|
||||||
|
{ done, close }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(t('[cl-import-btn] onSubmit is required'));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听绑定值
|
// 上传
|
||||||
watch(
|
function onUpload(raw: File, _: any, { next }: any) {
|
||||||
() => props.modelValue,
|
const reader = new FileReader();
|
||||||
(val: any[] | string) => {
|
const ext = extname(raw.name);
|
||||||
if (check()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const urls = (isArray(val) ? val : [val]).filter(Boolean);
|
reader.onload = (event: any) => {
|
||||||
|
try {
|
||||||
|
let data = '';
|
||||||
|
|
||||||
list.value = urls
|
if (ext == 'csv') {
|
||||||
.map((url, index) => {
|
const detected: any = chardet.detect(new Uint8Array(event.target.result));
|
||||||
const old = list.value[index] || {};
|
const decoder = new TextDecoder(detected);
|
||||||
|
data = decoder.decode(event.target.result);
|
||||||
|
} else {
|
||||||
|
data = event.target.result;
|
||||||
|
}
|
||||||
|
|
||||||
return assign(
|
const workbook = XLSX.read(data, { type: 'binary', raw: ext == 'csv' });
|
||||||
{
|
|
||||||
progress: 100,
|
let json: any[] = [];
|
||||||
uid: uuid()
|
for (const sheet in workbook.Sheets) {
|
||||||
},
|
if (has(workbook.Sheets, sheet)) {
|
||||||
old,
|
json = json.concat(
|
||||||
{
|
XLSX.utils.sheet_to_json(workbook.Sheets[sheet], {
|
||||||
type: getType(url),
|
raw: false,
|
||||||
url,
|
dateNF: 'yyyy-mm-dd',
|
||||||
preload: old.url == url ? old.preload : url // 防止重复预览
|
defval: ''
|
||||||
}
|
})
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
.filter((_, i) => {
|
}
|
||||||
return props.multiple ? true : i == 0;
|
|
||||||
|
upload.list = json.map((e, i) => {
|
||||||
|
return {
|
||||||
|
...e,
|
||||||
|
_index: i
|
||||||
|
};
|
||||||
});
|
});
|
||||||
},
|
upload.filename = raw.name;
|
||||||
{
|
upload.file = raw;
|
||||||
immediate: true
|
|
||||||
}
|
const sheet = workbook.Sheets[Object.keys(workbook.Sheets)[0]];
|
||||||
);
|
|
||||||
|
for (const i in sheet) {
|
||||||
|
if (i[0] === '!') continue;
|
||||||
|
|
||||||
|
const row = i.match(/[0-9]+/)?.[0];
|
||||||
|
|
||||||
|
if (row == '1') {
|
||||||
|
table.header.push(sheet[i].v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', json);
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error(t('文件异常,请检查内容是否正确'));
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ext == 'csv') {
|
||||||
|
reader.readAsArrayBuffer(raw);
|
||||||
|
} else {
|
||||||
|
reader.readAsBinaryString(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载模版
|
||||||
|
function download() {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.setAttribute('href', props.template);
|
||||||
|
link.setAttribute('download', '');
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
|
||||||
// 导出
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
isAdd,
|
open,
|
||||||
list,
|
|
||||||
check,
|
|
||||||
clear,
|
clear,
|
||||||
remove,
|
Form
|
||||||
upload
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cl-upload {
|
.upload {
|
||||||
line-height: normal;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.Ghost {
|
.inner {
|
||||||
.cl-upload__item {
|
|
||||||
border: 1px dashed var(--el-color-primary) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__file {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&-btn {
|
:deep(.cl-upload) {
|
||||||
width: fit-content;
|
.cl-upload__footer,
|
||||||
}
|
.cl-upload__list,
|
||||||
}
|
.el-upload,
|
||||||
|
.is-drag {
|
||||||
&__list {
|
width: 100% !important;
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item,
|
|
||||||
&__demo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: v-bind('size[0]');
|
|
||||||
width: v-bind('size[1]');
|
|
||||||
background-color: var(--el-fill-color-light);
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__demo {
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 46px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-dragger {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__file-btn {
|
|
||||||
& + .cl-upload__list {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-upload) {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
.el-upload-dragger {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background-color: transparent !important;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.is-dragover {
|
|
||||||
&::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px dashed var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
.cl-upload__demo {
|
|
||||||
color: var(--el-text-color-placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.cl-upload__item) {
|
|
||||||
cursor: not-allowed;
|
|
||||||
background-color: var(--el-disabled-bg-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-multiple {
|
|
||||||
.cl-upload__list {
|
|
||||||
margin-bottom: -5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload__item {
|
|
||||||
margin: 0 5px 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload__footer {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-small {
|
|
||||||
.cl-upload__demo {
|
|
||||||
.el-icon {
|
|
||||||
font-size: 20px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload__item-remove {
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
top: 0px;
|
|
||||||
color: var(--el-color-danger);
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.cl-upload-item) {
|
|
||||||
.cl-upload-item__progress-bar,
|
|
||||||
.cl-upload-item__actions,
|
|
||||||
.cl-upload-item__tag {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cl-upload-item__progress-value {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.is-disabled) {
|
|
||||||
.cl-upload__demo {
|
|
||||||
&:hover {
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
.head {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user