mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat(ui): 新增系统设置
This commit is contained in:
		
							
								
								
									
										11
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -35,6 +35,9 @@ importers:
 | 
			
		||||
      '@gpt-vue/packages':
 | 
			
		||||
        specifier: workspace:^1.0.0
 | 
			
		||||
        version: link:../../packages
 | 
			
		||||
      md-editor-v3:
 | 
			
		||||
        specifier: ^2.2.1
 | 
			
		||||
        version: 2.11.3(vue@3.4.21)
 | 
			
		||||
      pinia:
 | 
			
		||||
        specifier: ^2.1.7
 | 
			
		||||
        version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
 | 
			
		||||
@@ -2243,6 +2246,14 @@ packages:
 | 
			
		||||
    dev: true
 | 
			
		||||
    optional: true
 | 
			
		||||
 | 
			
		||||
  /md-editor-v3@2.11.3(vue@3.4.21):
 | 
			
		||||
    resolution: {integrity: sha512-SCfS4qMy0HldFdplcIGUMCpSv8qkNWkYShSdv2gTHeViKduA34zV89BOrWcqls2EZSlvt2n3G7nHRzYUvJjDKw==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      vue: ^3.2.47
 | 
			
		||||
    dependencies:
 | 
			
		||||
      vue: 3.4.21(typescript@5.3.3)
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /memorystream@0.3.1:
 | 
			
		||||
    resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
 | 
			
		||||
    engines: {node: '>= 0.10.0'}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@arco-design/web-vue": "^2.54.6",
 | 
			
		||||
    "@gpt-vue/packages": "workspace:^1.0.0",
 | 
			
		||||
    "md-editor-v3": "^2.2.1",
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "vue": "^3.4.15",
 | 
			
		||||
    "vue-router": "^4.2.5"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { ref, reactive, unref } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import type { BaseResponse } from "@gpt-vue/packages/type";
 | 
			
		||||
function useSubmit<T extends Record<string, unknown>, R = any>(defaultData: T) {
 | 
			
		||||
function useSubmit<T extends Record<string, any> = Record<string, any>, R = any>(defaultData?: T) {
 | 
			
		||||
  const formRef = ref();
 | 
			
		||||
  const formData = reactive<T>({ ...defaultData });
 | 
			
		||||
  const formData = reactive<T | Record<string, any>>({ ...defaultData ?? {} });
 | 
			
		||||
  const submitting = ref(false);
 | 
			
		||||
 | 
			
		||||
  const handleSubmit = async (api: (params?: any) => Promise<BaseResponse<R>>, params) => {
 | 
			
		||||
@@ -11,7 +11,7 @@ function useSubmit<T extends Record<string, unknown>, R = any>(defaultData: T) {
 | 
			
		||||
    try {
 | 
			
		||||
      const hasError = await formRef.value?.validate();
 | 
			
		||||
      if (!hasError) {
 | 
			
		||||
        const { data, message } = await api({ ...formData, ...unref(params) });
 | 
			
		||||
        const { data, message } = await api({ ...formData ?? {}, ...unref(params) });
 | 
			
		||||
        Message.success(message);
 | 
			
		||||
        return Promise.resolve({ formData, data });
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { Notification } from "@arco-design/web-vue";
 | 
			
		||||
import createInstance from "@gpt-vue/packages/request"
 | 
			
		||||
import type { BaseResponse } from "@gpt-vue/packages/type";
 | 
			
		||||
 | 
			
		||||
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/common/upload/minio";
 | 
			
		||||
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/upload";
 | 
			
		||||
 | 
			
		||||
export const instance = createInstance()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import {
 | 
			
		||||
  IconDashboard,
 | 
			
		||||
  IconOrderedList,
 | 
			
		||||
  IconCalendar,
 | 
			
		||||
  IconSettings,
 | 
			
		||||
} from "@arco-design/web-vue/es/icon";
 | 
			
		||||
 | 
			
		||||
const menu = [
 | 
			
		||||
@@ -60,6 +61,15 @@ const menu = [
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import('@/views/Chats/ChatsContainer.vue')
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/system',
 | 
			
		||||
    name: 'System',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: "系统设置",
 | 
			
		||||
      icon: IconSettings,
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import('@/views/System/SystemContainer.vue')
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/loginLog',
 | 
			
		||||
    name: 'LoginLog',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										149
									
								
								gpt-vue/projects/vue-admin/src/views/System/SystemBaseConfig.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								gpt-vue/projects/vue-admin/src/views/System/SystemBaseConfig.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import useRequest from "@/composables/useRequest";
 | 
			
		||||
import { getConfig, modelList, save } from "./api";
 | 
			
		||||
import SystemUploader from "./SystemUploader.vue";
 | 
			
		||||
 | 
			
		||||
const { formRef, formData: system, handleSubmit, submitting } = useSubmit({});
 | 
			
		||||
 | 
			
		||||
const [getModelOptions, modelOptions, modelOptionsLoading] = useRequest(modelList);
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  title: [{ required: true, message: "请输入网站标题" }],
 | 
			
		||||
  admin_title: [{ required: true, message: "请输入控制台标题" }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  await handleSubmit(
 | 
			
		||||
    () =>
 | 
			
		||||
      save({
 | 
			
		||||
        key: "system",
 | 
			
		||||
        config: system,
 | 
			
		||||
      }),
 | 
			
		||||
    {}
 | 
			
		||||
  );
 | 
			
		||||
  Message.success("保存成功");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  const { data } = await getConfig({ key: "system" });
 | 
			
		||||
  data && Object.assign(system, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  getModelOptions();
 | 
			
		||||
  reload();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="system" :rules="rules" auto-laba-width>
 | 
			
		||||
    <a-form-item label="网站标题" field="title">
 | 
			
		||||
      <a-input v-model="system['title']" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="控制台标题" field="admin_title">
 | 
			
		||||
      <a-input v-model="system['admin_title']" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="注册赠送对话次数" field="user_init_calls">
 | 
			
		||||
      <a-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="注册赠送绘图次数" field="init_img_calls">
 | 
			
		||||
      <a-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="邀请赠送对话次数" field="invite_chat_calls">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="system['invite_chat_calls']"
 | 
			
		||||
        placeholder="邀请新用户注册赠送对话次数"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="邀请赠送绘图次数" field="invite_img_calls">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="system['invite_img_calls']"
 | 
			
		||||
        placeholder="邀请新用户注册赠送绘图次数"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="VIP每月对话次数" field="vip_month_calls">
 | 
			
		||||
      <a-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="VIP每月绘图次数" field="vip_month_img_calls">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="system['vip_month_img_calls']"
 | 
			
		||||
        placeholder="VIP用户每月赠送绘图次数"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="开放注册" field="enabled_register">
 | 
			
		||||
      <a-switch v-model="system['enabled_register']" />
 | 
			
		||||
      <a-tooltip content="关闭注册之后只能通过管理后台添加用户" position="right">
 | 
			
		||||
        <icon-info-circle-fill size="18" />
 | 
			
		||||
      </a-tooltip>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="注册方式" field="register_ways">
 | 
			
		||||
      <a-checkbox-group v-model="system['register_ways']">
 | 
			
		||||
        <a-checkbox value="mobile">手机注册</a-checkbox>
 | 
			
		||||
        <a-checkbox value="email">邮箱注册</a-checkbox>
 | 
			
		||||
      </a-checkbox-group>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="启用众筹功能" field="enabled_reward">
 | 
			
		||||
      <a-switch v-model="system['enabled_reward']" />
 | 
			
		||||
      <a-tooltip content="如果关闭次功能将不在用户菜单显示众筹二维码" position="right">
 | 
			
		||||
        <icon-info-circle-fill size="18" />
 | 
			
		||||
      </a-tooltip>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <template v-if="system['enabled_reward']">
 | 
			
		||||
      <a-form-item label="单次对话价格" field="chat_call_price">
 | 
			
		||||
        <a-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="单次绘图价格" field="img_call_price">
 | 
			
		||||
        <a-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item label="收款二维码" field="reward_img">
 | 
			
		||||
        <SystemUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
    </template>
 | 
			
		||||
    <a-form-item label="微信客服二维码" field="wechat_card_url">
 | 
			
		||||
      <SystemUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="订单超时时间" field="order_pay_timeout">
 | 
			
		||||
      <a-space style="width: 100%">
 | 
			
		||||
        <a-input
 | 
			
		||||
          v-model.number="system['order_pay_timeout']"
 | 
			
		||||
          placeholder="单位:秒"
 | 
			
		||||
          style="width: 100%"
 | 
			
		||||
        />
 | 
			
		||||
        <a-tooltip position="right">
 | 
			
		||||
          <icon-info-circle-fill size="18" />
 | 
			
		||||
          <template #content> 系统会定期清理超时未支付的订单<br />默认值:900秒 </template>
 | 
			
		||||
        </a-tooltip>
 | 
			
		||||
      </a-space>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="会员充值说明" field="order_pay_info_text">
 | 
			
		||||
      <a-textarea
 | 
			
		||||
        v-model="system['order_pay_info_text']"
 | 
			
		||||
        :autosize="{ minRows: 3, maxRows: 10 }"
 | 
			
		||||
        placeholder="请输入会员充值说明文字,比如介绍会员计划"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="默认AI模型" field="default_models">
 | 
			
		||||
      <a-space style="width: 100%">
 | 
			
		||||
        <a-select
 | 
			
		||||
          v-model="system['default_models']"
 | 
			
		||||
          multiple
 | 
			
		||||
          :filterable="true"
 | 
			
		||||
          placeholder="选择AI模型,多选"
 | 
			
		||||
          :options="modelOptions"
 | 
			
		||||
          :loading="modelOptionsLoading"
 | 
			
		||||
          :field-names="{ value: 'value', label: 'name' }"
 | 
			
		||||
          style="width: 100%"
 | 
			
		||||
        >
 | 
			
		||||
        </a-select>
 | 
			
		||||
        <a-tooltip content="新用户注册默认开通的 AI 模型" position="right">
 | 
			
		||||
          <icon-info-circle-fill size="18" />
 | 
			
		||||
        </a-tooltip>
 | 
			
		||||
      </a-space>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item>
 | 
			
		||||
      <a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										131
									
								
								gpt-vue/projects/vue-admin/src/views/System/SystemChatConfig.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								gpt-vue/projects/vue-admin/src/views/System/SystemChatConfig.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import { getConfig, save } from "./api";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  formRef,
 | 
			
		||||
  formData: chat,
 | 
			
		||||
  handleSubmit,
 | 
			
		||||
  submitting,
 | 
			
		||||
} = useSubmit({
 | 
			
		||||
  open_ai: { temperature: 1, max_tokens: 1024 },
 | 
			
		||||
  azure: { temperature: 1, max_tokens: 1024 },
 | 
			
		||||
  chat_gml: { temperature: 0.95, max_tokens: 1024 },
 | 
			
		||||
  baidu: { temperature: 0.95, max_tokens: 1024 },
 | 
			
		||||
  xun_fei: { temperature: 0.5, max_tokens: 1024 },
 | 
			
		||||
  context_deep: 0,
 | 
			
		||||
  enable_context: true,
 | 
			
		||||
  enable_history: true,
 | 
			
		||||
  dall_api_url: "",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  init_chat_calls: [{ required: true, message: "请输入赠送对话次数" }],
 | 
			
		||||
  user_img_calls: [{ required: true, message: "请输入赠送绘图次数" }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  await handleSubmit(
 | 
			
		||||
    () =>
 | 
			
		||||
      save({
 | 
			
		||||
        key: "chat",
 | 
			
		||||
        config: chat,
 | 
			
		||||
      }),
 | 
			
		||||
    {}
 | 
			
		||||
  );
 | 
			
		||||
  Message.success("保存成功");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  const { data } = await getConfig({ key: "chat" });
 | 
			
		||||
  data && Object.assign(chat, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(reload);
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="chat" :rules="rules" auto-laba-width>
 | 
			
		||||
    <a-form-item label="开启聊天上下文">
 | 
			
		||||
      <a-switch v-model="chat['enable_context']" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="保存聊天记录">
 | 
			
		||||
      <a-switch v-model="chat['enable_history']" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item
 | 
			
		||||
      label="会话上下文深度"
 | 
			
		||||
      extra="会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为 0
 | 
			
		||||
        则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的
 | 
			
		||||
        API。"
 | 
			
		||||
    >
 | 
			
		||||
      <a-input-number v-model="chat['context_deep']" :min="0" :max="10" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">OpenAI</a-divider>
 | 
			
		||||
    <a-form-item label="模型创意度" extra="值越大 AI 回答越发散,值越小回答越保守,建议保持默认值">
 | 
			
		||||
      <a-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="最大响应长度">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['open_ai']['max_tokens']"
 | 
			
		||||
        placeholder="回复的最大字数,最大4096"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">Azure</a-divider>
 | 
			
		||||
    <a-form-item label="模型创意度" extra="值越大 AI 回答越发散,值越小回答越保守,建议保持默认值">
 | 
			
		||||
      <a-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="最大响应长度">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['azure']['max_tokens']"
 | 
			
		||||
        placeholder="回复的最大字数,最大4096"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">ChatGLM</a-divider>
 | 
			
		||||
    <a-form-item label="模型创意度" extra="值越大 AI 回答越发散,值越小回答越保守,建议保持默认值">
 | 
			
		||||
      <a-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="最大响应长度">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['chat_gml']['max_tokens']"
 | 
			
		||||
        placeholder="回复的最大字数,最大4096"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">文心一言</a-divider>
 | 
			
		||||
    <a-form-item label="模型创意度" extra="值越大 AI 回答越发散,值越小回答越保守,建议保持默认值">
 | 
			
		||||
      <a-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="最大响应长度">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['baidu']['max_tokens']"
 | 
			
		||||
        placeholder="回复的最大字数,最大4096"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">讯飞星火</a-divider>
 | 
			
		||||
    <a-form-item label="模型创意度" extra="值越大 AI 回答越发散,值越小回答越保守,建议保持默认值">
 | 
			
		||||
      <a-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1" />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item label="最大响应长度">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['xun_fei']['max_tokens']"
 | 
			
		||||
        placeholder="回复的最大字数,最大4096"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
 | 
			
		||||
    <a-divider content-position="center">AI绘图</a-divider>
 | 
			
		||||
    <a-form-item label="DALL-E3出图数量">
 | 
			
		||||
      <a-input
 | 
			
		||||
        v-model.number="chat['dall_img_num']"
 | 
			
		||||
        placeholder="调用 DALL E3 API 传入的出图数量"
 | 
			
		||||
      />
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
    <a-form-item>
 | 
			
		||||
      <a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import SystemBaseConfig from "./SystemBaseConfig.vue";
 | 
			
		||||
import SystemChatConfig from "./SystemChatConfig.vue";
 | 
			
		||||
import SystemNoticeConfig from "./SystemNoticeConfig.vue";
 | 
			
		||||
const tabsList = [
 | 
			
		||||
  { key: "1", title: "基本设置", components: SystemBaseConfig },
 | 
			
		||||
  { key: "2", title: "模型设置", components: SystemChatConfig },
 | 
			
		||||
  { key: "3", title: "公告设置", components: SystemNoticeConfig },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const activeKey = ref(tabsList[0].key);
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-tabs v-model:active-key="activeKey" lazy-load>
 | 
			
		||||
    <a-tab-pane v-for="item in tabsList" :key="item.key" :title="item.title">
 | 
			
		||||
      <component :is="item.components" />
 | 
			
		||||
    </a-tab-pane>
 | 
			
		||||
  </a-tabs>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -0,0 +1,65 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { onMounted } from "vue";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import MdEditor from "md-editor-v3";
 | 
			
		||||
import "md-editor-v3/lib/style.css";
 | 
			
		||||
import http from "@/http/config";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import { getConfig, save } from "./api";
 | 
			
		||||
 | 
			
		||||
const { formRef, formData, handleSubmit, submitting } = useSubmit({
 | 
			
		||||
  content: "",
 | 
			
		||||
  updated: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleSave = async () => {
 | 
			
		||||
  await handleSubmit(
 | 
			
		||||
    () =>
 | 
			
		||||
      save({
 | 
			
		||||
        key: "notice",
 | 
			
		||||
        config: formData,
 | 
			
		||||
      }),
 | 
			
		||||
    {}
 | 
			
		||||
  );
 | 
			
		||||
  Message.success("保存成功");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const reload = async () => {
 | 
			
		||||
  const { data } = await getConfig({ key: "notice" });
 | 
			
		||||
  data && Object.assign(formData, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onUploadImg = (files, callback) => {
 | 
			
		||||
  Promise.all(
 | 
			
		||||
    files.map((file) => {
 | 
			
		||||
      return new Promise((rev, rej) => {
 | 
			
		||||
        const formData = new FormData();
 | 
			
		||||
        formData.append("file", file, file.name);
 | 
			
		||||
        http({
 | 
			
		||||
          url: `/api/upload`,
 | 
			
		||||
          data: formData,
 | 
			
		||||
        })
 | 
			
		||||
          .then((res) => rev(res))
 | 
			
		||||
          .catch((e) => rej(e));
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      Message.success({ content: "上传成功", duration: 500 });
 | 
			
		||||
      callback(res.map((item) => item.data.url));
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      Message.error("图片上传失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(reload);
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-form ref="formRef" :model="formData" auto-laba-width>
 | 
			
		||||
    <md-editor v-model="formData.content" @on-upload-img="onUploadImg" />
 | 
			
		||||
    <a-form-item>
 | 
			
		||||
      <a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
 | 
			
		||||
    </a-form-item>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
 | 
			
		||||
import { uploadUrl } from "@/http/config";
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  modelValue: String,
 | 
			
		||||
  placeholder: String,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(["update:modelValue"]);
 | 
			
		||||
 | 
			
		||||
const uploadProps = computed<UploadInstance["$props"]>(() => ({
 | 
			
		||||
  action: uploadUrl,
 | 
			
		||||
  name: "file",
 | 
			
		||||
  headers: { [__AUTH_KEY]: localStorage.getItem(__AUTH_KEY) },
 | 
			
		||||
  showFileList: false,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const handleChange = (_, file: FileItem) => {
 | 
			
		||||
  console.log(file.response);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-space>
 | 
			
		||||
    <a-input :model-value="modelValue" :placeholder="placeholder" readonly>
 | 
			
		||||
      <template #append>
 | 
			
		||||
        <a-upload v-bind="uploadProps" @change="handleChange">
 | 
			
		||||
          <template #upload-button>
 | 
			
		||||
            <icon-upload />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-upload>
 | 
			
		||||
      </template>
 | 
			
		||||
    </a-input>
 | 
			
		||||
  </a-space>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										25
									
								
								gpt-vue/projects/vue-admin/src/views/System/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								gpt-vue/projects/vue-admin/src/views/System/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import http from "@/http/config";
 | 
			
		||||
 | 
			
		||||
export const getConfig = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/config/get",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const save = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/config/update",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const modelList = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/model/list",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user