mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	feat(ui): 函数管理
This commit is contained in:
		
							
								
								
									
										58
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										58
									
								
								gpt-vue/pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -60,6 +60,9 @@ importers:
 | 
			
		||||
      '@vitejs/plugin-vue-jsx':
 | 
			
		||||
        specifier: ^3.1.0
 | 
			
		||||
        version: 3.1.0(vite@5.1.5)(vue@3.4.21)
 | 
			
		||||
      '@vue/eslint-config-prettier':
 | 
			
		||||
        specifier: ^7.0.0
 | 
			
		||||
        version: 7.1.0(eslint@8.57.0)(prettier@3.2.5)
 | 
			
		||||
      '@vue/eslint-config-typescript':
 | 
			
		||||
        specifier: ^12.0.0
 | 
			
		||||
        version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
 | 
			
		||||
@@ -1245,6 +1248,18 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 | 
			
		||||
  /@vue/eslint-config-prettier@7.1.0(eslint@8.57.0)(prettier@3.2.5):
 | 
			
		||||
    resolution: {integrity: sha512-Pv/lVr0bAzSIHLd9iz0KnvAr4GKyCEl+h52bc4e5yWuDVtLgFwycF7nrbWTAQAS+FU6q1geVd07lc6EWfJiWKQ==}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      eslint: '>= 7.28.0'
 | 
			
		||||
      prettier: '>= 2.0.0'
 | 
			
		||||
    dependencies:
 | 
			
		||||
      eslint: 8.57.0
 | 
			
		||||
      eslint-config-prettier: 8.10.0(eslint@8.57.0)
 | 
			
		||||
      eslint-plugin-prettier: 4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@3.2.5)
 | 
			
		||||
      prettier: 3.2.5
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3):
 | 
			
		||||
    resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==}
 | 
			
		||||
    engines: {node: ^14.17.0 || >=16.0.0}
 | 
			
		||||
@@ -1670,6 +1685,32 @@ packages:
 | 
			
		||||
    engines: {node: '>=10'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /eslint-config-prettier@8.10.0(eslint@8.57.0):
 | 
			
		||||
    resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==}
 | 
			
		||||
    hasBin: true
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      eslint: '>=7.0.0'
 | 
			
		||||
    dependencies:
 | 
			
		||||
      eslint: 8.57.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.57.0)(prettier@3.2.5):
 | 
			
		||||
    resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
 | 
			
		||||
    engines: {node: '>=12.0.0'}
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      eslint: '>=7.28.0'
 | 
			
		||||
      eslint-config-prettier: '*'
 | 
			
		||||
      prettier: '>=2.0.0'
 | 
			
		||||
    peerDependenciesMeta:
 | 
			
		||||
      eslint-config-prettier:
 | 
			
		||||
        optional: true
 | 
			
		||||
    dependencies:
 | 
			
		||||
      eslint: 8.57.0
 | 
			
		||||
      eslint-config-prettier: 8.10.0(eslint@8.57.0)
 | 
			
		||||
      prettier: 3.2.5
 | 
			
		||||
      prettier-linter-helpers: 1.0.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /eslint-plugin-vue@9.22.0(eslint@8.57.0):
 | 
			
		||||
    resolution: {integrity: sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==}
 | 
			
		||||
    engines: {node: ^14.17.0 || >=16.0.0}
 | 
			
		||||
@@ -1788,6 +1829,10 @@ packages:
 | 
			
		||||
    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /fast-diff@1.3.0:
 | 
			
		||||
    resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /fast-glob@3.3.2:
 | 
			
		||||
    resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
 | 
			
		||||
    engines: {node: '>=8.6.0'}
 | 
			
		||||
@@ -2451,6 +2496,19 @@ packages:
 | 
			
		||||
    engines: {node: '>= 0.8.0'}
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /prettier-linter-helpers@1.0.0:
 | 
			
		||||
    resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
 | 
			
		||||
    engines: {node: '>=6.0.0'}
 | 
			
		||||
    dependencies:
 | 
			
		||||
      fast-diff: 1.3.0
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /prettier@3.2.5:
 | 
			
		||||
    resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
 | 
			
		||||
    engines: {node: '>=14'}
 | 
			
		||||
    hasBin: true
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /proxy-from-env@1.1.0:
 | 
			
		||||
    resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 | 
			
		||||
    dev: false
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,28 @@
 | 
			
		||||
/* eslint-env node */
 | 
			
		||||
require('@rushstack/eslint-patch/modern-module-resolution')
 | 
			
		||||
require("@rushstack/eslint-patch/modern-module-resolution");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  'extends': [
 | 
			
		||||
    'plugin:vue/vue3-essential',
 | 
			
		||||
    'eslint:recommended',
 | 
			
		||||
    '@vue/eslint-config-typescript'
 | 
			
		||||
  extends: [
 | 
			
		||||
    "plugin:vue/vue3-essential",
 | 
			
		||||
    "eslint:recommended",
 | 
			
		||||
    "@vue/eslint-config-typescript",
 | 
			
		||||
    "@vue/eslint-config-prettier",
 | 
			
		||||
  ],
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    ecmaVersion: 'latest'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
    ecmaVersion: "latest",
 | 
			
		||||
    sourceType: "module",
 | 
			
		||||
    parser: "@typescript-eslint/parser",
 | 
			
		||||
    ecmaFeatures: {
 | 
			
		||||
      jsx: true,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  plugins: ["vue", "@typescript-eslint"],
 | 
			
		||||
  rules: {
 | 
			
		||||
    "prettier/prettier": "warn",
 | 
			
		||||
    "@typescript-eslint/ban-ts-comment": "off",
 | 
			
		||||
    "vue/multi-word-component-names": "off",
 | 
			
		||||
    "@typescript-eslint/no-explicit-any": "off",
 | 
			
		||||
    "no-undef": "off",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								gpt-vue/projects/vue-admin/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								gpt-vue/projects/vue-admin/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "printWidth": 100,
 | 
			
		||||
  "tabWidth": 2,
 | 
			
		||||
  "useTabs": false,
 | 
			
		||||
  "singleQuote": false,
 | 
			
		||||
  "semi": true,
 | 
			
		||||
  "trailingComma": "es5",
 | 
			
		||||
  "bracketSpacing": true
 | 
			
		||||
}
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
    "@types/node": "^20.11.10",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.3",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.1.0",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^7.0.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^12.0.0",
 | 
			
		||||
    "@vue/tsconfig": "^0.5.1",
 | 
			
		||||
    "eslint": "^8.49.0",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								gpt-vue/projects/vue-admin/src/components/ConfirmSwitch.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								gpt-vue/projects/vue-admin/src/components/ConfirmSwitch.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { Message, type SwitchInstance } from "@arco-design/web-vue";
 | 
			
		||||
import type { BaseResponse } from "@gpt-vue/packages/type";
 | 
			
		||||
 | 
			
		||||
type OriginProps = SwitchInstance["$props"];
 | 
			
		||||
 | 
			
		||||
interface Props extends /* @vue-ignore */ OriginProps {
 | 
			
		||||
  modelValue: boolean | string | number;
 | 
			
		||||
  api: (params?: any) => Promise<BaseResponse<any>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>();
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(["update:modelValue"]);
 | 
			
		||||
 | 
			
		||||
const _value = computed({
 | 
			
		||||
  get: () => props.modelValue,
 | 
			
		||||
  set: (v) => {
 | 
			
		||||
    emits("update:modelValue", v);
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onBeforeChange = async (params) => {
 | 
			
		||||
  try {
 | 
			
		||||
    await props.api({ ...params, value: !_value.value });
 | 
			
		||||
    Message.success("操作成功");
 | 
			
		||||
    return true;
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.log(err);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-switch v-bind="{ ...props, ...$attrs }" v-model="_value" :before-change="onBeforeChange" />
 | 
			
		||||
</template>
 | 
			
		||||
@@ -5,9 +5,10 @@ import { useTableScroll } from "@/components/SearchTable/utils";
 | 
			
		||||
import { Message } from "@arco-design/web-vue";
 | 
			
		||||
import type { TableRequest, TableOriginalProps } from "./useAsyncTable";
 | 
			
		||||
 | 
			
		||||
export interface SimpleTable extends /* @vue-ignore */ TableOriginalProps {
 | 
			
		||||
interface SimpleTable extends /* @vue-ignore */ TableOriginalProps {
 | 
			
		||||
  request: TableRequest<Record<string, unknown>>;
 | 
			
		||||
  params?: Record<string, unknown>;
 | 
			
		||||
  columns?: TableOriginalProps["columns"];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<SimpleTable>();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								gpt-vue/projects/vue-admin/src/composables/useSubmit.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								gpt-vue/projects/vue-admin/src/composables/useSubmit.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
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) {
 | 
			
		||||
  const formRef = ref();
 | 
			
		||||
  const formData = reactive<T>({ ...defaultData });
 | 
			
		||||
  const submitting = ref(false);
 | 
			
		||||
 | 
			
		||||
  const handleSubmit = async (api: (params?: any) => Promise<BaseResponse<R>>, params) => {
 | 
			
		||||
    submitting.value = true;
 | 
			
		||||
    try {
 | 
			
		||||
      const hasError = await formRef.value?.validate();
 | 
			
		||||
      if (!hasError) {
 | 
			
		||||
        const { data, message } = await api({ ...formData, ...unref(params) });
 | 
			
		||||
        Message.success(message);
 | 
			
		||||
        return Promise.resolve({ formData, data });
 | 
			
		||||
      }
 | 
			
		||||
      return Promise.reject(false);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      return Promise.reject(err);
 | 
			
		||||
    } finally {
 | 
			
		||||
      submitting.value = false;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return { formRef, formData, handleSubmit, submitting };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default useSubmit;
 | 
			
		||||
@@ -42,6 +42,15 @@ const menu = [
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import('@/views/Reward/RewardContainer.vue')
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/functions',
 | 
			
		||||
    name: 'Functions',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: "函数管理",
 | 
			
		||||
      icon: IconCalendar,
 | 
			
		||||
    },
 | 
			
		||||
    component: () => import('@/views/Functions/FunctionsContainer.vue')
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/loginLog',
 | 
			
		||||
    name: 'LoginLog',
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
 | 
			
		||||
import ConfirmSwitch from "@/components/ConfirmSwitch.vue";
 | 
			
		||||
import usePopup from "@/composables/usePopup";
 | 
			
		||||
import { getList, remove, setStatus, save } from "./api";
 | 
			
		||||
import FunctionsForm from "./FunctionsForm.vue";
 | 
			
		||||
 | 
			
		||||
const columns: TableColumnData[] = [
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "name",
 | 
			
		||||
    title: "函数名称",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "label",
 | 
			
		||||
    title: "函数别名",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "description",
 | 
			
		||||
    title: "功能描述",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    dataIndex: "enabled",
 | 
			
		||||
    title: "启用状态",
 | 
			
		||||
    slotName: "switch",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "操作",
 | 
			
		||||
    slotName: "actions",
 | 
			
		||||
    fixed: "right",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const openFormModal = usePopup(FunctionsForm, {
 | 
			
		||||
  nodeProps: ([_, record]) => ({ record }),
 | 
			
		||||
  popupProps: ([reload, record], exposed) => ({
 | 
			
		||||
    title: `${record.id ? "编辑" : "新增"}函数`,
 | 
			
		||||
    width: "800px",
 | 
			
		||||
    onBeforeOk: async (done) => {
 | 
			
		||||
      await exposed()?.handleSubmit(save, {
 | 
			
		||||
        id: record.id,
 | 
			
		||||
        parameters: exposed()?.parameters(),
 | 
			
		||||
      });
 | 
			
		||||
      await reload();
 | 
			
		||||
      done(true);
 | 
			
		||||
    },
 | 
			
		||||
  }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleRemove = async (id, reload) => {
 | 
			
		||||
  await remove({ id });
 | 
			
		||||
  Message.success("删除成功");
 | 
			
		||||
  await reload();
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-button type="primary" @click="openFormModal">新增</a-button>
 | 
			
		||||
  <SimpleTable :request="getList" :columns="columns" :pagination="false">
 | 
			
		||||
    <template #switch="{ record, column }">
 | 
			
		||||
      <ConfirmSwitch
 | 
			
		||||
        v-model="record[column.dataIndex]"
 | 
			
		||||
        :api="(p) => setStatus({ ...p, id: record.id, filed: 'enabled' })"
 | 
			
		||||
      />
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #exchange="{ record }">
 | 
			
		||||
      <a-tag v-if="record.exchange.calls > 0">聊天{{ record.exchange.calls }}次</a-tag>
 | 
			
		||||
      <a-tag v-else-if="record.exchange.img_calls > 0" color="green"
 | 
			
		||||
        >绘图{{ record.exchange.img_calls }}次</a-tag
 | 
			
		||||
      >
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #actions="{ record, reload }">
 | 
			
		||||
      <a-link @click="openFormModal(reload, record)">编辑</a-link>
 | 
			
		||||
      <a-popconfirm
 | 
			
		||||
        content="是否删除?"
 | 
			
		||||
        position="left"
 | 
			
		||||
        type="warning"
 | 
			
		||||
        :on-before-ok="() => handleRemove(record.id, reload)"
 | 
			
		||||
      >
 | 
			
		||||
        <a-link status="danger">删除</a-link>
 | 
			
		||||
      </a-popconfirm>
 | 
			
		||||
    </template>
 | 
			
		||||
  </SimpleTable>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,74 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, watchEffect } from "vue";
 | 
			
		||||
import useSubmit from "@/composables/useSubmit";
 | 
			
		||||
import FunctionsFormTable from "./FunctionsFormTable.vue";
 | 
			
		||||
import translateTableData from "./translateTableData";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  record: Object,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const tableData = ref([]);
 | 
			
		||||
const { formRef, formData, handleSubmit, submitting } = useSubmit({
 | 
			
		||||
  name: "",
 | 
			
		||||
  label: "",
 | 
			
		||||
  description: "",
 | 
			
		||||
  action: "",
 | 
			
		||||
  token: "",
 | 
			
		||||
  parameters: {},
 | 
			
		||||
  enabled: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules = {
 | 
			
		||||
  name: [{ required: true, message: "请输入函数名称" }],
 | 
			
		||||
  label: [{ required: true, message: "请输入函数标签" }],
 | 
			
		||||
  description: [{ required: true, message: "请输入函数功能描述" }],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
watchEffect(() => {
 | 
			
		||||
  Object.assign(formData, props.record ?? {});
 | 
			
		||||
  tableData.value = translateTableData.get(formData.parameters);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  handleSubmit,
 | 
			
		||||
  parameters: () => translateTableData.set(tableData.value),
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-spin :loading="submitting" style="width: 100%">
 | 
			
		||||
    <a-form ref="formRef" :model="formData" auto-label-width :rules="rules">
 | 
			
		||||
      <a-form-item field="name" label="函数名称">
 | 
			
		||||
        <a-input v-model="formData.name" placeholder="函数名称最好为英文" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item field="label" label="函数标签">
 | 
			
		||||
        <a-input v-model="formData.label" placeholder="函数的中文名称" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item field="description" label="功能描述">
 | 
			
		||||
        <a-input v-model="formData.description" placeholder="函数的中文名称" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
 | 
			
		||||
      <a-form-item field="parameters" label="函数参数">
 | 
			
		||||
        <FunctionsFormTable v-model="tableData" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
 | 
			
		||||
      <a-form-item field="action" label="API 地址">
 | 
			
		||||
        <a-input v-model="formData.action" placeholder="该函数实现的API地址,可以是第三方服务API" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item field="token" label="API Token">
 | 
			
		||||
        <a-input-search v-model="formData.token" placeholder="API授权Token">
 | 
			
		||||
          <template #append>
 | 
			
		||||
            <a-tooltip
 | 
			
		||||
              content="只有本地服务才可以使用自动生成Token第三方服务请填写第三方服务API Token"
 | 
			
		||||
            >
 | 
			
		||||
              <a-button>生成Token</a-button>
 | 
			
		||||
            </a-tooltip>
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-input-search>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item field="enabled" label="启用状态">
 | 
			
		||||
        <a-switch v-model="formData.enabled" />
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
    </a-form>
 | 
			
		||||
  </a-spin>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { IconDelete } from "@arco-design/web-vue/es/icon";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default: () => [],
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(["update:modelValue"]);
 | 
			
		||||
 | 
			
		||||
const handleCreateRow = () => {
 | 
			
		||||
  emits("update:modelValue", [
 | 
			
		||||
    ...(props.modelValue ?? []),
 | 
			
		||||
    { name: "", type: "", description: "", required: false },
 | 
			
		||||
  ]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleRemoveRow = (index) => {
 | 
			
		||||
  emits(
 | 
			
		||||
    "update:modelValue",
 | 
			
		||||
    props.modelValue.filter((_, i) => i !== index)
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <a-space direction="vertical" style="width: 100%">
 | 
			
		||||
    <a-table :data="modelValue" size="small" style="width: 100%" :pagination="false">
 | 
			
		||||
      <template #columns>
 | 
			
		||||
        <a-table-column title="参数名称" :width="120">
 | 
			
		||||
          <template #cell="scope">
 | 
			
		||||
            <a-input v-model="scope.record.name" />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-table-column>
 | 
			
		||||
        <a-table-column title="参数类型" :width="120">
 | 
			
		||||
          <template #cell="scope">
 | 
			
		||||
            <a-select
 | 
			
		||||
              v-model="scope.record.type"
 | 
			
		||||
              placeholder="参数类型"
 | 
			
		||||
              :options="['string', 'number']"
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-table-column>
 | 
			
		||||
        <a-table-column title="参数描述">
 | 
			
		||||
          <template #cell="scope">
 | 
			
		||||
            <a-input v-model="scope.record.description" />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-table-column>
 | 
			
		||||
 | 
			
		||||
        <a-table-column title="必填参数" :width="100">
 | 
			
		||||
          <template #cell="scope">
 | 
			
		||||
            <a-checkbox v-model="scope.record.required" />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-table-column>
 | 
			
		||||
 | 
			
		||||
        <a-table-column title="操作" :width="80">
 | 
			
		||||
          <template #cell="scope">
 | 
			
		||||
            <a-button
 | 
			
		||||
              status="danger"
 | 
			
		||||
              :icon="IconDelete"
 | 
			
		||||
              circle
 | 
			
		||||
              @click="handleRemoveRow(scope.rowIndex)"
 | 
			
		||||
              size="small"
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-table-column>
 | 
			
		||||
      </template>
 | 
			
		||||
    </a-table>
 | 
			
		||||
    <a-button type="primary" long @click="handleCreateRow">新增参数</a-button>
 | 
			
		||||
  </a-space>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										41
									
								
								gpt-vue/projects/vue-admin/src/views/Functions/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								gpt-vue/projects/vue-admin/src/views/Functions/api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import http from "@/http/config";
 | 
			
		||||
 | 
			
		||||
export const getList = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/function/list",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const save = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/function/save",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const remove = (params) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/function/remove",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const setStatus = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/function/set",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const token = (data) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/function/token",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    data
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
const get = (origin) => {
 | 
			
		||||
  const { properties, required, type } = origin;
 | 
			
		||||
  if (type === "object") {
 | 
			
		||||
    const array = Object.keys(properties).reduce((prev, name) => {
 | 
			
		||||
      return [
 | 
			
		||||
        ...prev,
 | 
			
		||||
        {
 | 
			
		||||
          name,
 | 
			
		||||
          ...properties[name],
 | 
			
		||||
          required: required.includes(name),
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    }, []);
 | 
			
		||||
    return array;
 | 
			
		||||
  }
 | 
			
		||||
  return [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const set = (tableData) => {
 | 
			
		||||
  const properties = tableData.reduce((prev, curr) => {
 | 
			
		||||
    return {
 | 
			
		||||
      ...prev,
 | 
			
		||||
      [curr.name]: {
 | 
			
		||||
        description: curr.description,
 | 
			
		||||
        type: curr.type,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }, {});
 | 
			
		||||
  const required = tableData.filter((i) => i.required).map((i) => i.name);
 | 
			
		||||
  return { properties, required, type: "object" }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default { get, set }
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import type { TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
 | 
			
		||||
import { dateFormat } from "@gpt-vue/packages/utils";
 | 
			
		||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
 | 
			
		||||
import { getList } from "./api";
 | 
			
		||||
import { getList, remove } from "./api";
 | 
			
		||||
 | 
			
		||||
const columns: TableColumnData[] = [
 | 
			
		||||
  {
 | 
			
		||||
@@ -38,22 +38,38 @@ const columns: TableColumnData[] = [
 | 
			
		||||
    title: "操作",
 | 
			
		||||
    slotName: "actions",
 | 
			
		||||
    fixed: "right",
 | 
			
		||||
    width: 80,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const handleRemove = async (id, reload) => {
 | 
			
		||||
  await remove({ id });
 | 
			
		||||
  Message.success("删除成功");
 | 
			
		||||
  await reload();
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <SimpleTable :request="getList" :columns="columns">
 | 
			
		||||
    <template #updated_at="{ record }">
 | 
			
		||||
      <span v-if="record.status">{{ dateFormat(record.updated_at) }}</span>
 | 
			
		||||
      <a-tag v-else>未核销</a-tag>
 | 
			
		||||
      <a-tag v-else color="blue">未核销</a-tag>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #exchange="{ record }">
 | 
			
		||||
      <a-tag v-if="record.exchange.calls > 0"
 | 
			
		||||
        >聊天{{ record.exchange.calls }}次</a-tag
 | 
			
		||||
      >
 | 
			
		||||
      <a-tag v-else-if="record.exchange.img_calls > 0"
 | 
			
		||||
      <a-tag v-if="record.exchange.calls > 0">聊天{{ record.exchange.calls }}次</a-tag>
 | 
			
		||||
      <a-tag v-else-if="record.exchange.img_calls > 0" color="green"
 | 
			
		||||
        >绘图{{ record.exchange.img_calls }}次</a-tag
 | 
			
		||||
      >
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #actions="{ record, reload }">
 | 
			
		||||
      <a-popconfirm
 | 
			
		||||
        content="是否删除?"
 | 
			
		||||
        position="left"
 | 
			
		||||
        type="warning"
 | 
			
		||||
        :on-before-ok="() => handleRemove(record.id, reload)"
 | 
			
		||||
      >
 | 
			
		||||
        <a-link status="danger">删除</a-link>
 | 
			
		||||
      </a-popconfirm>
 | 
			
		||||
    </template>
 | 
			
		||||
  </SimpleTable>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,12 @@ export const getList = (params?: Record<string, unknown>) => {
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const remove = (params?: Record<string, unknown>) => {
 | 
			
		||||
  return http({
 | 
			
		||||
    url: "/api/admin/reward/remove",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    params
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@
 | 
			
		||||
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
 | 
			
		||||
  "exclude": ["src/**/__tests__/*"],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "strict": false,
 | 
			
		||||
    "noImplicitAny": false,
 | 
			
		||||
    "composite": true,
 | 
			
		||||
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
    "playwright.config.*"
 | 
			
		||||
  ],
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    
 | 
			
		||||
    "composite": true,
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user