create-simpleadmin-ui 2.0.0

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.
Files changed (55) hide show
  1. package/README.md +53 -0
  2. package/bin/create-simpleadmin-ui.mjs +2 -0
  3. package/package.json +35 -0
  4. package/src/index.mjs +557 -0
  5. package/src/sync-template.mjs +64 -0
  6. package/template/.env.development +4 -0
  7. package/template/.env.production.example +4 -0
  8. package/template/.vscode/extensions.json +3 -0
  9. package/template/README.md +26 -0
  10. package/template/index.html +19 -0
  11. package/template/package.json +30 -0
  12. package/template/public/vite.svg +1 -0
  13. package/template/src/App.vue +3 -0
  14. package/template/src/components/AppBreadcrumb.vue +132 -0
  15. package/template/src/components/AppPage.vue +23 -0
  16. package/template/src/components/ChangePasswordDialog.vue +97 -0
  17. package/template/src/components/ProfileEditDialog.vue +142 -0
  18. package/template/src/components/RichTextEditor.vue +121 -0
  19. package/template/src/components/SidebarMenuItem.vue +56 -0
  20. package/template/src/components/TagsView.vue +172 -0
  21. package/template/src/constants/menuIcons.ts +205 -0
  22. package/template/src/directives/permission.ts +13 -0
  23. package/template/src/env.d.ts +22 -0
  24. package/template/src/layouts/MainLayout.vue +410 -0
  25. package/template/src/main.ts +27 -0
  26. package/template/src/router/index.ts +198 -0
  27. package/template/src/stores/tagsView.ts +161 -0
  28. package/template/src/stores/theme.ts +68 -0
  29. package/template/src/stores/user.ts +191 -0
  30. package/template/src/styles/global.css +314 -0
  31. package/template/src/utils/datetime.ts +34 -0
  32. package/template/src/utils/request.ts +324 -0
  33. package/template/src/utils/requestSign.ts +162 -0
  34. package/template/src/views/error/NotFound.vue +49 -0
  35. package/template/src/views/home/HomeView.vue +1177 -0
  36. package/template/src/views/login/LoginView.vue +576 -0
  37. package/template/src/views/monitor/loginlog/index.vue +366 -0
  38. package/template/src/views/monitor/online/index.vue +298 -0
  39. package/template/src/views/monitor/operlog/index.vue +492 -0
  40. package/template/src/views/system/config/index.vue +407 -0
  41. package/template/src/views/system/dept/index.vue +517 -0
  42. package/template/src/views/system/dict/index.vue +747 -0
  43. package/template/src/views/system/file/index.vue +1031 -0
  44. package/template/src/views/system/menu/index.vue +822 -0
  45. package/template/src/views/system/message/index.vue +422 -0
  46. package/template/src/views/system/notice/index.vue +578 -0
  47. package/template/src/views/system/openApp/index.vue +596 -0
  48. package/template/src/views/system/post/index.vue +495 -0
  49. package/template/src/views/system/role/index.vue +546 -0
  50. package/template/src/views/system/user/index.vue +373 -0
  51. package/template/src/vite-env.d.ts +1 -0
  52. package/template/tsconfig.app.json +30 -0
  53. package/template/tsconfig.json +7 -0
  54. package/template/tsconfig.node.json +24 -0
  55. package/template/vite.config.ts +22 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 请求签名(HMAC-SHA256),与后端 RequestSignCanonical 规则一致。
3
+ */
4
+
5
+ /** 空 body 的 SHA-256 小写 hex */
6
+ export const EMPTY_BODY_SHA256 =
7
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
8
+
9
+ const HEX = '0123456789abcdef'
10
+ const textEncoder = new TextEncoder()
11
+
12
+ /** 缓存 HMAC CryptoKey,避免每次请求 importKey */
13
+ let cachedHmacKey: CryptoKey | null = null
14
+ let cachedHmacSecret = ''
15
+
16
+ /**
17
+ * 规范化路径:小写、去掉末尾斜杠(根除外)。
18
+ * @param path 原始路径
19
+ */
20
+ export function normalizeSignPath(path: string): string {
21
+ let p = (path || '/').trim()
22
+ if (!p.startsWith('/')) p = `/${p}`
23
+ p = p.toLowerCase()
24
+ while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1)
25
+ return p
26
+ }
27
+
28
+ /**
29
+ * 拼接完整 API 路径(含 /api/v1 前缀)。
30
+ * @param baseURL axios baseURL
31
+ * @param url 相对或绝对路径
32
+ */
33
+ export function resolveApiPath(baseURL: string | undefined, url: string): string {
34
+ const raw = String(url || '')
35
+ if (/^https?:\/\//i.test(raw)) {
36
+ try {
37
+ return normalizeSignPath(new URL(raw).pathname)
38
+ } catch {
39
+ return normalizeSignPath(raw)
40
+ }
41
+ }
42
+
43
+ const base = String(baseURL || '').replace(/\/+$/, '')
44
+ const path = raw.startsWith('/') ? raw : `/${raw}`
45
+ const full = path.toLowerCase().startsWith('/api') ? path : `${base}${path}`
46
+ return normalizeSignPath(full)
47
+ }
48
+
49
+ /**
50
+ * 将 params 规范为排序 QUERY 串。
51
+ * @param params axios params
52
+ */
53
+ export function buildSignQuery(params: unknown): string {
54
+ if (params == null || typeof params !== 'object') return ''
55
+
56
+ const map = new Map<string, string[]>()
57
+ for (const [key, value] of Object.entries(params as Record<string, unknown>)) {
58
+ if (!key || value === undefined || value === null) continue
59
+ const list = map.get(key) ?? []
60
+ if (Array.isArray(value)) {
61
+ for (const item of value) list.push(String(item))
62
+ } else {
63
+ list.push(String(value))
64
+ }
65
+ map.set(key, list)
66
+ }
67
+
68
+ if (map.size === 0) return ''
69
+
70
+ const keys = [...map.keys()].sort()
71
+ return keys
72
+ .map((k) => {
73
+ const values = map.get(k)!
74
+ if (values.length > 1) values.sort()
75
+ return `${k}=${values.join(',')}`
76
+ })
77
+ .join('&')
78
+ }
79
+
80
+ /**
81
+ * 拼接待签字符串(path 应已规范化)。
82
+ */
83
+ export function buildCanonicalString(parts: {
84
+ method: string
85
+ path: string
86
+ query: string
87
+ timestamp: string
88
+ nonce: string
89
+ bodySha256: string
90
+ idempotencyKey: string
91
+ }): string {
92
+ return `${parts.method}\n${parts.path}\n${parts.query || ''}\n${parts.timestamp}\n${parts.nonce}\n${parts.bodySha256}\n${parts.idempotencyKey || ''}`
93
+ }
94
+
95
+ function toHex(buf: ArrayBuffer): string {
96
+ const bytes = new Uint8Array(buf)
97
+ let out = ''
98
+ for (let i = 0; i < bytes.length; i++) {
99
+ const b = bytes[i]
100
+ out += HEX[b >>> 4] + HEX[b & 0xf]
101
+ }
102
+ return out
103
+ }
104
+
105
+ /**
106
+ * SHA-256 小写 hex。
107
+ * @param text UTF-8 文本
108
+ */
109
+ export async function sha256Hex(text: string): Promise<string> {
110
+ if (!text) return EMPTY_BODY_SHA256
111
+ const buf = await crypto.subtle.digest('SHA-256', textEncoder.encode(text))
112
+ return toHex(buf)
113
+ }
114
+
115
+ async function getHmacKey(secret: string): Promise<CryptoKey> {
116
+ if (cachedHmacKey && cachedHmacSecret === secret) return cachedHmacKey
117
+ cachedHmacKey = await crypto.subtle.importKey(
118
+ 'raw',
119
+ textEncoder.encode(secret),
120
+ { name: 'HMAC', hash: 'SHA-256' },
121
+ false,
122
+ ['sign'],
123
+ )
124
+ cachedHmacSecret = secret
125
+ return cachedHmacKey
126
+ }
127
+
128
+ /**
129
+ * HMAC-SHA256 小写 hex(缓存 CryptoKey)。
130
+ * @param secret AppSecret
131
+ * @param message 待签串
132
+ */
133
+ export async function hmacSha256Hex(secret: string, message: string): Promise<string> {
134
+ const key = await getHmacKey(secret)
135
+ const sig = await crypto.subtle.sign('HMAC', key, textEncoder.encode(message))
136
+ return toHex(sig)
137
+ }
138
+
139
+ /**
140
+ * 生成 Nonce(16–64 字符)。
141
+ */
142
+ export function createNonce(): string {
143
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
144
+ const bytes = new Uint8Array(16)
145
+ crypto.getRandomValues(bytes)
146
+ let out = ''
147
+ for (let i = 0; i < bytes.length; i++) {
148
+ out += HEX[bytes[i] >>> 4] + HEX[bytes[i] & 0xf]
149
+ }
150
+ return out
151
+ }
152
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 14)}`
153
+ }
154
+
155
+ /**
156
+ * 是否为签名失败类 401(不应清登录态)。
157
+ * @param message 错误文案
158
+ */
159
+ export function isRequestSignFailureMessage(message: string | undefined): boolean {
160
+ if (!message) return false
161
+ return /签名|Nonce|AppId|时间戳|缺少签名/.test(message)
162
+ }
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import { useRouter } from 'vue-router'
3
+ const router = useRouter()
4
+ </script>
5
+
6
+ <template>
7
+ <div class="not-found sa-page">
8
+ <div class="not-found__card">
9
+ <p class="not-found__code">404</p>
10
+ <h1>页面不存在</h1>
11
+ <p>请检查菜单路由配置,或返回工作台继续操作。</p>
12
+ <el-button type="primary" @click="router.push('/index')">返回首页</el-button>
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <style scoped lang="scss">
18
+ .not-found {
19
+ min-height: 60vh;
20
+ display: grid;
21
+ place-items: center;
22
+ }
23
+ .not-found__card {
24
+ text-align: center;
25
+ padding: 40px 36px;
26
+ background: #fff;
27
+ border: 1px solid var(--sa-border);
28
+ border-radius: 20px;
29
+ box-shadow: var(--sa-shadow);
30
+ max-width: 420px;
31
+ }
32
+ .not-found__code {
33
+ margin: 0;
34
+ font-size: 64px;
35
+ font-weight: 800;
36
+ letter-spacing: -0.06em;
37
+ color: var(--sa-accent);
38
+ line-height: 1;
39
+ }
40
+ h1 {
41
+ margin: 8px 0 0;
42
+ font-size: 24px;
43
+ }
44
+ p {
45
+ margin: 10px 0 22px;
46
+ color: var(--sa-ink-secondary);
47
+ font-size: 14px;
48
+ }
49
+ </style>