create-swdg-frontend 0.2.0 → 0.2.2

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.
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 发布物冒烟:npm pack create-swdg-frontend → 从 tarball 跑 CLI → 无 GIS 项目能 build。
4
+ */
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+ import { spawn } from 'node:child_process';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const rootDir = path.resolve(
12
+ path.dirname(fileURLToPath(import.meta.url)),
13
+ '..'
14
+ );
15
+ const createDir = path.join(rootDir, 'create-swdg');
16
+ const tmpBase = path.join(os.tmpdir(), 'swdg-pack-test');
17
+
18
+ function run(cmd, args, cwd, env = process.env) {
19
+ return new Promise((resolve, reject) => {
20
+ const child = spawn(cmd, args, {
21
+ cwd,
22
+ stdio: 'inherit',
23
+ shell: cmd === 'pnpm' || cmd === 'npm',
24
+ env,
25
+ });
26
+ child.on('exit', (code) => {
27
+ if (code === 0) resolve();
28
+ else reject(new Error(`${cmd} ${args.join(' ')} exited ${code} in ${cwd}`));
29
+ });
30
+ });
31
+ }
32
+
33
+ async function readJson(filePath) {
34
+ return JSON.parse(await fs.readFile(filePath, 'utf8'));
35
+ }
36
+
37
+ async function main() {
38
+ console.log('\n▶ test:pack\n');
39
+
40
+ const pkg = await readJson(path.join(createDir, 'package.json'));
41
+ const version = pkg.version;
42
+ const packName = `${pkg.name}-${version}.tgz`;
43
+
44
+ await fs.rm(tmpBase, { recursive: true, force: true });
45
+ await fs.mkdir(tmpBase, { recursive: true });
46
+
47
+ console.log(' · npm pack (create-swdg-frontend)');
48
+ await run('npm', ['pack', '--pack-destination', tmpBase], createDir);
49
+
50
+ const tgzPath = path.join(tmpBase, packName);
51
+ try {
52
+ await fs.access(tgzPath);
53
+ } catch {
54
+ throw new Error(`expected tarball missing: ${tgzPath}`);
55
+ }
56
+
57
+ const workspace = path.join(tmpBase, 'workspace');
58
+ await fs.mkdir(workspace, { recursive: true });
59
+
60
+ console.log(' · install tarball');
61
+ await run('npm', ['install', tgzPath], workspace);
62
+
63
+ const cliBin = path.join(
64
+ workspace,
65
+ 'node_modules',
66
+ pkg.name,
67
+ 'bin/index.js'
68
+ );
69
+ try {
70
+ await fs.access(cliBin);
71
+ } catch {
72
+ throw new Error(`CLI bin missing in packed install: ${cliBin}`);
73
+ }
74
+
75
+ const appName = 'pack-smoke-app';
76
+ console.log(' · CLI create --no-gis');
77
+ await run(
78
+ process.execPath,
79
+ [cliBin, appName, '--no-gis', '--no-install', '--no-git', '--no-verify'],
80
+ workspace
81
+ );
82
+
83
+ const appDir = path.join(workspace, appName);
84
+ const appPkg = await readJson(path.join(appDir, 'package.json'));
85
+ if (appPkg.dependencies?.cesium) {
86
+ throw new Error('packed CLI produced project with cesium dependency');
87
+ }
88
+
89
+ console.log(' · generated project install + typecheck + build');
90
+ await run('pnpm', ['install'], appDir);
91
+ await run('pnpm', ['run', 'typecheck'], appDir);
92
+ await run('pnpm', ['run', 'build'], appDir);
93
+
94
+ await fs.rm(tmpBase, { recursive: true, force: true });
95
+ console.log('\n✓ test:pack passed\n');
96
+ }
97
+
98
+ main().catch((err) => {
99
+ console.error(`\n✗ test:pack failed: ${err.message}\n`);
100
+ process.exit(1);
101
+ });
@@ -6,8 +6,9 @@
6
6
  * 0. sync — 生成 create-swdg/template(CI 全新 clone 必需)
7
7
  * 1. verify — typecheck + eslint
8
8
  * 2. unit — vitest
9
- * 3. scaffold — 脚手架集成(无 install
9
+ * 3. scaffold — 脚手架集成(含 --no-gis 生成项目 build
10
10
  * 4. template — template install/typecheck/build
11
+ * 5. pack — npm pack 后从 tarball 跑 CLI 并 build
11
12
  */
12
13
  import { spawn } from 'node:child_process';
13
14
  import path from 'node:path';
@@ -39,6 +40,11 @@ const stages = [
39
40
  cmd: process.execPath,
40
41
  args: ['scripts/test-template.mjs'],
41
42
  },
43
+ {
44
+ name: 'pack',
45
+ cmd: process.execPath,
46
+ args: ['scripts/test-pack.mjs'],
47
+ },
42
48
  ];
43
49
 
44
50
  function runStage(stage) {
@@ -88,6 +88,23 @@ async function assertGisProject(dir) {
88
88
  }
89
89
  }
90
90
 
91
+ function runInDir(cwd, cmd, args, env = process.env) {
92
+ return new Promise((resolve, reject) => {
93
+ const child = spawn(cmd, args, {
94
+ cwd,
95
+ stdio: 'inherit',
96
+ shell: cmd === 'pnpm',
97
+ env,
98
+ });
99
+ child.on('exit', (code) => {
100
+ if (code === 0) resolve();
101
+ else
102
+ reject(new Error(`${cmd} ${args.join(' ')} exited ${code} in ${cwd}`));
103
+ });
104
+ child.on('error', reject);
105
+ });
106
+ }
107
+
91
108
  function runCli(cwd, args) {
92
109
  return new Promise((resolve, reject) => {
93
110
  const child = spawn(process.execPath, [cliBin, ...args], {
@@ -183,6 +200,19 @@ async function main() {
183
200
  }
184
201
  });
185
202
 
203
+ const buildDir = path.join(tmpBase, 'proj-no-gis-build');
204
+ await runCase('no-gis generated project install + typecheck + build', async () => {
205
+ await rmDir(buildDir);
206
+ await createProject(buildDir, {
207
+ projectName: 'swdg-no-gis-build',
208
+ gis: false,
209
+ });
210
+ await assertNoGisProject(buildDir);
211
+ await runInDir(buildDir, 'pnpm', ['install']);
212
+ await runInDir(buildDir, 'pnpm', ['run', 'typecheck']);
213
+ await runInDir(buildDir, 'pnpm', ['run', 'build']);
214
+ });
215
+
186
216
  await rmDir(tmpBase);
187
217
 
188
218
  console.log(`\n scaffold: ${passed} passed, ${failed} failed\n`);
@@ -229,7 +229,7 @@ watch(
229
229
 
230
230
  .onboarding-deck--fullscreen {
231
231
  min-height: calc(100vh - 52px);
232
- padding: 24px;
232
+ padding: var(--space-page-x);
233
233
  background: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%);
234
234
  }
235
235
 
@@ -267,7 +267,7 @@ watch(
267
267
  flex: 1;
268
268
  display: flex;
269
269
  flex-direction: column;
270
- padding: 28px 32px;
270
+ padding: clamp(1.25rem, 4vw, 1.75rem) clamp(1rem, 4vw, 2rem);
271
271
  border-radius: var(--shell-radius);
272
272
  background: var(--shell-surface);
273
273
  box-shadow: var(--shell-shadow);
@@ -446,4 +446,35 @@ watch(
446
446
  background: var(--shell-accent);
447
447
  transform: scale(1.15);
448
448
  }
449
+
450
+ @media (width <= 47.99rem) {
451
+ .onboarding-deck__header {
452
+ flex-direction: column;
453
+ align-items: stretch;
454
+ }
455
+
456
+ .onboarding-deck__exit {
457
+ align-self: flex-end;
458
+ }
459
+
460
+ .onboarding-deck__footer {
461
+ flex-direction: column;
462
+ align-items: stretch;
463
+ }
464
+
465
+ .onboarding-deck__nav-btn {
466
+ min-width: 0;
467
+ width: 100%;
468
+ }
469
+
470
+ .onboarding-deck__dots {
471
+ order: -1;
472
+ }
473
+
474
+ .onboarding-slide__table {
475
+ display: block;
476
+ overflow-x: auto;
477
+ -webkit-overflow-scrolling: touch;
478
+ }
479
+ }
449
480
  </style>
@@ -1,9 +1,11 @@
1
1
  import { createApp } from 'vue';
2
2
  import { createPinia } from 'pinia';
3
3
 
4
+ import './styles/tokens.css';
4
5
  import './styles/global.css';
6
+ import './styles/responsive.css';
5
7
  import './styles/shell.css';
6
- import './styles/alerts.less';
8
+ import './styles/alerts.css';
7
9
 
8
10
  import App from './App.vue';
9
11
  import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
@@ -0,0 +1,75 @@
1
+ @keyframes fade-in {
2
+ from {
3
+ opacity: 0;
4
+ }
5
+
6
+ to {
7
+ opacity: 1;
8
+ }
9
+ }
10
+
11
+ @keyframes fade-out {
12
+ from {
13
+ opacity: 1;
14
+ }
15
+
16
+ to {
17
+ opacity: 0;
18
+ }
19
+ }
20
+
21
+ .alerts-container {
22
+ position: fixed;
23
+ top: clamp(0.75rem, 3vw, 1.25rem);
24
+ left: 50%;
25
+ z-index: 9999;
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ gap: 0.625rem;
30
+ width: min(100% - 2rem, 20rem);
31
+ transform: translateX(-50%);
32
+ }
33
+
34
+ .alert {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ gap: 0.625rem;
39
+ width: 100%;
40
+ padding: 0.625rem 1.25rem;
41
+ border-radius: 5px;
42
+ color: #fff;
43
+ font-size: clamp(0.875rem, 2.5vw, 1rem);
44
+ box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
45
+ animation: fade-in 0.5s ease-out;
46
+ }
47
+
48
+ .alert div:last-child {
49
+ font-size: larger;
50
+ line-height: 100%;
51
+ cursor: pointer;
52
+ user-select: none;
53
+ vertical-align: middle;
54
+ transform: translateX(5px);
55
+ }
56
+
57
+ .alert div:last-child:hover {
58
+ color: #f4f4f4;
59
+ }
60
+
61
+ .alert-success {
62
+ background-color: #67c23a;
63
+ }
64
+
65
+ .alert-warning {
66
+ background-color: #e6a23c;
67
+ }
68
+
69
+ .alert-error {
70
+ background-color: #f56c6c;
71
+ }
72
+
73
+ .alert-info {
74
+ background-color: #909399;
75
+ }
@@ -5,7 +5,7 @@ body {
5
5
  margin: 0;
6
6
  padding: 0;
7
7
  font-family: '思源黑体', 'Microsoft YaHei', '黑体', '宋体', Arial, sans-serif;
8
- font-size: 14px;
8
+ font-size: var(--font-size-fluid-body, 14px);
9
9
  }
10
10
 
11
11
  /* Flex 基础设置 */
@@ -167,25 +167,7 @@ body {
167
167
  height: 100%;
168
168
  }
169
169
 
170
- .grid-h-one-fourth {
171
- display: grid;
172
- grid-template-columns: repeat(4, 1fr);
173
- }
174
-
175
- .grid-h-one-third {
176
- display: grid;
177
- grid-template-columns: repeat(3, 1fr);
178
- }
179
-
180
- .grid-h-half {
181
- display: grid;
182
- grid-template-columns: repeat(2, 1fr);
183
- }
184
-
185
- .grid-h-ratio-1-2 {
186
- display: grid;
187
- grid-template-columns: 1fr 2fr;
188
- }
170
+ /* 固定列 grid 的响应式断点见 responsive.css */
189
171
 
190
172
  .no-wrap {
191
173
  white-space: nowrap;
@@ -266,7 +248,6 @@ body {
266
248
  .common-dialog-form-item {
267
249
  display: flex;
268
250
  align-items: center;
269
- width: 425px;
270
251
  height: 32px;
271
252
  }
272
253
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 响应式工具类:补齐 global.css 中固定列数 grid 在小屏下的表现
3
+ */
4
+
5
+ .grid-h-one-fourth,
6
+ .grid-h-one-third,
7
+ .grid-h-half,
8
+ .grid-h-ratio-1-2 {
9
+ display: grid;
10
+ gap: 1rem;
11
+ grid-template-columns: 1fr;
12
+ }
13
+
14
+ @media (min-width: 40rem) {
15
+ .grid-h-half,
16
+ .grid-h-ratio-1-2 {
17
+ grid-template-columns: repeat(2, 1fr);
18
+ }
19
+
20
+ .grid-h-one-third {
21
+ grid-template-columns: repeat(2, 1fr);
22
+ }
23
+
24
+ .grid-h-one-fourth {
25
+ grid-template-columns: repeat(2, 1fr);
26
+ }
27
+ }
28
+
29
+ @media (min-width: 48rem) {
30
+ .grid-h-one-third {
31
+ grid-template-columns: repeat(3, 1fr);
32
+ }
33
+
34
+ .grid-h-ratio-1-2 {
35
+ grid-template-columns: 1fr 2fr;
36
+ }
37
+ }
38
+
39
+ @media (min-width: 64rem) {
40
+ .grid-h-one-fourth {
41
+ grid-template-columns: repeat(4, 1fr);
42
+ }
43
+ }
44
+
45
+ .common-dialog-form-item {
46
+ width: min(26.5rem, 100%);
47
+ max-width: 100%;
48
+ }
@@ -18,9 +18,11 @@
18
18
 
19
19
  .shell-header {
20
20
  display: flex;
21
+ flex-wrap: wrap;
21
22
  align-items: center;
22
23
  justify-content: space-between;
23
- padding: 16px 28px;
24
+ gap: 0.75rem;
25
+ padding: var(--space-page-x) var(--space-page-x);
24
26
  color: #f8fafc;
25
27
  border-bottom: 1px solid rgb(255 255 255 / 8%);
26
28
  backdrop-filter: blur(8px);
@@ -34,7 +36,7 @@
34
36
 
35
37
  .shell-header__title {
36
38
  margin: 0;
37
- font-size: 18px;
39
+ font-size: var(--font-size-fluid-title);
38
40
  font-weight: 600;
39
41
  letter-spacing: 0.02em;
40
42
  }
@@ -45,14 +47,14 @@
45
47
  }
46
48
 
47
49
  .shell-main {
48
- max-width: 960px;
50
+ width: min(100%, var(--content-max));
49
51
  margin: 0 auto;
50
- padding: 40px 24px 64px;
52
+ padding: var(--space-page-y) var(--space-page-x) clamp(2.5rem, 8vw, 4rem);
51
53
  }
52
54
 
53
55
  .shell-hero {
54
- margin-bottom: 32px;
55
- padding: 32px;
56
+ margin-bottom: clamp(1.25rem, 4vw, 2rem);
57
+ padding: clamp(1.25rem, 4vw, 2rem);
56
58
  border-radius: var(--shell-radius);
57
59
  background: var(--shell-surface);
58
60
  box-shadow: var(--shell-shadow);
@@ -60,7 +62,7 @@
60
62
 
61
63
  .shell-hero h2 {
62
64
  margin: 0 0 8px;
63
- font-size: 26px;
65
+ font-size: clamp(1.25rem, 3vw, 1.625rem);
64
66
  font-weight: 700;
65
67
  color: var(--shell-text);
66
68
  }
@@ -81,8 +83,8 @@
81
83
 
82
84
  .shell-grid {
83
85
  display: grid;
84
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
85
- gap: 16px;
86
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, 16.25rem), 1fr));
87
+ gap: 1rem;
86
88
  }
87
89
 
88
90
  .shell-card {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 布局与设计 token(与 Tailwind 默认断点对齐,便于团队共识)
3
+ * 自适应优先:fluid spacing + CSS Grid/Flex,无需额外 UI 框架
4
+ */
5
+ :root {
6
+ --bp-sm: 40rem; /* 640px */
7
+ --bp-md: 48rem; /* 768px */
8
+ --bp-lg: 64rem; /* 1024px */
9
+ --bp-xl: 80rem; /* 1280px */
10
+
11
+ --content-max: 60rem;
12
+ --space-page-x: clamp(1rem, 4vw, 1.75rem);
13
+ --space-page-y: clamp(1.5rem, 5vw, 2.5rem);
14
+ --font-size-fluid-body: clamp(0.875rem, 1.2vw, 1rem);
15
+ --font-size-fluid-title: clamp(1.375rem, 2.5vw, 1.625rem);
16
+ }
@@ -67,13 +67,6 @@ export default defineConfig(({ mode }) => {
67
67
  '@interface': path.resolve(__dirname, './interface'),
68
68
  },
69
69
  },
70
- css: {
71
- preprocessorOptions: {
72
- less: {
73
- javascriptEnabled: true,
74
- },
75
- },
76
- },
77
70
  server: {
78
71
  host: '0.0.0.0',
79
72
  port: 3000,
@@ -1,85 +0,0 @@
1
- // 定义颜色变量
2
- @success-color: #67c23a;
3
- @warning-color: #e6a23c;
4
- @error-color: #f56c6c;
5
- @info-color: #909399;
6
- @text-color: #fff;
7
- @shadow-color: rgba(0, 0, 0, 0.1);
8
-
9
- // 基础字体大小和间距
10
- @font-size: 16px;
11
- @padding: 10px 20px;
12
-
13
- // 动画渐变定义
14
- @keyframes fadeIn {
15
- from {
16
- opacity: 0;
17
- }
18
- to {
19
- opacity: 1;
20
- }
21
- }
22
-
23
- @keyframes fadeOut {
24
- from {
25
- opacity: 1;
26
- }
27
- to {
28
- opacity: 0;
29
- }
30
- }
31
-
32
- .alerts-container {
33
- position: fixed;
34
- top: 20px;
35
- left: 50%;
36
- transform: translateX(-50%);
37
- z-index: 9999;
38
- display: flex;
39
- flex-direction: column;
40
- align-items: center;
41
- gap: 10px;
42
- }
43
-
44
- .alert {
45
- padding: @padding;
46
- color: @text-color;
47
- border-radius: 5px;
48
- box-shadow: 0 2px 8px @shadow-color;
49
- font-size: @font-size;
50
- display: flex;
51
- align-items: center;
52
- justify-content: space-between;
53
- gap: 10px;
54
- width: 250px;
55
- animation: fadeIn 0.5s ease-out;
56
-
57
- div:last-child {
58
- font-size: larger;
59
- cursor: pointer;
60
- user-select: none;
61
- line-height: 100%;
62
- vertical-align: middle;
63
- transform: translateX(5px);
64
-
65
- &:hover {
66
- color: #f4f4f4;
67
- }
68
- }
69
- }
70
-
71
- .alert-success {
72
- background-color: @success-color;
73
- }
74
-
75
- .alert-warning {
76
- background-color: @warning-color;
77
- }
78
-
79
- .alert-error {
80
- background-color: @error-color;
81
- }
82
-
83
- .alert-info {
84
- background-color: @info-color;
85
- }