123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841 |
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('axios'), require('lodash'), require('prettier'), require('@vue/compiler-sfc'), require('magic-string'), require('glob')) :
- typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'axios', 'lodash', 'prettier', '@vue/compiler-sfc', 'magic-string', 'glob'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.axios, global.lodash, global.prettier, global.compilerSfc, global.magicString, global.glob));
- })(this, (function (exports, fs, path, axios, lodash, prettier, compilerSfc, magicString, glob) { 'use strict';
- const config = {
- type: "admin",
- reqUrl: "",
- demo: false,
- eps: {
- api: "",
- dist: "./build/cool",
- mapping: [
- {
- // 自定义匹配
- custom: ({ propertyName, type }) => {
- // 如果没有,返回null或者不返回,则继续遍历其他匹配规则
- return null;
- },
- },
- {
- type: "string",
- test: ["varchar", "text", "simple-json"],
- },
- {
- type: "string[]",
- test: ["simple-array"],
- },
- {
- type: "Date",
- test: ["datetime", "date"],
- },
- {
- type: "number",
- test: ["tinyint", "int", "decimal"],
- },
- {
- type: "BigInt",
- test: ["bigint"],
- },
- ],
- },
- };
- // 根目录
- function rootDir(path$1) {
- switch (config.type) {
- case "app":
- return path.join(process.env.UNI_INPUT_DIR, path$1);
- default:
- return path.join(process.cwd(), path$1);
- }
- }
- // 首字母大写
- function firstUpperCase(value) {
- return value.replace(/\b(\w)(\w*)/g, function ($0, $1, $2) {
- return $1.toUpperCase() + $2;
- });
- }
- // 横杠转驼峰
- function toCamel(str) {
- return str.replace(/([^-])(?:-+([^-]))/g, function ($0, $1, $2) {
- return $1 + $2.toUpperCase();
- });
- }
- // 创建目录
- function createDir(path, recursive) {
- try {
- if (!fs.existsSync(path))
- fs.mkdirSync(path, { recursive });
- }
- catch (err) { }
- }
- // 读取文件
- function readFile(path, json) {
- try {
- const content = fs.readFileSync(path, "utf8");
- return json ? JSON.parse(content) : content;
- }
- catch (err) { }
- return "";
- }
- // 写入文件
- function writeFile(path, data) {
- try {
- return fs.writeFileSync(path, data);
- }
- catch (err) { }
- return "";
- }
- // 解析body
- function parseJson(req) {
- return new Promise((resolve) => {
- let d = "";
- req.on("data", function (chunk) {
- d += chunk;
- });
- req.on("end", function () {
- try {
- resolve(JSON.parse(d));
- }
- catch {
- resolve({});
- }
- });
- });
- }
- function error(message) {
- console.log("\x1B[31m%s\x1B[0m", message);
- }
- let service = {};
- let list = [];
- let customList = [];
- // 获取请求地址
- function getEpsUrl() {
- let url = config.eps.api;
- if (!url) {
- url = config.type;
- }
- switch (url) {
- case "app":
- url = "/app/base/comm/eps";
- break;
- case "admin":
- url = "/admin/base/open/eps";
- break;
- }
- return url;
- }
- // 获取路径
- function getEpsPath(filename) {
- return path.join(config.type == "admin" ? config.eps.dist : rootDir(config.eps.dist), filename || "");
- }
- // 获取方法名
- function getNames(v) {
- return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
- }
- // 获取数据
- async function getData(data) {
- // 自定义数据
- if (!lodash.isEmpty(data)) {
- customList = (data || []).map((e) => {
- return {
- ...e,
- isLocal: true,
- };
- });
- }
- // 本地文件
- const epsPath = getEpsPath("eps.json");
- try {
- list = readFile(epsPath, true) || [];
- }
- catch (err) {
- error(`[cool-eps] ${epsPath} 文件异常, ${err.message}`);
- }
- // 请求地址
- const url = config.reqUrl + getEpsUrl();
- // 请求数据
- await axios
- .get(url, {
- timeout: 5000,
- })
- .then((res) => {
- const { code, data, message } = res.data;
- if (code === 1000) {
- if (!lodash.isEmpty(data) && data) {
- lodash.merge(list, Object.values(data).flat());
- }
- }
- else {
- error(`[cool-eps] ${message}`);
- }
- })
- .catch(() => {
- error(`[cool-eps] 后端未启动 ➜ ${url}`);
- });
- // 合并自定义数据
- if (lodash.isArray(customList)) {
- customList.forEach((e) => {
- const d = list.find((a) => e.prefix === a.prefix);
- if (d) {
- lodash.merge(d, e);
- }
- else {
- list.push(e);
- }
- });
- }
- // 设置默认值
- list.forEach((e) => {
- if (!e.namespace) {
- e.namespace = "";
- }
- if (!e.api) {
- e.api = [];
- }
- });
- }
- // 创建 json 文件
- function createJson() {
- const arr = list.map((e) => {
- return {
- prefix: e.prefix,
- name: e.name || "",
- api: e.api.map((e) => {
- return {
- name: e.name,
- method: e.method,
- path: e.path,
- };
- }),
- };
- });
- const content = JSON.stringify(arr);
- const local_content = readFile(getEpsPath("eps.json"));
- // 是否需要更新
- const isUpdate = content != local_content;
- if (isUpdate) {
- fs.createWriteStream(getEpsPath("eps.json"), {
- flags: "w",
- }).write(content);
- }
- return isUpdate;
- }
- // 创建描述文件
- async function createDescribe({ list, service }) {
- // 获取类型
- function getType({ propertyName, type }) {
- for (const map of config.eps.mapping) {
- if (map.custom) {
- const resType = map.custom({ propertyName, type });
- if (resType)
- return resType;
- }
- if (map.test) {
- if (map.test.includes(type))
- return map.type;
- }
- }
- return type;
- }
- // 创建 Entity
- function createEntity() {
- const t0 = [];
- const arr = [];
- for (const item of list) {
- if (!item.name)
- continue;
- const t = [`interface ${item.name} {`];
- for (const col of item.columns || []) {
- // 描述
- t.push("\n");
- t.push("/**\n");
- t.push(` * ${col.comment}\n`);
- t.push(" */\n");
- t.push(`${col.propertyName}?: ${getType({
- propertyName: col.propertyName,
- type: col.type,
- })};`);
- }
- t.push("\n");
- t.push("/**\n");
- t.push(` * 任意键值\n`);
- t.push(" */\n");
- t.push(`[key: string]: any;`);
- t.push("}");
- if (!arr.includes(item.name)) {
- arr.push(item.name);
- t0.push(t);
- }
- }
- return t0.map((e) => e.join("")).join("\n\n");
- }
- // 创建 Service
- function createDts() {
- const t0 = [];
- const t1 = [
- `
- type json = any;
- type Service = {
- request(options?: {
- url: string;
- method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
- data?: any;
- params?: any;
- headers?: {
- [key: string]: any;
- },
- timeout?: number;
- proxy?: boolean;
- [key: string]: any;
- }): Promise<any>;
- `,
- ];
- // 处理数据
- function deep(d, k) {
- if (!k)
- k = "";
- for (const i in d) {
- const name = k + toCamel(firstUpperCase(i.replace(/[:]/g, "")));
- if (d[i].namespace) {
- // 查找配置
- const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`);
- if (item) {
- const t = [`interface ${name} {`];
- t1.push(`${i}: ${name};`);
- // 插入方法
- if (item.api) {
- // 权限列表
- const permission = [];
- item.api.forEach((a) => {
- // 方法名
- const n = toCamel(a.name || lodash.last(a.path.split("/")) || "").replace(/[:\/-]/g, "");
- if (n) {
- // 参数类型
- let q = [];
- // 参数列表
- const { parameters = [] } = a.dts || {};
- parameters.forEach((p) => {
- if (p.description) {
- q.push(`\n/** ${p.description} */\n`);
- }
- if (p.name.includes(":")) {
- return false;
- }
- const a = `${p.name}${p.required ? "" : "?"}`;
- const b = `${p.schema.type || "string"}`;
- q.push(`${a}: ${b},`);
- });
- if (lodash.isEmpty(q)) {
- q = ["any"];
- }
- else {
- q.unshift("{");
- q.push("}");
- }
- // 返回类型
- let res = "";
- // 实体名
- const en = item.name || "any";
- switch (a.path) {
- case "/page":
- res = `
- {
- pagination: { size: number; page: number; total: number; [key: string]: any };
- list: ${en} [];
- [key: string]: any;
- }
- `;
- break;
- case "/list":
- res = `${en} []`;
- break;
- case "/info":
- res = en;
- break;
- default:
- res = "any";
- break;
- }
- // 描述
- t.push("\n");
- t.push("/**\n");
- t.push(` * ${a.summary || n}\n`);
- t.push(" */\n");
- t.push(`${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;`);
- if (!permission.includes(n)) {
- permission.push(n);
- }
- }
- });
- // 权限标识
- t.push("\n");
- t.push("/**\n");
- t.push(" * 权限标识\n");
- t.push(" */\n");
- t.push(`permission: { ${permission
- .map((e) => `${e}: string;`)
- .join("\n")} };`);
- // 权限状态
- t.push("\n");
- t.push("/**\n");
- t.push(" * 权限状态\n");
- t.push(" */\n");
- t.push(`_permission: { ${permission
- .map((e) => `${e}: boolean;`)
- .join("\n")} };`);
- // 请求
- t.push("\n");
- t.push("/**\n");
- t.push(" * 请求\n");
- t.push(" */\n");
- t.push(`request: Service['request']`);
- }
- t.push("}");
- t0.push(t);
- }
- }
- else {
- t1.push(`${i}: {`);
- deep(d[i], name);
- t1.push(`},`);
- }
- }
- }
- // 深度
- deep(service);
- // 结束
- t1.push("}");
- // 追加
- t0.push(t1);
- return t0.map((e) => e.join("")).join("\n\n");
- }
- // 文件内容
- const text = `
- declare namespace Eps {
- ${createEntity()}
- ${createDts()}
- }
- `;
- // 文本内容
- const content = await prettier.format(text, {
- parser: "typescript",
- useTabs: true,
- tabWidth: 4,
- endOfLine: "lf",
- semi: true,
- singleQuote: false,
- printWidth: 100,
- trailingComma: "none",
- });
- const local_content = readFile(getEpsPath("eps.d.ts"));
- // 是否需要更新
- if (content != local_content) {
- // 创建 eps 描述文件
- fs.createWriteStream(getEpsPath("eps.d.ts"), {
- flags: "w",
- }).write(content);
- }
- }
- // 创建 service
- function createService() {
- // 路径第一层作为 id 标识
- const id = getEpsUrl().split("/")[1];
- list.forEach((e) => {
- // 请求地址
- const path = e.prefix[0] == "/" ? e.prefix.substring(1, e.prefix.length) : e.prefix;
- // 分隔路径
- const arr = path.replace(id, "").split("/").filter(Boolean).map(toCamel);
- // 遍历
- function deep(d, i) {
- const k = arr[i];
- if (k) {
- // 是否最后一个
- if (arr[i + 1]) {
- if (!d[k]) {
- d[k] = {};
- }
- deep(d[k], i + 1);
- }
- else {
- // 不存在则创建
- if (!d[k]) {
- d[k] = {
- namespace: path,
- permission: {},
- };
- }
- // 创建方法
- e.api.forEach((a) => {
- // 方法名
- const n = a.path.replace("/", "");
- if (n && !/[-:]/g.test(n)) {
- d[k][n] = a;
- }
- });
- // 创建权限
- getNames(d[k]).forEach((i) => {
- d[k].permission[i] = `${d[k].namespace.replace(`${id}/`, "")}/${i}`.replace(/\//g, ":");
- });
- }
- }
- }
- deep(service, 0);
- });
- }
- // 创建 eps
- async function createEps(query) {
- // 获取数据
- await getData(query?.list || []);
- // 创建 service
- createService();
- // 创建目录
- createDir(getEpsPath(), true);
- // 创建 json 文件
- const isUpdate = createJson();
- // 创建描述文件
- createDescribe({ service, list });
- return {
- service,
- list,
- isUpdate,
- };
- }
- function createTag(code, id) {
- if (/\.vue$/.test(id)) {
- let s;
- const str = () => s || (s = new magicString(code));
- const { descriptor } = compilerSfc.parse(code);
- if (!descriptor.script && descriptor.scriptSetup) {
- const res = compilerSfc.compileScript(descriptor, { id });
- const { name, lang } = res.attrs;
- str().appendLeft(0, `<script lang="${lang}">
- import { defineComponent } from 'vue'
- export default defineComponent({
- name: "${name}"
- })
- <\/script>`);
- return {
- map: str().generateMap(),
- code: str().toString()
- };
- }
- }
- return null;
- }
- function findFiles(dir) {
- const res = [];
- const dirs = fs.readdirSync(dir, {
- withFileTypes: true,
- });
- for (const d of dirs) {
- if (d.isDirectory()) {
- res.push(...findFiles(dir + d.name + "/"));
- }
- else {
- if (path.extname(d.name) == ".svg") {
- const svg = fs.readFileSync(dir + d.name)
- .toString()
- .replace(/(\r)|(\n)/g, "")
- .replace(/<svg([^>+].*?)>/, (_, $2) => {
- let width = 0;
- let height = 0;
- let content = $2.replace(/(width|height)="([^>+].*?)"/g, (_, s2, s3) => {
- if (s2 === "width") {
- width = s3;
- }
- else if (s2 === "height") {
- height = s3;
- }
- return "";
- });
- if (!/(viewBox="[^>+].*?")/g.test($2)) {
- content += `viewBox="0 0 ${width} ${height}"`;
- }
- return `<symbol id="icon-${d.name.replace(".svg", "")}" ${content}>`;
- })
- .replace("</svg>", "</symbol>");
- res.push(svg);
- }
- }
- }
- return res;
- }
- function createSvg(html) {
- const res = findFiles(rootDir("./src/"));
- return html.replace("<body>", `<body>
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
- ${res.join("")}
- </svg>`);
- }
- // 创建文件
- async function createMenu(options) {
- // 格式化内容
- const content = await prettier.format(options.code, {
- parser: "vue",
- useTabs: true,
- tabWidth: 4,
- endOfLine: "lf",
- semi: true,
- jsxBracketSameLine: true,
- singleQuote: false,
- printWidth: 100,
- trailingComma: "none",
- });
- // 目录路径
- const dir = (options.viewPath || "").split("/");
- // 文件名
- const fname = dir.pop();
- // 源码路径
- const srcPath = `./src/${dir.join("/")}`;
- // 创建目录
- createDir(srcPath, true);
- // 创建文件
- fs.createWriteStream(path.join(srcPath, fname || "demo"), {
- flags: "w",
- }).write(content);
- }
- function base() {
- return {
- name: "vite-cool-base",
- enforce: "pre",
- configureServer(server) {
- server.middlewares.use(async (req, res, next) => {
- function done(data) {
- res.writeHead(200, { "Content-Type": "text/html;charset=UTF-8" });
- res.end(JSON.stringify(data));
- }
- if (req.originalUrl?.includes("__cool")) {
- const body = await parseJson(req);
- switch (req.url) {
- // 快速创建菜单
- case "/__cool_createMenu":
- await createMenu(body);
- break;
- // 创建描述文件
- case "/__cool_eps":
- await createEps(body);
- break;
- default:
- return done({
- code: 1001,
- message: "未知请求",
- });
- }
- done({
- code: 1000,
- });
- }
- else {
- next();
- }
- });
- },
- transform(code, id) {
- if (config.type == "admin") {
- return createTag(code, id);
- }
- return code;
- },
- transformIndexHtml(html) {
- if (config.type == "admin") {
- return createSvg(html);
- }
- return html;
- },
- };
- }
- function demo(enable) {
- const virtualModuleIds = ["virtual:demo"];
- return {
- name: "vite-cool-demo",
- enforce: "pre",
- resolveId(id) {
- if (virtualModuleIds.includes(id)) {
- return "\0" + id;
- }
- },
- async load(id) {
- if (id === "\0virtual:demo") {
- const demo = {};
- if (enable) {
- const files = await glob.glob(rootDir("./src/modules/demo/views/crud/components") + "/**", {
- stat: true,
- withFileTypes: true,
- });
- for (const file of files) {
- if (file.isFile()) {
- const p = path.join(file.path, file.name);
- demo[p
- .replace(/\\/g, "/")
- .split("src/modules/demo/views/crud/components/")[1]] = fs.readFileSync(p, "utf-8");
- }
- }
- }
- return `
- export const demo = ${JSON.stringify(demo)};
- `;
- }
- },
- };
- }
- async function createCtx() {
- let ctx = {};
- if (config.type == "app") {
- const manifest = readFile(rootDir("manifest.json"), true);
- // 文件路径
- const ctxPath = rootDir("pages.json");
- // 页面配置
- ctx = readFile(ctxPath, true);
- // 原数据,做更新比较用
- const ctxData = lodash.cloneDeep(ctx);
- // 删除临时页面
- ctx.pages = ctx.pages?.filter((e) => !e.isTemp);
- ctx.subPackages = ctx.subPackages?.filter((e) => !e.isTemp);
- // 加载 uni_modules 配置文件
- const files = await glob.glob(rootDir("uni_modules") + "/**/pages_init.json", {
- stat: true,
- withFileTypes: true,
- });
- for (const file of files) {
- if (file.isFile()) {
- const { pages = [], subPackages = [] } = readFile(path.join(file.path, file.name), true);
- // 合并到 pages 中
- [...pages, ...subPackages].forEach((e) => {
- e.isTemp = true;
- const isSub = !!e.root;
- const d = isSub
- ? ctx.subPackages?.find((a) => a.root == e.root)
- : ctx.pages?.find((a) => a.path == e.path);
- if (d) {
- lodash.assign(d, e);
- }
- else {
- if (isSub) {
- ctx.subPackages?.unshift(e);
- }
- else {
- ctx.pages?.unshift(e);
- }
- }
- });
- }
- }
- // 排序后检测,避免加载顺序问题
- function order(d) {
- return {
- pages: lodash.orderBy(d.pages, "path"),
- subPackages: lodash.orderBy(d.subPackages, "root"),
- };
- }
- // 是否需要更新 pages.json
- if (!lodash.isEqual(order(ctxData), order(ctx))) {
- console.log("[cool-ctx] pages updated");
- writeFile(ctxPath, JSON.stringify(ctx, null, 4));
- }
- // appid
- ctx.appid = manifest.appid;
- }
- if (config.type == "admin") {
- const list = fs.readdirSync(rootDir("./src/modules"));
- ctx.modules = list.filter((e) => !e.includes("."));
- }
- return ctx;
- }
- async function virtual() {
- const virtualModuleIds = ["virtual:eps", "virtual:ctx"];
- return {
- name: "vite-cool-virtual",
- enforce: "pre",
- configureServer(server) {
- server.middlewares.use(async (req, res, next) => {
- // 页面刷新时触发
- if (req.url == "/@vite/client") {
- // 重新加载虚拟模块
- virtualModuleIds.forEach((vm) => {
- const mod = server.moduleGraph.getModuleById(`\0${vm}`);
- if (mod) {
- server.moduleGraph.invalidateModule(mod);
- }
- });
- }
- next();
- });
- },
- handleHotUpdate({ file, server }) {
- // 文件修改时触发
- if (!["pages.json", "dist", "build/cool", "eps.json", "eps.d.ts"].some((e) => file.includes(e))) {
- createCtx();
- createEps().then((data) => {
- if (data.isUpdate) {
- // 通知客户端刷新
- (server.hot || server.ws).send({
- type: "custom",
- event: "eps-update",
- data,
- });
- }
- });
- }
- },
- resolveId(id) {
- if (virtualModuleIds.includes(id)) {
- return "\0" + id;
- }
- },
- async load(id) {
- if (id === "\0virtual:eps") {
- const eps = await createEps();
- return `
- export const eps = ${JSON.stringify(eps)}
- `;
- }
- if (id === "\0virtual:ctx") {
- const ctx = await createCtx();
- return `
- export const ctx = ${JSON.stringify(ctx)}
- `;
- }
- },
- };
- }
- function cool(options) {
- // 应用类型,admin | app
- config.type = options.type;
- // 请求地址
- config.reqUrl = options.proxy["/dev/"].target;
- // Eps
- if (options.eps) {
- const { dist, mapping, api } = options.eps;
- // 类型
- if (api) {
- config.eps.api = api;
- }
- // 输出目录
- if (dist) {
- config.eps.dist = dist;
- }
- // 匹配规则
- if (mapping) {
- lodash.merge(config.eps.mapping, mapping);
- }
- }
- return [base(), virtual(), demo(options.demo)];
- }
- exports.cool = cool;
- }));
|