Procházet zdrojové kódy

完成表单填写

mrh před 9 měsíci
rodič
revize
8507f52858
7 změnil soubory, kde provedl 197 přidání a 9 odebrání
  1. 7 0
      CONVENTIONS.md
  2. 4 0
      README.md
  3. 3 1
      package.json
  4. 37 0
      pnpm-lock.yaml
  5. 2 2
      src/App.vue
  6. 27 6
      src/components/Header.vue
  7. 117 0
      src/components/Settings.vue

+ 7 - 0
CONVENTIONS.md

@@ -0,0 +1,7 @@
+编程规范:
+- 编写任何 vue 文件必须使用语法糖形式:
+```vue
+<script setup>
+import { reactive, ref } from 'vue';
+</script>
+```

+ 4 - 0
README.md

@@ -10,6 +10,10 @@ git checkout -b main
 
 # Vue 3 + TypeScript + Vite
 
+https://vue-devui.github.io/components/icon/#%E9%99%84%E5%B8%A6%E6%8F%8F%E8%BF%B0%E4%BF%A1%E6%81%AF
+
+https://matechat.gitcode.com/components/header/demo.html
+
 This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
 
 Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

+ 3 - 1
package.json

@@ -11,9 +11,11 @@
   "dependencies": {
     "@devui-design/icons": "^1.4.0",
     "@matechat/core": "^1.0.0",
+    "@vueuse/core": "^12.6.1",
     "openai": "^4.78.1",
     "vue": "^3.5.13",
-    "vue-devui": "^1.6.30"
+    "vue-devui": "^1.6.30",
+    "vue-router": "4"
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.2.1",

+ 37 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       '@matechat/core':
         specifier: ^1.0.0
         version: 1.0.0(vue@3.5.13(typescript@5.6.3))
+      '@vueuse/core':
+        specifier: ^12.6.1
+        version: 12.6.1(typescript@5.6.3)
       openai:
         specifier: ^4.78.1
         version: 4.78.1
@@ -23,6 +26,9 @@ importers:
       vue-devui:
         specifier: ^1.6.30
         version: 1.6.30(vue@3.5.13(typescript@5.6.3))
+      vue-router:
+        specifier: '4'
+        version: 4.5.0(vue@3.5.13(typescript@5.6.3))
     devDependencies:
       '@vitejs/plugin-vue':
         specifier: ^5.2.1
@@ -367,6 +373,9 @@ packages:
   '@types/web-bluetooth@0.0.14':
     resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
 
+  '@types/web-bluetooth@0.0.20':
+    resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+
   '@vitejs/plugin-vue@5.2.1':
     resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
@@ -437,6 +446,9 @@ packages:
       vue:
         optional: true
 
+  '@vueuse/core@12.6.1':
+    resolution: {integrity: sha512-FpgM1tXGAHsAC5n4Tflyg0vSoJUmdevfKaAhKFdxiK9BTIdHOHOiWmo+xivwdzjYFIvI8cEeJWYuqs646jOM2w==}
+
   '@vueuse/core@8.9.4':
     resolution: {integrity: sha512-B/Mdj9TK1peFyWaPof+Zf/mP9XuGAngaJZBwPaXBvU3aCTZlx3ltlrFFFyMV4iGBwsjSCeUCgZrtkEj9dS2Y3Q==}
     peerDependencies:
@@ -448,9 +460,15 @@ packages:
       vue:
         optional: true
 
+  '@vueuse/metadata@12.6.1':
+    resolution: {integrity: sha512-2094HNXGdsU3aqRbad0vmlRgGncMC4u2f6nFdW1mUn7b7ym4hORrDZfyeq8G5BfGvX4y0zZynWfCdtB2WwpyVw==}
+
   '@vueuse/metadata@8.9.4':
     resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
 
+  '@vueuse/shared@12.6.1':
+    resolution: {integrity: sha512-ukTb2na19KT1/YVjj4CYBDOgiV/xmsSJRL6TcKeiz2db+P5bT3I0OJxy38eRR3WSN8CmSnt7MdVJ16vX6VZFxg==}
+
   '@vueuse/shared@8.9.4':
     resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==}
     peerDependencies:
@@ -1495,6 +1513,8 @@ snapshots:
 
   '@types/web-bluetooth@0.0.14': {}
 
+  '@types/web-bluetooth@0.0.20': {}
+
   '@vitejs/plugin-vue@5.2.1(vite@6.0.7(@types/node@18.19.70)(sass-embedded@1.85.0))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       vite: 6.0.7(@types/node@18.19.70)(sass-embedded@1.85.0)
@@ -1591,6 +1611,15 @@ snapshots:
       typescript: 5.6.3
       vue: 3.5.13(typescript@5.6.3)
 
+  '@vueuse/core@12.6.1(typescript@5.6.3)':
+    dependencies:
+      '@types/web-bluetooth': 0.0.20
+      '@vueuse/metadata': 12.6.1
+      '@vueuse/shared': 12.6.1(typescript@5.6.3)
+      vue: 3.5.13(typescript@5.6.3)
+    transitivePeerDependencies:
+      - typescript
+
   '@vueuse/core@8.9.4(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@types/web-bluetooth': 0.0.14
@@ -1600,8 +1629,16 @@ snapshots:
     optionalDependencies:
       vue: 3.5.13(typescript@5.6.3)
 
+  '@vueuse/metadata@12.6.1': {}
+
   '@vueuse/metadata@8.9.4': {}
 
+  '@vueuse/shared@12.6.1(typescript@5.6.3)':
+    dependencies:
+      vue: 3.5.13(typescript@5.6.3)
+    transitivePeerDependencies:
+      - typescript
+
   '@vueuse/shared@8.9.4(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.3))

+ 2 - 2
src/App.vue

@@ -1,6 +1,6 @@
 <template>
   <McLayout class="container">
-    <Header></Header>
+    <Header @logoClicked="startPage = true"></Header>
     <McLayoutContent
       v-if="startPage"
       style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px"
@@ -192,4 +192,4 @@ const onSubmit = (evt: string) => {
     }
   }
 }
-</style>
+</style>

+ 27 - 6
src/components/Header.vue

@@ -7,16 +7,37 @@
       </template>
     </McHeader> -->
     <!-- <div class="container"> -->
-      <McHeader :title="'AI 助手'" :logoImg="'https://matechat.gitcode.com/logo.svg'">
+      <McHeader 
+      :title="'AI 助手'" 
+      :logoImg="'https://matechat.gitcode.com/logo.svg'"
+      :logoClickable="true"
+      @logoClicked="onLogoClicked"
+      >
         <template #operationArea>
-          <i class="icon icon-setting"></i>
-          <i class="icon icon-history"></i>
-          <i class="icon icon-personal-data"></i>
+          <i class="icon icon-setting" @click="showSettings = true"></i>
+          <!-- <i class="icon icon-history"></i> -->
+          <!-- <i class="icon icon-personal-data"></i> -->
         </template>
       </McHeader>
+    <Settings v-model:visible="showSettings" />
     <!-- </div> -->
   </template>
-  
+<script setup>
+import { useRouter } from 'vue-router';
+import Settings from './Settings.vue';
+import { ref, watch } from 'vue';
+
+const router = useRouter();
+const emit = defineEmits(['logoClicked']);
+const showSettings = ref(false);
+
+const onLogoClicked = () => {
+  console.log('logo clicked');
+  emit('logoClicked');
+  router.push('/');
+};
+</script>
+
   <style scoped lang="scss">
   .container {
     background-color: var(--devui-global-bg);
@@ -34,4 +55,4 @@
     }
   }
   </style>
-  
+  

+ 117 - 0
src/components/Settings.vue

@@ -0,0 +1,117 @@
+<template>
+    <div>
+      <!-- 模态框 -->
+      <d-modal v-model="visible" title="设置">
+        <d-form ref="settingsForm" :data="formModel" layout="vertical" @submit.stop="onSubmitForm">
+          <d-form-item prop="apiBase" label="API Base URL" 
+            help-tips="API服务的基础地址" 
+            extra-info="格式示例:https://api.example.com/v1">
+            <d-input 
+              v-model="formModel.apiBase" 
+              placeholder="输入API基础地址"
+              :rules="[{ required: true, message: 'API地址不能为空' }, { type: 'url', message: '请输入有效的URL地址' }]"
+            />
+          </d-form-item>
+
+          <d-form-item prop="openaiApiKey" label="OpenAI API Key"
+            help-tips="用于身份验证的API密钥"
+            extra-info="">
+              <d-input 
+                v-model="formModel.openaiApiKey" 
+                placeholder="输入 API密钥"
+                :type="showPassword ? 'text' : 'password'"
+                :rules="[{ required: true, message: 'API密钥不能为空' }, { pattern: /^sk-[A-Za-z0-9\-]{48}$/, message: '密钥格式应为sk-开头+48位字符' }]"
+              >
+                <template #suffix>
+                  <i
+                    class="icon"
+                    :class="showPassword ? 'icon-view' : 'icon-blind'"
+                    @click="showPassword = !showPassword"
+                  />
+                </template>
+              </d-input>
+          </d-form-item>
+  
+          <d-form-item prop="model" label="Model"
+            help-tips="选择要使用的AI模型"
+            extra-info="不同模型可能需要不同的API地址">
+            <d-select v-model="formModel.model" :options="modelOptions" />
+          </d-form-item>
+
+          <d-form-operation class="form-demo-form-operation">
+            <d-button variant="solid" type="submit">保存设置</d-button>
+            <d-button @click="resetForm">重置</d-button>
+          </d-form-operation>
+        </d-form>
+      </d-modal>
+    </div>
+  </template>
+  
+  <script setup>
+  import { reactive, computed, ref } from 'vue';
+  import { useLocalStorage } from '@vueuse/core';
+  
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      required: true
+    }
+  });
+  
+  const emit = defineEmits(['update:visible']);
+  
+  const visible = computed({
+    get() {
+      return props.visible;
+    },
+    set(value) {
+      emit('update:visible', value);
+    }
+  });
+  
+  // 初始化表单数据时优先从本地存储读取
+  const formModel = reactive(useLocalStorage('ai-settings', {
+    apiBase: 'https://aiapi.magong.site/v1', // API Base URL
+    openaiApiKey: 'sk-jTzZGIAMZux11AA6666d54D5A27541C28c92Ca54F8D33c83', // OpenAI API Key  
+    model: 'deepseek-chat', // 默认模型
+  }));
+  
+  // 模型选项
+  const showPassword = ref(false);
+  const modelOptions = reactive([
+    { value: 'deepseek-chat', label: 'DeepSeek Chat' },
+    { value: 'deepseek-ai/DeepSeek-V3', label: 'DeepSeek V3' },
+    { value: 'glm-4-flash', label: '智谱 GLM-4 Flash' }
+  ]);
+  
+  // 表单引用
+  const settingsForm = ref(null);
+  
+  // 重置表单
+  const resetForm = () => {
+    settingsForm.value.resetFormFields();
+  };
+  
+  // 提交表单
+  const onSubmitForm = () => {
+    // 已自动保存到本地存储(通过useLocalStorage响应式实现)
+    visible.value = false;
+  };
+  
+  </script>
+  
+  <style scoped>
+.form-demo-form-operation {
+  display: flex;
+  gap: 12px;
+  margin-top: 24px;
+}
+
+:deep(.d-modal-body) {
+  padding: 20px;
+}
+
+:deep(.d-modal-footer) {
+  border-top: none;
+}
+</style>