Compare commits

..

2 Commits

Author SHA1 Message Date
icssoa
c496af0566 添加 uniapp-x 插件 2025-05-19 12:39:12 +08:00
icssoa
c5e2ac3805 优化 2025-05-17 14:44:44 +08:00
14 changed files with 1639 additions and 739 deletions

View File

@ -165,10 +165,10 @@
<body>
<div class="preload__wrap" id="Loading">
<div class="preload__container">
<p class="preload__name"></p>
<div class="preload__name"></div>
<div class="preload__loading"></div>
<p class="preload__title"></p>
<p class="preload__sub-title"></p>
<div class="preload__title"></div>
<div class="preload__sub-title"></div>
</div>
</div>

View File

@ -1,2 +1,24 @@
import type { Config } from "../types";
export declare function cool(options: Config.Options): (import("vite").Plugin<any> | Promise<import("vite").Plugin<any>>)[];
export declare function cool(options: Config.Options): (import("vite").Plugin<any> | Promise<import("vite").Plugin<any>> | {
name: string;
enforce: "pre";
config(): {
css: {
postcss: {
plugins: {
postcssPlugin: string;
prepare(): {
Rule(rule: any): void;
Declaration(decl: any): void;
};
}[];
};
};
};
transform(code: string, id: string): {
code: string;
map: {
mappings: string;
};
} | null;
}[])[];

View File

@ -1,8 +1,8 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo')) :
typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo));
})(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('fs'), require('path'), require('prettier'), require('axios'), require('lodash'), require('@vue/compiler-sfc'), require('magic-string'), require('glob'), require('node:util'), require('svgo'), require('postcss-value-parser')) :
typeof define === 'function' && define.amd ? define(['exports', 'fs', 'path', 'prettier', 'axios', 'lodash', '@vue/compiler-sfc', 'magic-string', 'glob', 'node:util', 'svgo', 'postcss-value-parser'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.index = {}, global.fs, global.path, global.prettier, global.axios, global.lodash, global.compilerSfc, global.magicString, global.glob, global.util, global.svgo, global.valueParser));
})(this, (function (exports, fs, path, prettier, axios, lodash, compilerSfc, magicString, glob, util, svgo, valueParser) { 'use strict';
const config = {
type: "admin",
@ -50,6 +50,7 @@
function rootDir(path$1) {
switch (config.type) {
case "app":
case "uniapp-x":
return path.join(process.env.UNI_INPUT_DIR, path$1);
default:
return path.join(process.cwd(), path$1);
@ -136,6 +137,7 @@
}
switch (url) {
case "app":
case "uniapp-x":
url = "/app/base/comm/eps";
break;
case "admin":
@ -319,8 +321,8 @@
}
return t0;
}
// 创建 Service
async function createDts() {
// 创建 Controller
async function createController() {
let controller = "";
let chain = "";
// 处理数据
@ -379,7 +381,7 @@
case "/page":
res = `
{
pagination: { size: number; page: number; total: number; [key: string]: any };
pagination: { size: number; page: number; total: number; [key: string]: any; };
list: ${en} [];
[key: string]: any;
}
@ -444,7 +446,7 @@
${controller}
type Service = {
interface Service {
/**
* 基础请求
*/
@ -453,10 +455,7 @@
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
data?: any;
params?: any;
headers?: {
authorization?: string;
[key: string]: any;
},
headers?: any,
timeout?: number;
proxy?: boolean;
[key: string]: any;
@ -469,19 +468,33 @@
`;
}
// 文件内容
const text = `
declare namespace Eps {
${createEntity()}
${await createDts()}
}
let text = `
${createEntity()}
${await createController()}
`;
// 文件名
let name = "eps.d.ts";
if (config.type == "uniapp-x") {
name = "eps.uts";
text = text
.replaceAll("interface ", "export interface ")
.replaceAll("type Dict", "export type Dict")
.replaceAll("[key: string]: any;", "");
}
else {
text = `
declare namespace Eps {
${text}
}
`;
}
// 文本内容
const content = await formatCode(text);
const local_content = readFile(getEpsPath("eps.d.ts"));
const local_content = readFile(getEpsPath(name));
// 是否需要更新
if (content && content != local_content) {
// 创建 eps 描述文件
fs.createWriteStream(getEpsPath("eps.d.ts"), {
fs.createWriteStream(getEpsPath(name), {
flags: "w",
}).write(content);
}
@ -541,8 +554,18 @@
}
// 创建 dict
async function createDict() {
const url = config.reqUrl + "/" + config.type + "/dict/info/types";
return axios
let p = "";
switch (config.type) {
case "app":
case "uniapp-x":
p = "/app";
break;
case "admin":
p = "/admin";
break;
}
const url = config.reqUrl + p + "/dict/info/types";
const text = await axios
.get(url)
.then((res) => {
const { code, data } = res.data;
@ -557,6 +580,7 @@
.catch(() => {
error(`[cool-eps] Error${url}`);
});
return text || "";
}
// 创建 eps
async function createEps() {
@ -787,7 +811,7 @@
let ctx = {
serviceLang: "Node",
};
if (config.type == "app") {
if (config.type == "app" || config.type == "uniapp-x") {
const manifest = readFile(rootDir("manifest.json"), true);
// 文件路径
const ctxPath = rootDir("pages.json");
@ -1018,6 +1042,339 @@ if (typeof window !== 'undefined') {
};
}
// @ts-ignore
/**
* Tailwind CSS 特殊字符映射表
* 用于将类名中的特殊字符转换为安全字符避免编译或运行时冲突
*/
const TAILWIND_SAFE_CHAR_MAP = {
"[": "-",
"]": "-",
"(": "-",
")": "-",
"#": "-h-",
"!": "-i-",
"/": "-s-",
":": "-c-",
",": "-2c-",
};
/**
* Tailwind CSS 常用类名前缀集合
* 按功能分类便于维护和扩展
*/
const TAILWIND_CLASS_PREFIXES = [
// 间距
"p-",
"px-",
"py-",
"pt-",
"pr-",
"pb-",
"pl-",
"m-",
"mx-",
"my-",
"mt-",
"mr-",
"mb-",
"ml-",
"gap-",
"gap-x-",
"gap-y-",
"space-x-",
"space-y-",
"inset-",
"top-",
"right-",
"bottom-",
"left-",
// 尺寸
"w-",
"h-",
"min-w-",
"min-h-",
"max-w-",
"max-h-",
// 排版
"text-",
"font-",
"leading-",
"tracking-",
"indent-",
// 边框
"border-",
"border-t-",
"border-r-",
"border-b-",
"border-l-",
"rounded-",
"rounded-t-",
"rounded-r-",
"rounded-b-",
"rounded-l-",
"rounded-tl-",
"rounded-tr-",
"rounded-br-",
"rounded-bl-",
// 效果
"shadow-",
"blur-",
"brightness-",
"contrast-",
"drop-shadow-",
"grayscale-",
"hue-rotate-",
"invert-",
"saturate-",
"sepia-",
"backdrop-blur-",
"backdrop-brightness-",
"backdrop-contrast-",
"backdrop-grayscale-",
"backdrop-hue-rotate-",
"backdrop-invert-",
"backdrop-opacity-",
"backdrop-saturate-",
"backdrop-sepia-",
// 动画
"transition-",
"duration-",
"delay-",
"animate-",
// 变换
"translate-x-",
"translate-y-",
"rotate-",
"scale-",
"scale-x-",
"scale-y-",
"skew-x-",
"skew-y-",
"origin-",
// 布局
"columns-",
"break-after-",
"break-before-",
"break-inside-",
// Flexbox 和 Grid
"basis-",
"grow-",
"shrink-",
"grid-cols-",
"grid-rows-",
"col-span-",
"row-span-",
"col-start-",
"col-end-",
"row-start-",
"row-end-",
// SVG
"stroke-",
"stroke-w-",
"fill-",
];
/**
* Tailwind CSS 颜色变量映射
* 用于移除不需要的 CSS 变量声明
*/
const TAILWIND_COLOR_VARS = {
"--tw-text-opacity": 1,
"--tw-bg-opacity": 1,
};
/**
* 转换类名中的特殊字符为安全字符
* @param value 原始类名或值
* @param isSelector 是否为选择器true或普通值false
* @returns 转换后的安全字符串
*/
function toSafeTailwindClass(value, isSelector = false) {
// 处理任意值语法(如 w-[100px]
const arbitrary = value.match(/^(.+?)-\[(.*?)\]$/);
if (arbitrary) {
if (isSelector)
return value;
const [, prefix, content] = arbitrary;
const safePrefix = toSafeTailwindClass(prefix, isSelector);
const safeContent = content.replace(/[^\d.\w]/g, "-");
return `${safePrefix}-${safeContent}`;
}
let safeValue = value;
// 移除转义字符
if (safeValue.includes("\\")) {
safeValue = safeValue.replace(/\\/g, "");
}
// 替换特殊字符
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
const reg = new RegExp("\\" + char, "g");
if (reg.test(safeValue)) {
safeValue = safeValue.replace(reg, rep);
}
}
return safeValue;
}
/**
* 将现代 rgb 格式 rgb(234 179 8 / 0.1)转换为标准 rgba 格式
* @param value rgb 字符串
* @returns 标准 rgba 字符串
*/
function rgbToRgba(value) {
const match = value.match(/rgb\(([\d\s]+)\/\s*([\d.]+)\)/);
if (match) {
const [, rgb, alpha] = match;
const [r, g, b] = rgb.split(/\s+/);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return value;
}
/**
* PostCSS 插件 rem 单位转换为 rpx并处理 Tailwind 特殊字符
* @param options 配置项
* @returns PostCSS 插件对象
*/
function postcssRemToRpx(options) {
return {
postcssPlugin: "vite-cool-uniappx-remToRpx",
prepare() {
const handledSelectors = new Set();
const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = options;
const factor = remUnit * rpxRatio;
return {
Rule(rule) {
const sel = rule.selector;
if (handledSelectors.has(sel))
return;
const safeSel = toSafeTailwindClass(sel, true);
if (safeSel !== sel) {
rule.selector = safeSel;
handledSelectors.add(sel);
}
},
Declaration(decl) {
if (decl.value.includes("/* no-rem */"))
return;
if (TAILWIND_COLOR_VARS[decl.prop]) {
decl.remove();
return;
}
if (decl.value.includes("rgb(") && decl.value.includes("/")) {
decl.value = rgbToRgba(decl.value);
}
if (decl.value.includes("rpx") && decl.parent.selector.includes("text-")) {
decl.prop = "font-size";
}
const parsed = valueParser(decl.value);
let changed = false;
parsed.walk((node) => {
if (node.type === "word") {
// rem 转 rpx
const unit = valueParser.unit(node.value);
if (unit?.unit === "rem") {
const num = unit.number;
const precision = (num.split(".")[1] || "").length;
const rpxVal = (parseFloat(num) * factor)
.toFixed(precision || remPrecision)
.replace(/\.?0+$/, "");
node.value = `${rpxVal}rpx`;
changed = true;
}
// 特殊字符处理
if (node.value.includes(".") || /[[\]()#!/:,]/.test(node.value)) {
const safe = toSafeTailwindClass(node.value, true);
if (safe !== node.value) {
node.value = safe;
changed = true;
}
}
}
// 处理 var(--tw-xxx)
if (node.type === "function" && node.value === "var") {
if (node.nodes.length > 0 && node.nodes[0].value.startsWith("--tw-")) {
node.type = "word";
node.value = TAILWIND_COLOR_VARS[node.nodes[0].value];
changed = true;
}
}
});
if (changed) {
decl.value = parsed.toString();
}
},
};
},
};
}
postcssRemToRpx.postcss = true;
/**
* Vite 插件自动转换 .uvue 文件中的 Tailwind 类名为安全字符
* 并自动注入 rem rpx PostCSS 插件
* @param options 配置项
* @returns Vite 插件对象
*/
function tailwindTransformPlugin(options = {}) {
const merged = {
remUnit: 16,
remPrecision: 6,
rpxRatio: 2,
...options,
};
return {
name: "vite-cool-uniappx-tailwind",
enforce: "pre",
config() {
return {
css: {
postcss: {
plugins: [postcssRemToRpx(merged)],
},
},
};
},
transform(code, id) {
if (!id.includes(".uvue"))
return null;
let resultCode = code;
const tplMatch = resultCode.match(/<template>([\s\S]*?)<\/template>/);
if (!tplMatch?.[1])
return null;
let tpl = tplMatch[1];
const tplOrigin = tpl;
TAILWIND_CLASS_PREFIXES.forEach((prefix) => {
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
const reg = new RegExp(`(${prefix}[^\\s'"]*?\\${char}[^\\s'"]*?)`, "g");
const matches = [...tpl.matchAll(reg)];
matches.forEach((m) => {
const raw = m[1];
const safe = raw.replace(new RegExp("\\" + char, "g"), rep);
if (process.env.NODE_ENV === "development") {
console.log(`类名转换: ${raw}${safe}`);
}
tpl = tpl.replace(raw, safe);
});
}
});
if (tpl !== tplOrigin) {
resultCode = resultCode.replace(tplMatch[0], `<template>${tpl}</template>`);
return {
code: resultCode,
map: { mappings: "" },
};
}
return null;
},
};
}
/**
* uniappX 入口自动注入 Tailwind 类名转换插件
* @param options 配置项
* @returns Vite 插件数组
*/
function uniappX(options) {
if (config.type == "uniapp-x") {
return [tailwindTransformPlugin(options?.tailwind)];
}
return [];
}
function cool(options) {
// 应用类型admin | app
config.type = options.type;
@ -1047,7 +1404,7 @@ if (typeof window !== 'undefined') {
lodash.merge(config.eps.mapping, mapping);
}
}
return [base(), virtual(), demo(options.demo)];
return [base(), virtual(), uniappX(), demo(options.demo)];
}
exports.cool = cool;

67
packages/vite-plugin/dist/uniapp-x.d.ts vendored Normal file
View File

@ -0,0 +1,67 @@
interface PostcssRemToRpxOptions {
remUnit?: number;
remPrecision?: number;
rpxRatio?: number;
}
interface TailwindTransformOptions extends PostcssRemToRpxOptions {
}
/**
* Vite .uvue Tailwind
* rem rpx PostCSS
* @param options
* @returns Vite
*/
export declare function tailwindTransformPlugin(options?: TailwindTransformOptions): {
name: string;
enforce: "pre";
config(): {
css: {
postcss: {
plugins: {
postcssPlugin: string;
prepare(): {
Rule(rule: any): void;
Declaration(decl: any): void;
};
}[];
};
};
};
transform(code: string, id: string): {
code: string;
map: {
mappings: string;
};
} | null;
};
/**
* uniappX Tailwind
* @param options
* @returns Vite
*/
export declare function uniappX(options?: {
tailwind?: TailwindTransformOptions;
}): {
name: string;
enforce: "pre";
config(): {
css: {
postcss: {
plugins: {
postcssPlugin: string;
prepare(): {
Rule(rule: any): void;
Declaration(decl: any): void;
};
}[];
};
};
};
transform(code: string, id: string): {
code: string;
map: {
mappings: string;
};
} | null;
}[];
export {};

View File

@ -0,0 +1,9 @@
type primaryColor = "emerald" | "green" | "lime" | "orange" | "amber" | "yellow" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink";
type surfaceColor = "slate" | "gray" | "zinc" | "neutral" | "stone" | "soho" | "viva" | "ocean";
export declare function colorPalette(options: {
primary: primaryColor;
surface: surfaceColor;
}): {
[x: string]: string;
};
export {};

View File

@ -0,0 +1,38 @@
interface PostcssRemToRpxOptions {
remUnit?: number;
remPrecision?: number;
rpxRatio?: number;
}
interface TailwindTransformOptions extends PostcssRemToRpxOptions {
}
/**
* uniappX Tailwind
* @param options
* @returns Vite
*/
export declare function uniappX(options?: {
tailwind?: TailwindTransformOptions;
}): {
name: string;
enforce: "pre";
config(): {
css: {
postcss: {
plugins: {
postcssPlugin: string;
prepare(): {
Rule(rule: any): void;
Declaration(decl: any): void;
};
}[];
};
};
};
transform(code: string, id: string): {
code: string;
map: {
mappings: string;
};
} | null;
}[];
export {};

View File

@ -13,7 +13,7 @@ export async function createCtx() {
serviceLang: "Node",
};
if (config.type == "app") {
if (config.type == "app" || config.type == "uniapp-x") {
const manifest = readFile(rootDir("manifest.json"), true);
// 文件路径

View File

@ -20,6 +20,7 @@ function getEpsUrl() {
switch (url) {
case "app":
case "uniapp-x":
url = "/app/base/comm/eps";
break;
@ -237,8 +238,8 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
return t0;
}
// 创建 Service
async function createDts() {
// 创建 Controller
async function createController() {
let controller = "";
let chain = "";
@ -311,7 +312,7 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
case "/page":
res = `
{
pagination: { size: number; page: number; total: number; [key: string]: any };
pagination: { size: number; page: number; total: number; [key: string]: any; };
list: ${en} [];
[key: string]: any;
}
@ -387,7 +388,7 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
${controller}
type Service = {
interface Service {
/**
*
*/
@ -396,10 +397,7 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
data?: any;
params?: any;
headers?: {
authorization?: string;
[key: string]: any;
},
headers?: any,
timeout?: number;
proxy?: boolean;
[key: string]: any;
@ -413,22 +411,37 @@ async function createDescribe({ list, service }: { list: Eps.Entity[]; service:
}
// 文件内容
const text = `
declare namespace Eps {
${createEntity()}
${await createDts()}
}
let text = `
${createEntity()}
${await createController()}
`;
// 文件名
let name = "eps.d.ts";
if (config.type == "uniapp-x") {
name = "eps.uts";
text = text
.replaceAll("interface ", "export interface ")
.replaceAll("type Dict", "export type Dict")
.replaceAll("[key: string]: any;", "");
} else {
text = `
declare namespace Eps {
${text}
}
`;
}
// 文本内容
const content = await formatCode(text);
const local_content = readFile(getEpsPath("eps.d.ts"));
const local_content = readFile(getEpsPath(name));
// 是否需要更新
if (content && content != local_content) {
// 创建 eps 描述文件
createWriteStream(getEpsPath("eps.d.ts"), {
createWriteStream(getEpsPath(name), {
flags: "w",
}).write(content);
}
@ -500,9 +513,22 @@ function createService() {
// 创建 dict
async function createDict() {
const url = config.reqUrl + "/" + config.type + "/dict/info/types";
let p = "";
return axios
switch (config.type) {
case "app":
case "uniapp-x":
p = "/app";
break;
case "admin":
p = "/admin";
break;
}
const url = config.reqUrl + p + "/dict/info/types";
const text = await axios
.get(url)
.then((res) => {
const { code, data } = res.data as { code: number; data: any[] };
@ -520,6 +546,8 @@ async function createDict() {
.catch(() => {
error(`[cool-eps] Error${url}`);
});
return text || "";
}
// 创建 eps

View File

@ -5,6 +5,7 @@ import { getProxyTarget } from "./proxy";
import type { Config } from "../types";
import { virtual } from "./virtual";
import { assign, merge } from "lodash";
import { uniappX } from "./uniapp-x";
export function cool(options: Config.Options) {
// 应用类型admin | app
@ -44,5 +45,5 @@ export function cool(options: Config.Options) {
}
}
return [base(), virtual(), demo(options.demo)];
return [base(), virtual(), uniappX(), demo(options.demo)];
}

View File

@ -0,0 +1,370 @@
// @ts-ignore
import valueParser from "postcss-value-parser";
import { config } from "../config";
/**
* Tailwind CSS
*
*/
const TAILWIND_SAFE_CHAR_MAP: Record<string, string> = {
"[": "-",
"]": "-",
"(": "-",
")": "-",
"#": "-h-",
"!": "-i-",
"/": "-s-",
":": "-c-",
",": "-2c-",
};
/**
* Tailwind CSS
* 便
*/
const TAILWIND_CLASS_PREFIXES: string[] = [
// 间距
"p-",
"px-",
"py-",
"pt-",
"pr-",
"pb-",
"pl-",
"m-",
"mx-",
"my-",
"mt-",
"mr-",
"mb-",
"ml-",
"gap-",
"gap-x-",
"gap-y-",
"space-x-",
"space-y-",
"inset-",
"top-",
"right-",
"bottom-",
"left-",
// 尺寸
"w-",
"h-",
"min-w-",
"min-h-",
"max-w-",
"max-h-",
// 排版
"text-",
"font-",
"leading-",
"tracking-",
"indent-",
// 边框
"border-",
"border-t-",
"border-r-",
"border-b-",
"border-l-",
"rounded-",
"rounded-t-",
"rounded-r-",
"rounded-b-",
"rounded-l-",
"rounded-tl-",
"rounded-tr-",
"rounded-br-",
"rounded-bl-",
// 效果
"shadow-",
"blur-",
"brightness-",
"contrast-",
"drop-shadow-",
"grayscale-",
"hue-rotate-",
"invert-",
"saturate-",
"sepia-",
"backdrop-blur-",
"backdrop-brightness-",
"backdrop-contrast-",
"backdrop-grayscale-",
"backdrop-hue-rotate-",
"backdrop-invert-",
"backdrop-opacity-",
"backdrop-saturate-",
"backdrop-sepia-",
// 动画
"transition-",
"duration-",
"delay-",
"animate-",
// 变换
"translate-x-",
"translate-y-",
"rotate-",
"scale-",
"scale-x-",
"scale-y-",
"skew-x-",
"skew-y-",
"origin-",
// 布局
"columns-",
"break-after-",
"break-before-",
"break-inside-",
// Flexbox 和 Grid
"basis-",
"grow-",
"shrink-",
"grid-cols-",
"grid-rows-",
"col-span-",
"row-span-",
"col-start-",
"col-end-",
"row-start-",
"row-end-",
// SVG
"stroke-",
"stroke-w-",
"fill-",
];
/**
* Tailwind CSS
* CSS
*/
const TAILWIND_COLOR_VARS: Record<string, number> = {
"--tw-text-opacity": 1,
"--tw-bg-opacity": 1,
};
/**
*
* @param value
* @param isSelector truefalse
* @returns
*/
function toSafeTailwindClass(value: string, isSelector: boolean = false): string {
// 处理任意值语法(如 w-[100px]
const arbitrary = value.match(/^(.+?)-\[(.*?)\]$/);
if (arbitrary) {
if (isSelector) return value;
const [, prefix, content] = arbitrary;
const safePrefix = toSafeTailwindClass(prefix, isSelector);
const safeContent = content.replace(/[^\d.\w]/g, "-");
return `${safePrefix}-${safeContent}`;
}
let safeValue = value;
// 移除转义字符
if (safeValue.includes("\\")) {
safeValue = safeValue.replace(/\\/g, "");
}
// 替换特殊字符
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
const reg = new RegExp("\\" + char, "g");
if (reg.test(safeValue)) {
safeValue = safeValue.replace(reg, rep);
}
}
return safeValue;
}
/**
* rgb rgb(234 179 8 / 0.1) rgba
* @param value rgb
* @returns rgba
*/
function rgbToRgba(value: string): string {
const match = value.match(/rgb\(([\d\s]+)\/\s*([\d.]+)\)/);
if (match) {
const [, rgb, alpha] = match;
const [r, g, b] = rgb.split(/\s+/);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
return value;
}
interface PostcssRemToRpxOptions {
remUnit?: number;
remPrecision?: number;
rpxRatio?: number;
}
/**
* PostCSS rem rpx Tailwind
* @param options
* @returns PostCSS
*/
function postcssRemToRpx(options: PostcssRemToRpxOptions) {
return {
postcssPlugin: "vite-cool-uniappx-remToRpx",
prepare() {
const handledSelectors = new Set<string>();
const { remUnit = 16, remPrecision = 6, rpxRatio = 2 } = options;
const factor = remUnit * rpxRatio;
return {
Rule(rule: any) {
const sel = rule.selector;
if (handledSelectors.has(sel)) return;
const safeSel = toSafeTailwindClass(sel, true);
if (safeSel !== sel) {
rule.selector = safeSel;
handledSelectors.add(sel);
}
},
Declaration(decl: any) {
if (decl.value.includes("/* no-rem */")) return;
if (TAILWIND_COLOR_VARS[decl.prop]) {
decl.remove();
return;
}
if (decl.value.includes("rgb(") && decl.value.includes("/")) {
decl.value = rgbToRgba(decl.value);
}
if (decl.value.includes("rpx") && decl.parent.selector.includes("text-")) {
decl.prop = "font-size";
}
const parsed = valueParser(decl.value);
let changed = false;
parsed.walk((node: any) => {
if (node.type === "word") {
// rem 转 rpx
const unit = valueParser.unit(node.value);
if (unit?.unit === "rem") {
const num = unit.number;
const precision = (num.split(".")[1] || "").length;
const rpxVal = (parseFloat(num) * factor)
.toFixed(precision || remPrecision)
.replace(/\.?0+$/, "");
node.value = `${rpxVal}rpx`;
changed = true;
}
// 特殊字符处理
if (node.value.includes(".") || /[[\]()#!/:,]/.test(node.value)) {
const safe = toSafeTailwindClass(node.value, true);
if (safe !== node.value) {
node.value = safe;
changed = true;
}
}
}
// 处理 var(--tw-xxx)
if (node.type === "function" && node.value === "var") {
if (node.nodes.length > 0 && node.nodes[0].value.startsWith("--tw-")) {
node.type = "word";
node.value = TAILWIND_COLOR_VARS[node.nodes[0].value];
changed = true;
}
}
});
if (changed) {
decl.value = parsed.toString();
}
},
};
},
};
}
postcssRemToRpx.postcss = true;
interface TailwindTransformOptions extends PostcssRemToRpxOptions {}
/**
* Vite .uvue Tailwind
* rem rpx PostCSS
* @param options
* @returns Vite
*/
function tailwindTransformPlugin(options: TailwindTransformOptions = {}) {
const merged: Required<TailwindTransformOptions> = {
remUnit: 16,
remPrecision: 6,
rpxRatio: 2,
...options,
};
return {
name: "vite-cool-uniappx-tailwind",
enforce: "pre" as const,
config() {
return {
css: {
postcss: {
plugins: [postcssRemToRpx(merged)],
},
},
};
},
transform(code: string, id: string) {
if (!id.includes(".uvue")) return null;
let resultCode = code;
const tplMatch = resultCode.match(/<template>([\s\S]*?)<\/template>/);
if (!tplMatch?.[1]) return null;
let tpl = tplMatch[1];
const tplOrigin = tpl;
TAILWIND_CLASS_PREFIXES.forEach((prefix) => {
for (const [char, rep] of Object.entries(TAILWIND_SAFE_CHAR_MAP)) {
const reg = new RegExp(`(${prefix}[^\\s'"]*?\\${char}[^\\s'"]*?)`, "g");
const matches = [...tpl.matchAll(reg)];
matches.forEach((m) => {
const raw = m[1];
const safe = raw.replace(new RegExp("\\" + char, "g"), rep);
if (process.env.NODE_ENV === "development") {
console.log(`类名转换: ${raw}${safe}`);
}
tpl = tpl.replace(raw, safe);
});
}
});
if (tpl !== tplOrigin) {
resultCode = resultCode.replace(tplMatch[0], `<template>${tpl}</template>`);
return {
code: resultCode,
map: { mappings: "" },
};
}
return null;
},
};
}
/**
* uniappX Tailwind
* @param options
* @returns Vite
*/
export function uniappX(options?: { tailwind?: TailwindTransformOptions }) {
if (config.type == "uniapp-x") {
return [tailwindTransformPlugin(options?.tailwind)];
}
return [];
}

View File

@ -7,6 +7,7 @@ import prettier from "prettier";
export function rootDir(path: string) {
switch (config.type) {
case "app":
case "uniapp-x":
return join(process.env.UNI_INPUT_DIR!, path);
default:

View File

@ -1,4 +1,4 @@
export declare type Type = "app" | "admin";
export declare type Type = "admin" | "app" | "uniapp-x";
export declare namespace Eps {
interface Column {
@ -76,7 +76,6 @@ export declare namespace Ctx {
}
export declare namespace Config {
type Type = "app" | "admin";
interface Eps {
// 是否开启Eps
enable: boolean;
@ -93,7 +92,7 @@ export declare namespace Config {
}
interface Options {
// 应用类型
type: Config.Type;
type: Type;
// 代理配置
proxy: any;
// Eps
@ -109,7 +108,7 @@ export declare namespace Config {
};
}
interface Data {
type: Config.Type;
type: Type;
reqUrl: string;
eps: Config.Eps;
demo: boolean;

1369
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -50,10 +50,19 @@ export default (): ModuleConfig => {
const loading = document.querySelector('#Loading');
if (loading) {
loading.querySelector('.preload__name')!.innerHTML = config.app.name;
loading.querySelector('.preload__title')!.innerHTML = t('正在加载资源...');
loading.querySelector('.preload__sub-title')!.innerHTML =
t('初次加载资源可能需要较多时间,请耐心等待');
const name = loading.querySelector('.preload__name');
const title = loading.querySelector('.preload__title');
const subTitle = loading.querySelector('.preload__sub-title');
if (name) {
name.innerHTML = config.app.name;
}
if (title) {
title.innerHTML = t('正在加载资源...');
}
if (subTitle) {
subTitle.innerHTML = t('初次加载资源可能需要较多时间,请耐心等待');
}
}
},
async onLoad() {