@dongmingzhixiu/vuexir 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gen-android.js +5 -2
- package/dist/gen-ios.js +145 -15
- package/package.json +1 -1
package/dist/gen-android.js
CHANGED
|
@@ -27,10 +27,13 @@ export function generateAndroid(opts = {}) {
|
|
|
27
27
|
if (/\.(ts|js|mts)$/.test(input) && existsSync(input)) {
|
|
28
28
|
return runTabs(dirname(input), { routerFile: input, outDirRoot: opts.outDirRoot, projectName: opts.projectName, bundleId: opts.bundleId, renderGradle: (p, b) => buildGradle(b), templateVersion: TEMPLATE_VERSION });
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// 目录入口:兼容 router.ts / index.ts(Vue 工程标准 src/router/index.ts)
|
|
31
|
+
const routerFile = ['router.ts', 'router.js', 'index.ts', 'index.js'].find((f) => existsSync(join(input, f)));
|
|
32
|
+
if (!routerFile) {
|
|
33
|
+
throw new Error(`[vrn] ${input} 缺少 router.ts/index.ts(多页模式需要路由文件;单页请用 gen-ios 路径或补 router.ts)`);
|
|
32
34
|
}
|
|
33
35
|
return runTabs(input, {
|
|
36
|
+
routerFile: join(input, routerFile),
|
|
34
37
|
outDirRoot: opts.outDirRoot,
|
|
35
38
|
projectName: opts.projectName,
|
|
36
39
|
bundleId: opts.bundleId,
|
package/dist/gen-ios.js
CHANGED
|
@@ -228,7 +228,14 @@ const RUNTIME_SWIFT = `final class Runtime {
|
|
|
228
228
|
|
|
229
229
|
/// M3 注册 @State 绑定:JS stateStore.<name> 写回 → <setter>(onAppear 时由 View 注册)
|
|
230
230
|
func bindState(_ name: String, _ setter: @escaping (Any) -> Void) {
|
|
231
|
-
|
|
231
|
+
// R-THEME:ui_skin 变化 → ThemeColor.currentSkin + UserDefaults 同步 → 各视图 @AppStorage("vrn.skin") 自动重绘换肤
|
|
232
|
+
stateBindings[name] = { v in
|
|
233
|
+
setter(v)
|
|
234
|
+
if name == "ui_skin" || name == "ui.skin", let s = v as? String {
|
|
235
|
+
ThemeColor.currentSkin = s
|
|
236
|
+
UserDefaults.standard.set(s, forKey: "vrn.skin")
|
|
237
|
+
}
|
|
238
|
+
}
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
/// M3 Swift → JS 推送:@State 变更(v-model 输出等)→ stateStore 同步(__fromSwift 防回环)
|
|
@@ -293,18 +300,18 @@ struct ${projectName}App: App {
|
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
|
|
296
|
-
/// R68:全局 UINavigationBar
|
|
303
|
+
/// R68:全局 UINavigationBar 外观(R-THEME:bg/ink/primary 走 ThemeColor 皮肤表)
|
|
297
304
|
func configureGlobalNavigationBar() {
|
|
298
305
|
let appearance = UINavigationBarAppearance()
|
|
299
306
|
appearance.configureWithOpaqueBackground()
|
|
300
|
-
appearance.backgroundColor = UIColor(
|
|
301
|
-
let titleColor = UIColor(
|
|
307
|
+
appearance.backgroundColor = UIColor(ThemeColor.color("bg"))
|
|
308
|
+
let titleColor = UIColor(ThemeColor.color("ink"))
|
|
302
309
|
appearance.titleTextAttributes = [.foregroundColor: titleColor]
|
|
303
310
|
appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
|
|
304
311
|
UINavigationBar.appearance().standardAppearance = appearance
|
|
305
312
|
UINavigationBar.appearance().scrollEdgeAppearance = appearance
|
|
306
313
|
UINavigationBar.appearance().compactAppearance = appearance
|
|
307
|
-
UINavigationBar.appearance().tintColor = UIColor(
|
|
314
|
+
UINavigationBar.appearance().tintColor = UIColor(ThemeColor.color("primary"))
|
|
308
315
|
}
|
|
309
316
|
`;
|
|
310
317
|
/** 屏/工程名 → PascalCase(my-app → MyApp;用于 Swift 类型名与 bundle id 派生) */
|
|
@@ -508,6 +515,8 @@ function runOne(input) {
|
|
|
508
515
|
const theme = buildTheme(themeTokens);
|
|
509
516
|
const themeSrc = generateThemeSource(theme);
|
|
510
517
|
const cssVars = loadCssVars(input);
|
|
518
|
+
// R-THEME:皮肤 token 表(var(--x) 按皮肤分组 → ThemeColors.swift,换肤原生跟随)
|
|
519
|
+
writeFileSync(join(srcDir, 'ThemeColors.swift'), buildThemeColorsSwift(loadSkinVars(input)));
|
|
511
520
|
const swift = generateScreenSource(screen, { warnings: genWarnings, theme, cssVars });
|
|
512
521
|
if (genWarnings.length > 0) {
|
|
513
522
|
console.log(`[gen-ios] 布局降级提示(${genWarnings.length} 条,R-H3/H4/H5):`);
|
|
@@ -518,7 +527,7 @@ function runOne(input) {
|
|
|
518
527
|
writeFileSync(join(srcDir, 'App.swift'), APP_SWIFT(projectName, viewName, screen.title));
|
|
519
528
|
writeFileSync(join(srcDir, 'recite.json'), JSON.stringify(screen, null, 2));
|
|
520
529
|
// 事件运行时(<script setup> 逻辑源码;无逻辑时生成空文件保持 Bundle 引用成立)
|
|
521
|
-
writeFileSync(join(srcDir, 'runtime.js'), runtimeSrc ?? '');
|
|
530
|
+
writeFileSync(join(srcDir, 'runtime.js'), injectSkinBridge(runtimeSrc ?? ''));
|
|
522
531
|
// R41 组件:每个 components/Xxx.vue → 独立 XxxView.swift(struct XxxView;页面引用 XxxView(attrs))
|
|
523
532
|
const compFiles = [];
|
|
524
533
|
for (const comp of components) {
|
|
@@ -599,11 +608,11 @@ ${tabs
|
|
|
599
608
|
.join('\n')}
|
|
600
609
|
}
|
|
601
610
|
.frame(height: 56)
|
|
602
|
-
.background(
|
|
611
|
+
.background(ThemeColor.color("surface-2"))
|
|
603
612
|
.overlay(Divider(), alignment: .top)
|
|
604
613
|
}
|
|
605
|
-
// R68
|
|
606
|
-
.background(${swiftColorExpr(rootBg)})
|
|
614
|
+
// R68:容器背景兜底——页面内容不足或底部安全区不露白(R-THEME:默认深色 → ThemeColor 皮肤 bg)
|
|
615
|
+
.background(${rootBg === '#0a0f1e' || rootBg === '#0a0F1E' || !rootBg ? 'ThemeColor.color("bg")' : swiftColorExpr(rootBg)})
|
|
607
616
|
${details.length > 0
|
|
608
617
|
? ` .navigationDestination(item: $pushedRoute) { route in
|
|
609
618
|
Group {
|
|
@@ -648,18 +657,18 @@ struct ${projectName}App: App {
|
|
|
648
657
|
}
|
|
649
658
|
}
|
|
650
659
|
|
|
651
|
-
/// R68:全局 UINavigationBar
|
|
660
|
+
/// R68:全局 UINavigationBar 外观(R-THEME:bg/ink/primary 走 ThemeColor 皮肤表)
|
|
652
661
|
func configureGlobalNavigationBar() {
|
|
653
662
|
let appearance = UINavigationBarAppearance()
|
|
654
663
|
appearance.configureWithOpaqueBackground()
|
|
655
|
-
appearance.backgroundColor = UIColor(
|
|
656
|
-
let titleColor = UIColor(
|
|
664
|
+
appearance.backgroundColor = UIColor(ThemeColor.color("bg"))
|
|
665
|
+
let titleColor = UIColor(ThemeColor.color("ink"))
|
|
657
666
|
appearance.titleTextAttributes = [.foregroundColor: titleColor]
|
|
658
667
|
appearance.largeTitleTextAttributes = [.foregroundColor: titleColor]
|
|
659
668
|
UINavigationBar.appearance().standardAppearance = appearance
|
|
660
669
|
UINavigationBar.appearance().scrollEdgeAppearance = appearance
|
|
661
670
|
UINavigationBar.appearance().compactAppearance = appearance
|
|
662
|
-
UINavigationBar.appearance().tintColor = UIColor(
|
|
671
|
+
UINavigationBar.appearance().tintColor = UIColor(ThemeColor.color("primary"))
|
|
663
672
|
}
|
|
664
673
|
`;
|
|
665
674
|
/** 计算页面 state 在全部 state 平铺数组中的下标(TAB_APP_SWIFT 生成 @State 命名 p<i>_<name> 用) */
|
|
@@ -689,6 +698,123 @@ function loadCssVars(appDir) {
|
|
|
689
698
|
}
|
|
690
699
|
return map;
|
|
691
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* R-THEME:JS 侧换肤桥——页面 bundle 的 ui.setSkin(skin) 调用后,
|
|
703
|
+
* 把皮肤广播进 stateStore.ui_skin → Proxy set → __setState → Swift bindState → ThemeColor.currentSkin → 原生换肤
|
|
704
|
+
* 注入位置:esbuild 产物中 ui store 的 setSkin 方法体开头(stateStore 在 RUNTIME 模板段,函数被调用时已就绪)
|
|
705
|
+
*/
|
|
706
|
+
function injectSkinBridge(js) {
|
|
707
|
+
if (!js || js.indexOf('setSkin') === -1)
|
|
708
|
+
return js;
|
|
709
|
+
return js.replace(/setSkin\s*\(\s*skin\s*\)\s*\{/g, 'setSkin(skin) { if(typeof stateStore!=="undefined"&&stateStore){try{stateStore["ui_skin"]=skin;}catch(e){}}');
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* R-THEME:按皮肤分组解析 tokens.css 的 var(--x)
|
|
713
|
+
* 选择器 → 皮肤名::root/.skin-warm → warm;.skin-mint → mint;.skin-lavender → lavender;.skin-night → night
|
|
714
|
+
* 返回 Map<skin, Map<varName, value>>;:root 覆盖块(半径/字体等)并入 warm
|
|
715
|
+
*/
|
|
716
|
+
function loadSkinVars(appDir) {
|
|
717
|
+
const root = findProjectRoot(appDir);
|
|
718
|
+
const skins = new Map();
|
|
719
|
+
const roots = [join(root, 'src/styles/tokens.css'), join(root, 'src/tokens.css'), join(root, 'styles/tokens.css')];
|
|
720
|
+
for (const p of roots) {
|
|
721
|
+
if (!existsSync(p))
|
|
722
|
+
continue;
|
|
723
|
+
const css = readFileSync(p, 'utf-8');
|
|
724
|
+
// 按 } 切块,取块头选择器判断皮肤归属
|
|
725
|
+
for (const block of css.split('}')) {
|
|
726
|
+
const selLine = block.split('{')[0] ?? '';
|
|
727
|
+
let skin = '';
|
|
728
|
+
if (/\.skin-warm/.test(selLine) || /:root/.test(selLine))
|
|
729
|
+
skin = 'warm';
|
|
730
|
+
else if (/\.skin-mint/.test(selLine))
|
|
731
|
+
skin = 'mint';
|
|
732
|
+
else if (/\.skin-lavender/.test(selLine))
|
|
733
|
+
skin = 'lavender';
|
|
734
|
+
else if (/\.skin-night/.test(selLine))
|
|
735
|
+
skin = 'night';
|
|
736
|
+
if (!skin)
|
|
737
|
+
continue;
|
|
738
|
+
const body = block.slice(block.indexOf('{') + 1);
|
|
739
|
+
let map = skins.get(skin);
|
|
740
|
+
if (!map) {
|
|
741
|
+
map = new Map();
|
|
742
|
+
skins.set(skin, map);
|
|
743
|
+
}
|
|
744
|
+
for (const m of body.matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g)) {
|
|
745
|
+
map.set(m[1], m[2].trim());
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return skins;
|
|
750
|
+
}
|
|
751
|
+
/** var 值 → Swift Color 字面量(hex/rgb/rgba;非颜色返回 null) */
|
|
752
|
+
function cssValueToSwiftColor(v) {
|
|
753
|
+
const t = v.trim();
|
|
754
|
+
if (t.startsWith('#')) {
|
|
755
|
+
let hex = t.slice(1);
|
|
756
|
+
if (hex.length === 3)
|
|
757
|
+
hex = hex.split('').map((h) => h + h).join('');
|
|
758
|
+
const n = parseInt(hex, 16);
|
|
759
|
+
return `Color(red: ${(n >> 16) & 255}/255.0, green: ${(n >> 8) & 255}/255.0, blue: ${n & 255}/255.0)`;
|
|
760
|
+
}
|
|
761
|
+
const m = t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/);
|
|
762
|
+
if (m) {
|
|
763
|
+
const a = m[4] !== undefined ? +m[4] : 1;
|
|
764
|
+
return `Color(red: ${+m[1]}/255.0, green: ${+m[2]}/255.0, blue: ${+m[3]}/255.0, opacity: ${a})`;
|
|
765
|
+
}
|
|
766
|
+
if (/^rgba?\(\s*[\d.]+\s*[,\s]/.test(t))
|
|
767
|
+
return null;
|
|
768
|
+
if (t.startsWith('var(') || /^[a-z]+$/i.test(t))
|
|
769
|
+
return null;
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
/** 生成 ThemeColors.swift:4 皮肤 token 表 + currentSkin + color(name) 解析 */
|
|
773
|
+
function buildThemeColorsSwift(skins) {
|
|
774
|
+
const order = ['warm', 'mint', 'lavender', 'night'];
|
|
775
|
+
const lines = [];
|
|
776
|
+
lines.push('import SwiftUI');
|
|
777
|
+
lines.push('');
|
|
778
|
+
lines.push('/// Vuexir 皮肤 token 表:由 tokens.css 的 var(--x) 按皮肤分组生成');
|
|
779
|
+
lines.push('/// 换肤:JS ui.setSkin → stateStore.ui_skin → __setState → ThemeColor.currentSkin → 视图重建自动取新色');
|
|
780
|
+
lines.push('enum ThemeColor {');
|
|
781
|
+
lines.push(' static var currentSkin = UserDefaults.standard.string(forKey: "vrn.skin") ?? "warm"');
|
|
782
|
+
lines.push(' static func color(_ name: String) -> Color {');
|
|
783
|
+
lines.push(' let skin = UserDefaults.standard.string(forKey: "vrn.skin") ?? currentSkin');
|
|
784
|
+
lines.push(' switch skin {');
|
|
785
|
+
const names = new Set();
|
|
786
|
+
for (const skin of order)
|
|
787
|
+
names.add(skin);
|
|
788
|
+
for (const skin of order) {
|
|
789
|
+
const map = skins.get(skin);
|
|
790
|
+
if (!map)
|
|
791
|
+
continue;
|
|
792
|
+
const ref = skin === 'warm' ? 'warmMap' : `${skin}Map`;
|
|
793
|
+
const fallback = skin === 'warm' ? 'Color.black' : 'warmMap[name] ?? Color.black';
|
|
794
|
+
lines.push(` case "${skin}": return ${ref}[name] ?? ${fallback}`);
|
|
795
|
+
}
|
|
796
|
+
lines.push(' default: return warmMap[name] ?? Color.black');
|
|
797
|
+
lines.push(' }');
|
|
798
|
+
lines.push(' }');
|
|
799
|
+
for (const skin of order) {
|
|
800
|
+
const map = skins.get(skin);
|
|
801
|
+
if (!map)
|
|
802
|
+
continue;
|
|
803
|
+
const entries = [];
|
|
804
|
+
for (const [k, v] of map) {
|
|
805
|
+
const c = cssValueToSwiftColor(v);
|
|
806
|
+
if (c)
|
|
807
|
+
entries.push(` "${k.slice(2)}": ${c}`);
|
|
808
|
+
}
|
|
809
|
+
if (entries.length === 0)
|
|
810
|
+
continue;
|
|
811
|
+
lines.push(` private static let ${skin === 'warm' ? 'warmMap' : `${skin}Map`}: [String: Color] = [`);
|
|
812
|
+
lines.push(entries.join(',\n'));
|
|
813
|
+
lines.push(' ]');
|
|
814
|
+
}
|
|
815
|
+
lines.push('}');
|
|
816
|
+
return lines.join('\n') + '\n';
|
|
817
|
+
}
|
|
692
818
|
function runTabs(appDir, opts = {}) {
|
|
693
819
|
const routerSrc = readFileSync(opts.routerFile ?? join(appDir, 'router.ts'), 'utf-8');
|
|
694
820
|
const { tabs, details, warnings: routerWarnings } = parseRouterV2(routerSrc, findProjectRoot(appDir), opts.routerFile ?? join(appDir, 'router.ts'));
|
|
@@ -696,6 +822,9 @@ function runTabs(appDir, opts = {}) {
|
|
|
696
822
|
console.log(`[gen-ios] 路由提示: ${w}`);
|
|
697
823
|
console.log(`[gen-ios] 多页模式: router.ts → ${tabs.length} 个 tab 页面` + (details.length ? ` + ${details.length} 个 push 页面` : ''));
|
|
698
824
|
const cssVars = loadCssVars(appDir);
|
|
825
|
+
// R-THEME:皮肤 token 表(var(--x) 按皮肤分组 → ThemeColors.swift,换肤原生跟随)
|
|
826
|
+
const skinVars = loadSkinVars(appDir);
|
|
827
|
+
const themeColorsSwift = buildThemeColorsSwift(skinVars);
|
|
699
828
|
const projectName = FORCED_PROJECT_NAME ?? toIdentifier(basename(appDir));
|
|
700
829
|
const bundleId = FORCED_BUNDLE_ID ?? `com.vrn.${projectName.toLowerCase()}`;
|
|
701
830
|
const outDir = join(OUT_ROOT, projectName);
|
|
@@ -703,6 +832,7 @@ function runTabs(appDir, opts = {}) {
|
|
|
703
832
|
// 写前清空源目录:删除页面后残留的旧 View/runtime 仍会进 Xcode 工程(幽灵页面)
|
|
704
833
|
rmSync(srcDir, { recursive: true, force: true });
|
|
705
834
|
mkdirSync(srcDir, { recursive: true });
|
|
835
|
+
writeFileSync(join(srcDir, 'ThemeColors.swift'), themeColorsSwift);
|
|
706
836
|
// R97 P2:把可独立运行的 utils 打成 IIFE,前置到每个页面 runtime 之前,
|
|
707
837
|
// 让 computed 里引用的外部函数(countChars / normalize …)在原生 JS 运行时可用
|
|
708
838
|
const utilsBundle = bundleProjectUtils(findProjectRoot(appDir));
|
|
@@ -761,7 +891,7 @@ function runTabs(appDir, opts = {}) {
|
|
|
761
891
|
const pageBundle = bundlePageScript(tab.file, findProjectRoot(appDir));
|
|
762
892
|
if (pageBundle)
|
|
763
893
|
console.log(`[gen-ios] 页面脚本打包:${tab.id}(${pageBundle.names.length} 个绑定)`);
|
|
764
|
-
writeFileSync(join(srcDir, `runtime_${pageId}.js`), utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? ''));
|
|
894
|
+
writeFileSync(join(srcDir, `runtime_${pageId}.js`), injectSkinBridge(utilsBundle.code + '\n' + (pageBundle?.code ?? '') + '\n' + (r.runtime ?? '')));
|
|
765
895
|
pageFiles.push(`${pageId}View.swift`);
|
|
766
896
|
pageRuntimes.push({ id: pageId, src: r.runtime ?? '' });
|
|
767
897
|
pageStates.push({
|
|
@@ -821,7 +951,7 @@ function runTabs(appDir, opts = {}) {
|
|
|
821
951
|
for (const w of genWarnings)
|
|
822
952
|
console.log(`[gen-ios] 生成提示(${d.id},push 页): ${w}`);
|
|
823
953
|
writeFileSync(join(srcDir, viewFile), swift.replace(/Text\("(_vrn[EAC][0-9]+)"\)/g, 'Text($1)'));
|
|
824
|
-
writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), utilsBundle.code + '\n' + (r.runtime ?? ''));
|
|
954
|
+
writeFileSync(join(srcDir, `runtime_${pageId}Detail.js`), injectSkinBridge(utilsBundle.code + '\n' + (r.runtime ?? '')));
|
|
825
955
|
pageFiles.push(viewFile);
|
|
826
956
|
pageRuntimes.push({ id: `${pageId}Detail`, src: r.runtime ?? '' });
|
|
827
957
|
detailPages.push({ id: d.id, title: d.title, view: viewName, path: d.path });
|