@templmf/temp-solf-lmf 0.0.55 → 0.0.57

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 (89) hide show
  1. package/fe-flow.md +348 -0
  2. package/package.json +1 -1
  3. package/ui-parse.md +308 -0
  4. package//345/242/236/351/207/217/351/234/200/346/261/202prompt +72 -0
  5. package/guanwang/README.md +0 -95
  6. package/guanwang/docs/changelog.md +0 -145
  7. package/guanwang/docs/doc-maintenance.md +0 -229
  8. package/guanwang/docs/product.md +0 -181
  9. package/guanwang/docs/test-cases.md +0 -395
  10. package/guanwang/docs/usage.md +0 -291
  11. package/guanwang/env.example +0 -27
  12. package/guanwang/index.html +0 -13
  13. package/guanwang/package-lock.json +0 -3825
  14. package/guanwang/package.json +0 -32
  15. package/guanwang/public/favicon.svg +0 -4
  16. package/guanwang/public/react-runtime/babel.min.js +0 -4
  17. package/guanwang/public/react-runtime/react-dom.min.js +0 -267
  18. package/guanwang/public/react-runtime/react.min.js +0 -31
  19. package/guanwang/public/vue-repl-assets/compiler-sfc.esm-browser.js +0 -50795
  20. package/guanwang/public/vue-repl-assets/runtime-dom.esm-browser.js +0 -12758
  21. package/guanwang/public/vue-repl-assets/server-renderer.esm-browser.js +0 -8600
  22. package/guanwang/public/vue-repl-assets/vue.esm-browser.js +0 -18672
  23. package/guanwang/src/App.vue +0 -61
  24. package/guanwang/src/chat-sdk/core/components/ChatBox.vue +0 -305
  25. package/guanwang/src/chat-sdk/core/components/ChatSidebar.vue +0 -84
  26. package/guanwang/src/chat-sdk/core/components/InputBar.vue +0 -354
  27. package/guanwang/src/chat-sdk/core/components/MessageBubble.vue +0 -703
  28. package/guanwang/src/chat-sdk/core/useTheme.js +0 -31
  29. package/guanwang/src/chat-sdk/features/artifact/ArtifactCard.vue +0 -172
  30. package/guanwang/src/chat-sdk/features/artifact/ArtifactPanel.vue +0 -963
  31. package/guanwang/src/chat-sdk/features/artifact/index.js +0 -13
  32. package/guanwang/src/chat-sdk/features/artifact/useArtifactStore.js +0 -275
  33. package/guanwang/src/chat-sdk/features/codepreview/CodePreview.vue +0 -523
  34. package/guanwang/src/chat-sdk/features/codepreview/index.js +0 -7
  35. package/guanwang/src/chat-sdk/features/markdown/index.js +0 -13
  36. package/guanwang/src/chat-sdk/features/markdown/useMarkdown.js +0 -724
  37. package/guanwang/src/chat-sdk/features/mermaid/MermaidZoom.vue +0 -254
  38. package/guanwang/src/chat-sdk/features/upload/FileAttachment.vue +0 -142
  39. package/guanwang/src/chat-sdk/features/upload/index.js +0 -17
  40. package/guanwang/src/chat-sdk/features/upload/useFileHandler.js +0 -336
  41. package/guanwang/src/chat-sdk/headless/api/adapters/openai.js +0 -76
  42. package/guanwang/src/chat-sdk/headless/api/chatApi.js +0 -126
  43. package/guanwang/src/chat-sdk/headless/buildSystemPrompt.js +0 -351
  44. package/guanwang/src/chat-sdk/headless/index.js +0 -15
  45. package/guanwang/src/chat-sdk/headless/useChat.js +0 -77
  46. package/guanwang/src/chat-sdk/headless/useChatDB.js +0 -147
  47. package/guanwang/src/chat-sdk/headless/useChatStore.js +0 -529
  48. package/guanwang/src/chat-sdk/index.js +0 -79
  49. package/guanwang/src/chat-sdk/modes/architect.js +0 -27
  50. package/guanwang/src/chat-sdk/modes/ask.js +0 -26
  51. package/guanwang/src/chat-sdk/modes/code.js +0 -25
  52. package/guanwang/src/chat-sdk/modes/index.js +0 -36
  53. package/guanwang/src/chat-sdk/modes/requirements.js +0 -175
  54. package/guanwang/src/chat-sdk/settings/SettingsPanel.vue +0 -170
  55. package/guanwang/src/chat-sdk/settings/index.js +0 -9
  56. package/guanwang/src/chat-sdk/settings/useSettings.js +0 -122
  57. package/guanwang/src/chat-sdk/tools/defaults.js +0 -89
  58. package/guanwang/src/chat-sdk/tools/index.js +0 -16
  59. package/guanwang/src/chat-sdk/tools/parser.js +0 -116
  60. package/guanwang/src/components/CustomCursor.vue +0 -69
  61. package/guanwang/src/components/Footer.vue +0 -24
  62. package/guanwang/src/components/LoginModal.vue +0 -109
  63. package/guanwang/src/components/Navbar.vue +0 -193
  64. package/guanwang/src/components/ThemeToggle.vue +0 -25
  65. package/guanwang/src/composables/useArtifactStore.js +0 -253
  66. package/guanwang/src/composables/useAuth.js +0 -88
  67. package/guanwang/src/composables/useChatDB.js +0 -147
  68. package/guanwang/src/composables/useCountUp.js +0 -24
  69. package/guanwang/src/composables/useFileHandler.js +0 -345
  70. package/guanwang/src/composables/useTheme.js +0 -31
  71. package/guanwang/src/config/api.js +0 -71
  72. package/guanwang/src/main.js +0 -23
  73. package/guanwang/src/router/index.js +0 -23
  74. package/guanwang/src/services/authApi.js +0 -27
  75. package/guanwang/src/services/chatApi.js +0 -66
  76. package/guanwang/src/styles/global.css +0 -478
  77. package/guanwang/src/tracker/analyze.js +0 -73
  78. package/guanwang/src/tracker/config.js +0 -82
  79. package/guanwang/src/tracker/index.js +0 -18
  80. package/guanwang/src/tracker/service.js +0 -102
  81. package/guanwang/src/tracker/useChatTracker.js +0 -179
  82. package/guanwang/src/tracker/useTracker.js +0 -45
  83. package/guanwang/src/views/ChatView.vue +0 -65
  84. package/guanwang/src/views/HomeView.vue +0 -156
  85. package/guanwang/src/views/MarketView.vue +0 -143
  86. package/guanwang/src/views/PracticesView.vue +0 -190
  87. package/guanwang/src/views/SkillsView.vue +0 -129
  88. package/guanwang/temp +0 -19
  89. package/guanwang/vite.config.js +0 -6
@@ -1,116 +0,0 @@
1
- /**
2
- * tools/parser.js
3
- * 解析 AI 输出文本中的 XML 工具调用
4
- *
5
- * 工具调用格式(AI 输出):
6
- * <tool_name>
7
- * <param1>value1</param1>
8
- * <param2>value2</param2>
9
- * </tool_name>
10
- */
11
-
12
- /**
13
- * 从文本中提取第一个工具调用
14
- * @param {string} text
15
- * @returns {{ name: string, params: Record<string, string> } | null}
16
- */
17
- export function parseToolCall(text) {
18
- if (!text) return null
19
-
20
- // 匹配最外层工具标签(非贪婪)
21
- const toolMatch = text.match(/<([a-z_]+)>([\s\S]*?)<\/\1>/i)
22
- if (!toolMatch) return null
23
-
24
- const name = toolMatch[1]
25
- const innerText = toolMatch[2]
26
-
27
- // 已知工具名列表,避免把普通 XML/HTML 误判为工具调用
28
- const KNOWN_TOOLS = new Set([
29
- 'ask_followup_question',
30
- 'attempt_completion',
31
- 'switch_mode',
32
- 'render_artifact',
33
- 'request_context',
34
- ])
35
-
36
- if (!KNOWN_TOOLS.has(name)) return null
37
-
38
- const params = parseParams(innerText)
39
- return { name, params, raw: toolMatch[0] }
40
- }
41
-
42
- /**
43
- * 从文本中提取所有工具调用(流式结束后用)
44
- * @param {string} text
45
- * @returns {Array<{ name, params, raw }>}
46
- */
47
- export function extractAllToolCalls(text) {
48
- if (!text) return []
49
-
50
- const KNOWN_TOOLS = [
51
- 'ask_followup_question',
52
- 'attempt_completion',
53
- 'switch_mode',
54
- 'render_artifact',
55
- 'request_context',
56
- ]
57
-
58
- const results = []
59
-
60
- for (const toolName of KNOWN_TOOLS) {
61
- const regex = new RegExp(`<${toolName}>([\\s\\S]*?)<\\/${toolName}>`, 'gi')
62
- let match
63
- while ((match = regex.exec(text)) !== null) {
64
- results.push({
65
- name: toolName,
66
- params: parseParams(match[1]),
67
- raw: match[0],
68
- index: match.index,
69
- })
70
- }
71
- }
72
-
73
- // 按出现顺序排序
74
- return results.sort((a, b) => a.index - b.index)
75
- }
76
-
77
- /**
78
- * 解析工具内部的参数标签
79
- * @param {string} innerText
80
- * @returns {Record<string, string>}
81
- */
82
- function parseParams(innerText) {
83
- const params = {}
84
- const paramRegex = /<([a-z_]+)>([\s\S]*?)<\/\1>/gi
85
- let match
86
-
87
- while ((match = paramRegex.exec(innerText)) !== null) {
88
- const key = match[1]
89
- const value = match[2].trim()
90
-
91
- // suggest 标签可能有多个,收集成数组
92
- if (key === 'suggest') {
93
- if (!params.suggests) params.suggests = []
94
- // 提取 mode 属性(如果有)
95
- const modeAttr = match[0].match(/<suggest\s+mode="([^"]+)"/)
96
- params.suggests.push({
97
- text: value,
98
- mode: modeAttr ? modeAttr[1] : null,
99
- })
100
- } else {
101
- params[key] = value
102
- }
103
- }
104
-
105
- return params
106
- }
107
-
108
- /**
109
- * 判断文本是否包含工具调用(流式期间快速检测)
110
- * @param {string} text
111
- * @returns {boolean}
112
- */
113
- export function hasToolCall(text) {
114
- if (!text) return false
115
- return /<(ask_followup_question|attempt_completion|switch_mode|render_artifact|request_context)>/i.test(text)
116
- }
@@ -1,69 +0,0 @@
1
- <template>
2
- <div
3
- :style="{
4
- position: 'fixed',
5
- pointerEvents: 'none',
6
- zIndex: 9999,
7
- left: pos.x + 'px',
8
- top: pos.y + 'px',
9
- }"
10
- >
11
- <!-- 实心点 -->
12
- <div style="position: absolute; width: 7px; height: 7px; background: var(--brand-500); border-radius: 50%; transform: translate(-50%, -50%);" />
13
- <!-- 跟随圆环 -->
14
- <div
15
- :style="{
16
- position: 'absolute',
17
- borderRadius: '50%',
18
- border: hover ? '1.5px solid var(--brand-400)' : '1px solid rgba(128,128,128,0.4)',
19
- width: hover ? '40px' : '28px',
20
- height: hover ? '40px' : '28px',
21
- transform: 'translate(-50%, -50%)',
22
- left: (outer.x - pos.x) + 'px',
23
- top: (outer.y - pos.y) + 'px',
24
- transition: 'width 0.3s, height 0.3s, border-color 0.3s',
25
- }"
26
- />
27
- </div>
28
- </template>
29
-
30
- <script setup>
31
- import { ref, onMounted, onUnmounted } from 'vue'
32
-
33
- const pos = ref({ x: -100, y: -100 })
34
- const outer = ref({ x: -100, y: -100 })
35
- const hover = ref(false)
36
-
37
- const posRaw = { x: -100, y: -100 }
38
- const outerRaw = { x: -100, y: -100 }
39
- let rafId = null
40
-
41
- function lerp(a, b, t) { return a + (b - a) * t }
42
-
43
- function tick() {
44
- outerRaw.x = lerp(outerRaw.x, posRaw.x, 0.1)
45
- outerRaw.y = lerp(outerRaw.y, posRaw.y, 0.1)
46
- outer.value = { x: outerRaw.x, y: outerRaw.y }
47
- rafId = requestAnimationFrame(tick)
48
- }
49
-
50
- function onMove(e) {
51
- posRaw.x = e.clientX
52
- posRaw.y = e.clientY
53
- pos.value = { x: e.clientX, y: e.clientY }
54
- const el = document.elementFromPoint(e.clientX, e.clientY)
55
- hover.value = !!(el && el.closest('button, a'))
56
- }
57
-
58
- onMounted(() => {
59
- window.addEventListener('mousemove', onMove)
60
- document.body.style.cursor = 'none'
61
- rafId = requestAnimationFrame(tick)
62
- })
63
-
64
- onUnmounted(() => {
65
- window.removeEventListener('mousemove', onMove)
66
- document.body.style.cursor = ''
67
- cancelAnimationFrame(rafId)
68
- })
69
- </script>
@@ -1,24 +0,0 @@
1
- <template>
2
- <footer style="border-top: 1px solid var(--border-subtle); padding: 36px 0; margin-top: 80px; background: var(--bg-secondary); transition: background 0.3s;">
3
- <div style="max-width: 1280px; margin: 0 auto; padding: 0 24px; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px;">
4
- <div style="display: flex; align-items: center; gap: 7px;">
5
- <div class="font-display" style="width: 22px; height: 22px; border-radius: 5px; background: var(--brand-500); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">K</div>
6
- <span style="color: var(--text-secondary); font-size: 13px; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;">Citic Frontend</span>
7
- </div>
8
- <p class="font-mono" style="color: var(--text-faint); font-size: 12px;">© 2026 Citic Frontend. All rights reserved.</p>
9
- <div style="display: flex; gap: 16px;">
10
- <a
11
- v-for="t in ['文档', 'API', '隐私政策']"
12
- :key="t"
13
- href="#"
14
- class="footer-link"
15
- style="color: var(--text-muted); font-size: 12px; text-decoration: none; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; transition: color 0.15s;"
16
- >{{ t }}</a>
17
- </div>
18
- </div>
19
- </footer>
20
- </template>
21
-
22
- <style scoped>
23
- .footer-link:hover { color: var(--text-primary) !important; }
24
- </style>
@@ -1,109 +0,0 @@
1
- <template>
2
- <el-dialog
3
- :model-value="true"
4
- :close-on-click-modal="true"
5
- :show-close="true"
6
- width="400px"
7
- align-center
8
- @close="$emit('close')"
9
- >
10
- <template #header>
11
- <div style="display: flex; align-items: center; gap: 12px;">
12
- <div style="width: 36px; height: 36px; border-radius: 10px; background: var(--brand-500); display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
13
- <LogIn :size="16" color="white" :stroke-width="1.75" />
14
- </div>
15
- <div>
16
- <span class="font-display" style="font-weight: 700; color: var(--text-primary); font-size: 17px;">登录账号</span>
17
- <p style="color: var(--text-muted); font-size: 12px; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; margin: 2px 0 0;">Citic Frontend开发者平台</p>
18
- </div>
19
- </div>
20
- </template>
21
-
22
- <div style="display: flex; flex-direction: column; gap: 12px;">
23
- <el-input
24
- v-model="form.username"
25
- placeholder="账号"
26
- size="large"
27
- autocomplete="username"
28
- clearable
29
- @keyup.enter="handleSubmit"
30
- >
31
- <template #prefix>
32
- <User :size="14" style="color: var(--text-muted);" />
33
- </template>
34
- </el-input>
35
-
36
- <el-input
37
- v-model="form.password"
38
- :type="showPwd ? 'text' : 'password'"
39
- placeholder="密码"
40
- size="large"
41
- autocomplete="current-password"
42
- @keyup.enter="handleSubmit"
43
- >
44
- <template #prefix>
45
- <Lock :size="14" style="color: var(--text-muted);" />
46
- </template>
47
- <template #suffix>
48
- <el-icon style="cursor: pointer; color: var(--text-muted);" @click="showPwd = !showPwd">
49
- <View v-if="!showPwd" />
50
- <Hide v-else />
51
- </el-icon>
52
- </template>
53
- </el-input>
54
-
55
- <el-alert
56
- v-if="error"
57
- :title="error"
58
- type="error"
59
- :closable="false"
60
- show-icon
61
- style="border-radius: 8px;"
62
- />
63
-
64
- <el-button
65
- type="primary"
66
- size="large"
67
- :loading="isLoading"
68
- :disabled="!form.username || !form.password"
69
- style="width: 100%; font-size: 14px; border-radius: 10px; margin-top: 4px;"
70
- @click="handleSubmit"
71
- >
72
- <template v-if="!isLoading">
73
- <LogIn :size="15" style="margin-right: 6px;" /> 登录
74
- </template>
75
- </el-button>
76
-
77
- <div style="padding: 10px 14px; border-radius: 8px; background: var(--bg-secondary); border: 1px solid var(--border-subtle);">
78
- <p class="font-mono" style="font-size: 11px; color: var(--text-muted); margin-bottom: 4px;">测试账号</p>
79
- <p class="font-mono" style="font-size: 12px; color: var(--text-secondary);">
80
- admin / admin123 &nbsp;·&nbsp; dev / dev123
81
- </p>
82
- </div>
83
- </div>
84
- </el-dialog>
85
- </template>
86
-
87
- <script setup>
88
- import { reactive, ref } from 'vue'
89
- import { LogIn, User, Lock } from 'lucide-vue-next'
90
- import { View, Hide } from '@element-plus/icons-vue'
91
- import { useAuth } from '../composables/useAuth.js'
92
- import { useTracker } from '../tracker/index.js'
93
-
94
- const emit = defineEmits(['close'])
95
- const { doLogin, isLoading, error } = useAuth()
96
- const tracker = useTracker()
97
-
98
- const form = reactive({ username: '', password: '' })
99
- const showPwd = ref(false)
100
-
101
- async function handleSubmit() {
102
- if (!form.username || !form.password) return
103
- const ok = await doLogin(form.username.trim(), form.password)
104
- if (ok) {
105
- tracker.loginSuccess(form.username.trim())
106
- emit('close')
107
- }
108
- }
109
- </script>
@@ -1,193 +0,0 @@
1
- <template>
2
- <header
3
- :style="{
4
- position: 'fixed', top: 0, left: 0, right: 0, zIndex: 50, transition: 'all 0.3s',
5
- background: scrolled ? 'var(--nav-mobile-bg)' : 'transparent',
6
- backdropFilter: scrolled ? 'blur(12px)' : 'none',
7
- borderBottom: scrolled ? '1px solid var(--border-subtle)' : '1px solid transparent',
8
- }"
9
- >
10
- <div style="max-width: 1280px; margin: 0 auto; padding: 0 24px; display: flex; align-items: center; justify-content: space-between; height: 64px;">
11
-
12
- <!-- Logo -->
13
- <button style="display: flex; align-items: center; gap: 8px; background: none; border: none; cursor: pointer;" @click="handleNav('/')">
14
- <div class="font-display" style="width: 32px; height: 32px; border-radius: 8px; background: var(--brand-500); display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: 15px;">K</div>
15
- <span class="font-display" style="font-weight: 700; color: var(--text-primary); font-size: 16px; letter-spacing: -0.02em;">Citic Frontend</span>
16
- </button>
17
-
18
- <!-- Desktop nav -->
19
- <nav v-if="!isMobile" style="display: flex; align-items: center; gap: 2px;">
20
- <RouterLink
21
- v-for="link in NAV_LINKS"
22
- :key="link.path"
23
- :to="link.path"
24
- class="btn-ghost"
25
- :class="{ 'nav-active': isActive(link.path) }"
26
- style="position: relative; color: var(--text-secondary);"
27
- >
28
- <span
29
- v-if="link.dot"
30
- class="animate-pulse-slow"
31
- style="width: 6px; height: 6px; border-radius: 50%; background: #4ade80; display: inline-block; margin-right: 4px;"
32
- />{{ link.label }}
33
- </RouterLink>
34
- </nav>
35
-
36
- <!-- Right side -->
37
- <div style="display: flex; align-items: center; gap: 10px;">
38
- <ThemeToggle />
39
-
40
- <template v-if="!isMobile">
41
- <!-- 已登录:EP Dropdown -->
42
- <el-dropdown v-if="isLoggedIn" trigger="click" placement="bottom-end" @command="handleDropdownCmd">
43
- <div class="user-trigger">
44
- <UserAvatar :user="user" :size="26" />
45
- <span style="color: var(--text-primary); font-size: 13px; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;">{{ user.name }}</span>
46
- <el-icon style="color: var(--text-muted); font-size: 12px; margin-left: 2px;"><ArrowDown /></el-icon>
47
- </div>
48
- <template #dropdown>
49
- <el-dropdown-menu>
50
- <div style="padding: 10px 14px; border-bottom: 1px solid var(--border-subtle); margin-bottom: 4px; display: flex; align-items: center; gap: 8px;">
51
- <UserAvatar :user="user" :size="30" />
52
- <div>
53
- <p class="font-display" style="font-weight: 600; color: var(--text-primary); font-size: 13px; margin: 0;">{{ user.name }}</p>
54
- <p class="font-mono" style="font-size: 10px; color: var(--text-muted); margin: 0;">{{ user.role }}</p>
55
- </div>
56
- </div>
57
- <el-dropdown-item command="profile">
58
- <User :size="14" style="margin-right: 8px;" /> 个人中心
59
- </el-dropdown-item>
60
- <el-dropdown-item command="logout" style="color: #f87171;">
61
- <LogOut :size="14" style="margin-right: 8px; color: #f87171;" /> 退出登录
62
- </el-dropdown-item>
63
- </el-dropdown-menu>
64
- </template>
65
- </el-dropdown>
66
-
67
- <!-- 未登录 -->
68
- <template v-else>
69
- <el-button text style="font-size: 13px; color: var(--text-secondary);" @click="showLogin = true">
70
- <LogIn :size="14" style="margin-right: 5px;" /> 登录
71
- </el-button>
72
- <el-button type="primary" style="font-size: 13px; border-radius: 8px;" @click="showLogin = true">
73
- <UserPlus :size="14" style="margin-right: 5px;" /> 注册
74
- </el-button>
75
- </template>
76
- </template>
77
-
78
- <!-- Mobile hamburger -->
79
- <el-button v-if="isMobile" text circle @click="mobileOpen = true">
80
- <Menu :size="20" color="var(--text-secondary)" />
81
- </el-button>
82
- </div>
83
- </div>
84
- </header>
85
-
86
- <!-- Mobile Drawer -->
87
- <el-drawer v-model="mobileOpen" direction="rtl" size="72%" :with-header="false" :z-index="200">
88
- <div style="display: flex; flex-direction: column; height: 100%; padding: 20px 0;">
89
- <div style="padding: 0 20px 16px; border-bottom: 1px solid var(--border-subtle); margin-bottom: 8px; display: flex; align-items: center; gap: 8px;">
90
- <div class="font-display" style="width: 28px; height: 28px; border-radius: 7px; background: var(--brand-500); display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: 13px;">K</div>
91
- <span class="font-display" style="font-weight: 700; color: var(--text-primary); font-size: 15px;">Citic Frontend</span>
92
- </div>
93
- <div style="flex: 1; overflow-y: auto; padding: 0 12px;">
94
- <el-menu :default-active="route.path" style="border: none; background: transparent;">
95
- <el-menu-item
96
- v-for="link in NAV_LINKS"
97
- :key="link.path"
98
- :index="link.path"
99
- style="border-radius: 8px; margin-bottom: 2px;"
100
- @click="handleNav(link.path)"
101
- >
102
- <span>
103
- <span v-if="link.dot" class="animate-pulse-slow" style="width: 6px; height: 6px; border-radius: 50%; background: #4ade80; display: inline-block; margin-right: 8px; vertical-align: middle;" />
104
- {{ link.label }}
105
- </span>
106
- </el-menu-item>
107
- </el-menu>
108
- </div>
109
- <div style="padding: 16px 20px 0; border-top: 1px solid var(--border-subtle);">
110
- <el-button v-if="!isLoggedIn" type="primary" style="width: 100%; border-radius: 10px;" @click="mobileOpen = false; showLogin = true">
111
- <LogIn :size="14" style="margin-right: 6px;" /> 登录 / 注册
112
- </el-button>
113
- <el-button v-else style="width: 100%; color: #f87171; border-color: rgba(248,113,113,0.3);" @click="mobileOpen = false; doLogout()">
114
- <LogOut :size="14" style="margin-right: 6px;" /> 退出登录
115
- </el-button>
116
- </div>
117
- </div>
118
- </el-drawer>
119
-
120
- <LoginModal v-if="showLogin" @close="showLogin = false" />
121
- </template>
122
-
123
- <script setup>
124
- import { ref, onMounted, onUnmounted, defineComponent, h } from 'vue'
125
- import { useRouter, useRoute } from 'vue-router'
126
- import { Menu, LogIn, UserPlus, LogOut, User } from 'lucide-vue-next'
127
- import { ArrowDown } from '@element-plus/icons-vue'
128
- import ThemeToggle from './ThemeToggle.vue'
129
- import LoginModal from './LoginModal.vue'
130
- import { useAuth } from '../composables/useAuth.js'
131
-
132
- const AVATAR_COLORS = ['#1A6FC4', '#0891b2', '#059669', '#7c3aed', '#b45309']
133
- const UserAvatar = defineComponent({
134
- props: { user: Object, size: { type: Number, default: 28 } },
135
- setup(props) {
136
- return () => {
137
- const initial = (props.user?.name || props.user?.username || '?')[0].toUpperCase()
138
- const bg = AVATAR_COLORS[(props.user?.id || 0) % AVATAR_COLORS.length]
139
- return h('div', {
140
- style: {
141
- width: props.size + 'px', height: props.size + 'px', borderRadius: '50%',
142
- background: bg, display: 'flex', alignItems: 'center', justifyContent: 'center',
143
- color: 'white', fontFamily: 'Syne, sans-serif', fontWeight: 700,
144
- fontSize: (props.size * 0.42) + 'px', flexShrink: 0,
145
- },
146
- }, initial)
147
- }
148
- },
149
- })
150
-
151
- const NAV_LINKS = [
152
- { path: '/', label: '首页' },
153
- { path: '/chat', label: 'AI 对话', dot: true },
154
- { path: '/market', label: '组件市场' },
155
- { path: '/skills', label: 'Skills' },
156
- { path: '/practices', label: '最佳实践' },
157
- ]
158
-
159
- const router = useRouter()
160
- const route = useRoute()
161
- const isMobile = window.innerWidth < 768
162
- const { isLoggedIn, user, doLogout } = useAuth()
163
-
164
- const scrolled = ref(false)
165
- const mobileOpen = ref(false)
166
- const showLogin = ref(false)
167
-
168
- function isActive(path) {
169
- return path === '/' ? route.path === '/' : route.path.startsWith(path)
170
- }
171
- function handleNav(path) { router.push(path); mobileOpen.value = false }
172
- async function handleDropdownCmd(cmd) { if (cmd === 'logout') await doLogout() }
173
- function onScroll() { scrolled.value = window.scrollY > 20 }
174
-
175
- onMounted(() => window.addEventListener('scroll', onScroll, { passive: true }))
176
- onUnmounted(() => window.removeEventListener('scroll', onScroll))
177
- </script>
178
-
179
- <style scoped>
180
- .user-trigger {
181
- display: flex; align-items: center; gap: 6px;
182
- border: 1px solid var(--border-subtle); border-radius: 20px;
183
- padding: 3px 10px 3px 4px; cursor: pointer; transition: border-color 0.15s;
184
- }
185
- .user-trigger:hover { border-color: var(--border-default); }
186
-
187
- :deep(.el-menu-item) { color: var(--text-secondary) !important; background: transparent !important; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; }
188
- :deep(.el-menu-item.is-active), :deep(.el-menu-item:hover) { color: var(--text-primary) !important; background: var(--border-subtle) !important; }
189
- :deep(.el-drawer__body) { background: var(--bg-secondary) !important; padding: 0 !important; }
190
- :deep(.el-dropdown-menu) { background: var(--bg-secondary) !important; border-color: var(--border-subtle) !important; }
191
- :deep(.el-dropdown-menu__item) { color: var(--text-secondary) !important; font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 13px; display: flex; align-items: center; }
192
- :deep(.el-dropdown-menu__item:hover) { background: var(--border-subtle) !important; color: var(--text-primary) !important; }
193
- </style>
@@ -1,25 +0,0 @@
1
- <template>
2
- <button
3
- class="theme-track"
4
- :title="isDark ? '切换浅色主题' : '切换深色主题'"
5
- :style="{ background: isDark ? 'var(--brand-500)' : '#cbd5e1' }"
6
- @click="toggleTheme"
7
- >
8
- <div
9
- class="theme-knob"
10
- :style="{ transform: isDark ? 'translateX(20px)' : 'translateX(0)' }"
11
- >
12
- <Moon v-if="isDark" :size="10" color="#e2e8f0" :stroke-width="2" />
13
- <Sun v-else :size="10" color="#f59e0b" :stroke-width="2" />
14
- </div>
15
- </button>
16
- </template>
17
-
18
- <script setup>
19
- import { computed } from 'vue'
20
- import { Sun, Moon } from 'lucide-vue-next'
21
- import { useTheme } from '../composables/useTheme.js'
22
-
23
- const { theme, toggleTheme } = useTheme()
24
- const isDark = computed(() => theme.value === 'dark')
25
- </script>