restore new ui files
@@ -6,4 +6,4 @@ declare module "*.vue" {
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare const __AUTH_KEY: string;
|
||||
declare const __AUTH_KEY: string;
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@gpt-vue-projects/vue-admin",
|
||||
"name": "@chatgpt-plus-projects/admin",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/web-vue": "^2.54.6",
|
||||
"@gpt-vue/packages": "workspace:^1.0.0",
|
||||
"@chatgpt-plus/packages": "workspace:^1.0.0",
|
||||
"echarts": "^5.5.0",
|
||||
"md-editor-v3": "^2.2.1",
|
||||
"pinia": "^2.1.7",
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
@@ -1,7 +1,7 @@
|
||||
<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";
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
|
||||
type OriginProps = SwitchInstance["$props"];
|
||||
|
||||
10
new-ui/projects/admin/src/components/PermissionRender.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { hasPermission } from "@/directives/permission";
|
||||
defineProps<{
|
||||
permission: string | string[] | true;
|
||||
}>();
|
||||
</script>
|
||||
<template>
|
||||
<slot v-if="hasPermission(permission)" />
|
||||
<slot v-else name="none" />
|
||||
</template>
|
||||
@@ -38,30 +38,32 @@ onActivated(handleSearch);
|
||||
<template>
|
||||
<div class="search-table">
|
||||
<div class="search-table-header">
|
||||
<div>
|
||||
<slot name="header-title">{{ props.headerTitle }}</slot>
|
||||
</div>
|
||||
<div class="header-option">
|
||||
<slot name="header-option" :formData="formData" :reload="handleSearch" />
|
||||
<div class="search-table-header-option">
|
||||
<div>
|
||||
<slot name="header-title">{{ props.headerTitle }}</slot>
|
||||
</div>
|
||||
<div class="header-option">
|
||||
<slot name="header-option" :formData="formData" :reload="handleSearch" />
|
||||
</div>
|
||||
</div>
|
||||
<FormSection
|
||||
v-model="formData"
|
||||
:columns="columns"
|
||||
:submitting="tableConfig.loading as boolean"
|
||||
@request="handleSearch"
|
||||
>
|
||||
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
||||
<slot :name="slot" v-bind="{ ...config, reload: handleSearch }" />
|
||||
</template>
|
||||
</FormSection>
|
||||
</div>
|
||||
<FormSection
|
||||
v-model="formData"
|
||||
:columns="columns"
|
||||
:submitting="tableConfig.loading as boolean"
|
||||
@request="handleSearch"
|
||||
>
|
||||
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
||||
<slot :name="slot" v-bind="{ ...config, reload: handleSearch }" />
|
||||
</template>
|
||||
</FormSection>
|
||||
<div ref="tableContainerRef" class="search-table-container">
|
||||
<ATable
|
||||
v-bind="{
|
||||
...$attrs,
|
||||
...tableConfig,
|
||||
...props,
|
||||
scroll: useTableScroll(_columns, tableContainerRef as HTMLElement),
|
||||
scroll: useTableScroll(_columns),
|
||||
columns: _columns,
|
||||
}"
|
||||
>
|
||||
@@ -74,14 +76,18 @@ onActivated(handleSearch);
|
||||
</template>
|
||||
<style scoped>
|
||||
.search-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
.search-table-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.search-table-header {
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
}
|
||||
.search-table-header-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, onMounted, reactive, unref, type Ref } from "vue";
|
||||
import type { TableInstance } from "@arco-design/web-vue";
|
||||
import type { BaseResponse, ListResponse } from "@gpt-vue/packages/type";
|
||||
import type { BaseResponse, ListResponse } from "@chatgpt-plus/packages/type";
|
||||
|
||||
export type TableOriginalProps = TableInstance["$props"];
|
||||
export type TableRequest<T extends Record<string, unknown>> = (params?: any) => Promise<BaseResponse<ListResponse<T>>>
|
||||
@@ -1,20 +1,11 @@
|
||||
import type { TableColumnData } from "@arco-design/web-vue";
|
||||
import type { SearchTableColumns, SearchColumns } from "./type";
|
||||
|
||||
export function useTableXScroll(columns: TableColumnData[]) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
const width = curr.width ?? 150;
|
||||
return prev + width;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function useTableScroll(columns: SearchTableColumns[], container?: HTMLElement) {
|
||||
export function useTableScroll(columns: SearchTableColumns[]) {
|
||||
const x = columns.reduce((prev, curr) => {
|
||||
const width = curr.hideInTable ? 0 : curr.width ?? 150;
|
||||
return prev + width;
|
||||
}, 0);
|
||||
const y = container?.clientHeight ?? undefined;
|
||||
return { x, y };
|
||||
return { x };
|
||||
}
|
||||
|
||||
export function getDefaultFormData(columns: SearchTableColumns[]) {
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, onMounted, reactive, unref } from "vue";
|
||||
import type { TableInstance } from "@arco-design/web-vue";
|
||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
|
||||
export type TableOriginalProps = TableInstance["$props"];
|
||||
export type TableRequest<T extends Record<string, unknown>> = (
|
||||
64
new-ui/projects/admin/src/components/SystemMenu.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h } from "vue";
|
||||
import type { Component, PropType } from "vue";
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import { SubMenu, MenuItem } from "@arco-design/web-vue";
|
||||
const CustomMenuItem: Component = defineComponent({
|
||||
props: {
|
||||
tree: {
|
||||
type: Array as PropType<RouteRecordRaw[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup: (props) => {
|
||||
return () =>
|
||||
props.tree?.map((item) => {
|
||||
const _icon = item.meta?.icon ? h(item.meta.icon) : undefined;
|
||||
const hasChildren = Array.isArray(item.children) && item.children.length;
|
||||
if (hasChildren) {
|
||||
return h(
|
||||
SubMenu,
|
||||
{ title: item.meta.title, key: item.name },
|
||||
{
|
||||
default: () => h(CustomMenuItem, { tree: item.children }),
|
||||
icon: () => _icon,
|
||||
}
|
||||
);
|
||||
}
|
||||
return h(
|
||||
MenuItem,
|
||||
{ key: item.name },
|
||||
{ default: () => item.meta.title, icon: () => _icon }
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import router from "@/router";
|
||||
import menu from "@/router/menu";
|
||||
import { hasPermission } from "@/directives/permission";
|
||||
defineProps({
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 200,
|
||||
},
|
||||
});
|
||||
const route = useRoute();
|
||||
const goto = (name: string) => router.push({ name });
|
||||
|
||||
const selectedKeys = computed(() => [route.name]);
|
||||
|
||||
const showMenu = computed(() => menu.filter((item: any) => hasPermission(item.meta?.permission)));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ALayoutSider :style="{ width, height: '100%' }">
|
||||
<AMenu :selectedKeys="selectedKeys" auto-open-selected @menu-item-click="goto">
|
||||
<CustomMenuItem :tree="showMenu" />
|
||||
</AMenu>
|
||||
</ALayoutSider>
|
||||
</template>
|
||||
@@ -1,44 +1,44 @@
|
||||
import usePopup, { type Config } from "./usePopup";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import type { Component } from "vue";
|
||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||
interface Arg {
|
||||
reload?: () => void;
|
||||
record?: Record<string, any>;
|
||||
}
|
||||
|
||||
export default function (
|
||||
node: Component,
|
||||
api: (params?: any) => Promise<BaseResponse<any>>,
|
||||
config?: Config
|
||||
): (arg: Arg) => void {
|
||||
const nodeProps = (arg: Arg[]) => {
|
||||
return {
|
||||
data: arg[0].record || {},
|
||||
...config.nodeProps?.(arg),
|
||||
};
|
||||
};
|
||||
|
||||
const popupProps = (arg: Arg[], getExposed) => {
|
||||
return {
|
||||
width: 750,
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
const exposed = getExposed();
|
||||
const validateRes = await exposed?.formRef.value.validate();
|
||||
if (validateRes) {
|
||||
return false;
|
||||
}
|
||||
const { code } = await api(exposed?.form.value);
|
||||
if (code === 0) {
|
||||
Message.success("操作成功");
|
||||
}
|
||||
arg[0]?.reload?.();
|
||||
return code === 0;
|
||||
},
|
||||
...config.popupProps?.(arg, getExposed),
|
||||
};
|
||||
};
|
||||
|
||||
return usePopup(node, { nodeProps, popupProps });
|
||||
}
|
||||
import usePopup, { type Config } from "./usePopup";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import type { Component } from "vue";
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
interface Arg {
|
||||
reload?: () => void;
|
||||
record?: Record<string, any>;
|
||||
}
|
||||
|
||||
export default function (
|
||||
node: Component,
|
||||
api: (params?: any) => Promise<BaseResponse<any>>,
|
||||
config?: Config
|
||||
): (arg: Arg) => void {
|
||||
const nodeProps = (arg: Arg[]) => {
|
||||
return {
|
||||
data: arg[0].record || {},
|
||||
...config.nodeProps?.(arg),
|
||||
};
|
||||
};
|
||||
|
||||
const popupProps = (arg: Arg[], getExposed) => {
|
||||
return {
|
||||
width: 750,
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
const exposed = getExposed();
|
||||
const validateRes = await exposed?.formRef.value.validate();
|
||||
if (validateRes) {
|
||||
return false;
|
||||
}
|
||||
const { code } = await api(exposed?.form.value);
|
||||
if (code === 0) {
|
||||
Message.success("操作成功");
|
||||
}
|
||||
arg[0]?.reload?.();
|
||||
return code === 0;
|
||||
},
|
||||
...config.popupProps?.(arg, getExposed),
|
||||
};
|
||||
};
|
||||
|
||||
return usePopup(node, { nodeProps, popupProps });
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
|
||||
type Request<T> = (params?: any) => Promise<BaseResponse<T>>
|
||||
function useRequest<T>(request: Request<T>) {
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ref, reactive, unref } from "vue";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
function useSubmit<T extends Record<string, any> = Record<string, any>, R = any>(defaultData?: T) {
|
||||
const formRef = ref();
|
||||
const formData = reactive<T | Record<string, any>>({ ...defaultData ?? {} });
|
||||
@@ -10,14 +9,18 @@ function useSubmit<T extends Record<string, any> = Record<string, any>, R = any>
|
||||
submitting.value = true;
|
||||
try {
|
||||
const hasError = await formRef.value?.validate();
|
||||
if (!hasError) {
|
||||
const { data, message } = await api({ ...formData ?? {}, ...unref(params) });
|
||||
message && Message.success(message);
|
||||
return Promise.resolve({ formData, data });
|
||||
if (hasError) {
|
||||
return Promise.reject({ validateErrors: hasError });
|
||||
}
|
||||
return Promise.reject(false);
|
||||
|
||||
const { data, code, message } = await api({ ...formData ?? {}, ...unref(params) });
|
||||
if (code) {
|
||||
return Promise.reject({ requestErrors: message })
|
||||
}
|
||||
|
||||
return Promise.resolve({ formData, data });
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
return Promise.reject({ errors: err });
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
32
new-ui/projects/admin/src/directives/permission.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
// 判断操作权限
|
||||
export function hasPermission(permissionTag: string | string[] | boolean) {
|
||||
const authStore = useAuthStore();
|
||||
const { is_super_admin, permissions = [] } = authStore;
|
||||
if (is_super_admin) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(permissionTag)) {
|
||||
return permissionTag.every((tag) => permissions.includes(tag));
|
||||
}
|
||||
if (typeof permissionTag === "string") {
|
||||
return permissions.includes(permissionTag);
|
||||
}
|
||||
return permissionTag;
|
||||
}
|
||||
|
||||
function checkPermission(el, binding) {
|
||||
if (!hasPermission(binding.value)) {
|
||||
el.parentNode && el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
export const permission = {
|
||||
mounted(el, binding) {
|
||||
checkPermission(el, binding);
|
||||
},
|
||||
updated(el, binding) {
|
||||
checkPermission(el, binding);
|
||||
},
|
||||
};
|
||||
@@ -1,25 +1,33 @@
|
||||
import router from "@/router";
|
||||
import { Notification } from "@arco-design/web-vue";
|
||||
import createInstance from "@gpt-vue/packages/request"
|
||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||
import createInstance from "@chatgpt-plus/packages/request"
|
||||
import type { BaseResponse } from "@chatgpt-plus/packages/type";
|
||||
|
||||
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/admin/upload";
|
||||
|
||||
export const instance = createInstance(import.meta.env.VITE_PROXY_BASE_URL)
|
||||
|
||||
instance.interceptors.request.use((config) => {
|
||||
config.headers[__AUTH_KEY] = localStorage.getItem(__AUTH_KEY);
|
||||
config.headers["Authorization"] = localStorage.getItem(__AUTH_KEY);
|
||||
const TOKEN = JSON.parse(localStorage.getItem(__AUTH_KEY))?.token
|
||||
config.headers[__AUTH_KEY] = TOKEN;
|
||||
config.headers["Authorization"] = TOKEN;
|
||||
return config;
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
const { data }: { data: BaseResponse<unknown> } = response
|
||||
if (data && typeof data === "object" && data.code !== 0) {
|
||||
if (data.code === 400) {
|
||||
localStorage.removeItem(__AUTH_KEY);
|
||||
router.push({ name: "Login" })
|
||||
if (data && typeof data === "object" && data.code > 0) {
|
||||
switch (data.code) {
|
||||
case 400: {
|
||||
localStorage.removeItem(__AUTH_KEY);
|
||||
router.push({ name: "Login" })
|
||||
break;
|
||||
}
|
||||
case 403: {
|
||||
router.replace({ name: "403" })
|
||||
break;
|
||||
}
|
||||
}
|
||||
Notification.error(data.message ?? '未知错误')
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import http from "@/http/config";
|
||||
|
||||
export const userLogin = (data: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) => {
|
||||
export const userLogin = (data) => {
|
||||
return http({
|
||||
url: "/api/admin/login",
|
||||
method: "post",
|
||||
@@ -26,7 +23,7 @@ export const getSession = () => {
|
||||
};
|
||||
|
||||
|
||||
export const loginLog = (params?: Record<string, unknown>) => {
|
||||
export const loginLog = (params) => {
|
||||
return http({
|
||||
url: "/api/admin/user/loginLog",
|
||||
method: "get",
|
||||
@@ -3,6 +3,8 @@ import { createPinia } from "pinia";
|
||||
import ArcoVue from "@arco-design/web-vue";
|
||||
import ArcoVueIcon from "@arco-design/web-vue/es/icon";
|
||||
import "@arco-design/web-vue/dist/arco.css";
|
||||
import PermissionRender from "@/components/PermissionRender.vue";
|
||||
import { permission } from "@/directives/permission";
|
||||
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
@@ -14,6 +16,9 @@ app.use(router);
|
||||
app.use(ArcoVue);
|
||||
app.use(ArcoVueIcon);
|
||||
|
||||
app.component("PermissionRender", PermissionRender);
|
||||
app.directive("permission", permission);
|
||||
|
||||
app.mount("#app");
|
||||
app.config.warnHandler = (msg, vm, trace) => {
|
||||
if (msg.includes('Invalid prop name: "key" is a reserved property.')) {
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import CustomLayout from '@/components/CustomLayout.vue'
|
||||
import { hasPermission } from "@/directives/permission";
|
||||
import menu from './menu'
|
||||
|
||||
const whiteListRoutes = [
|
||||
@@ -17,20 +18,27 @@ const whiteListRoutes = [
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: CustomLayout,
|
||||
redirect: () => menu[0].path,
|
||||
children: menu
|
||||
children: [
|
||||
{
|
||||
path: "403",
|
||||
name: "403",
|
||||
component: () => import("@/views/NoPermission.vue"),
|
||||
},
|
||||
...menu
|
||||
]
|
||||
},
|
||||
...whiteListRoutes
|
||||
]
|
||||
})
|
||||
|
||||
const whiteList = whiteListRoutes.map((i) => i.name);
|
||||
const whiteList = [...whiteListRoutes.map((i) => i.name), "403"];
|
||||
|
||||
router.beforeEach((to, _, next) => {
|
||||
const authStore = useAuthStore();
|
||||
@@ -44,9 +52,13 @@ router.beforeEach((to, _, next) => {
|
||||
return;
|
||||
}
|
||||
if (!authStore.token) {
|
||||
authStore.$reset();
|
||||
next({ name: "Login" });
|
||||
return;
|
||||
}
|
||||
if (to.meta.permission) {
|
||||
next(!hasPermission(to.meta.permission) ? { name: "403" } : undefined);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
import {
|
||||
IconUser,
|
||||
IconDashboard,
|
||||
IconOrderedList,
|
||||
IconCalendar,
|
||||
IconHeartFill,
|
||||
IconCodeSandbox,
|
||||
IconCodeSquare,
|
||||
IconMessage,
|
||||
IconSettings,
|
||||
IconUserGroup,
|
||||
IconLock,
|
||||
IconCodepen,
|
||||
IconWechatpay,
|
||||
IconRobot,
|
||||
} from "@arco-design/web-vue/es/icon";
|
||||
|
||||
const menu = [
|
||||
import system from "./system";
|
||||
|
||||
const menu: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/dashboard",
|
||||
name: "Dashboard",
|
||||
@@ -30,6 +32,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "用户管理",
|
||||
icon: IconUser,
|
||||
permission: "api_admin_user_list",
|
||||
},
|
||||
component: () => import("@/views/User/UserContainer.vue"),
|
||||
},
|
||||
@@ -37,8 +40,9 @@ const menu = [
|
||||
path: "/role",
|
||||
name: "Role",
|
||||
meta: {
|
||||
title: "角色管理",
|
||||
icon: IconUserGroup,
|
||||
title: "角色模型",
|
||||
icon: IconCodeSandbox,
|
||||
permission: "api_admin_role_list",
|
||||
},
|
||||
component: () => import("@/views/Role/RoleContainer.vue"),
|
||||
},
|
||||
@@ -48,6 +52,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "语言模型",
|
||||
icon: IconCodepen,
|
||||
permission: "api_admin_model_list",
|
||||
},
|
||||
component: () => import("@/views/ChatModel/ChatModelContainer.vue"),
|
||||
},
|
||||
@@ -57,6 +62,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "充值产品",
|
||||
icon: IconWechatpay,
|
||||
permission: "api_admin_product_list",
|
||||
},
|
||||
component: () => import("@/views/Product/ProductContainer.vue"),
|
||||
},
|
||||
@@ -66,6 +72,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "APIKEY",
|
||||
icon: IconLock,
|
||||
permission: "api_admin_apikey_list",
|
||||
},
|
||||
component: () => import("@/views/ApiKey/ApiKeyContainer.vue"),
|
||||
},
|
||||
@@ -75,6 +82,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "充值订单",
|
||||
icon: IconOrderedList,
|
||||
permission: "api_admin_order_list",
|
||||
},
|
||||
component: () => import("@/views/Order/OrderContainer.vue"),
|
||||
},
|
||||
@@ -85,6 +93,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "众筹管理",
|
||||
icon: IconHeartFill,
|
||||
permission: "api_admin_reward_list",
|
||||
},
|
||||
component: () => import("@/views/Reward/RewardContainer.vue"),
|
||||
},
|
||||
@@ -94,6 +103,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "函数管理",
|
||||
icon: IconCodeSquare,
|
||||
permission: "api_admin_function_list",
|
||||
},
|
||||
component: () => import("@/views/Functions/FunctionsContainer.vue"),
|
||||
},
|
||||
@@ -103,6 +113,7 @@ const menu = [
|
||||
meta: {
|
||||
title: "对话管理",
|
||||
icon: IconMessage,
|
||||
permission: "api_admin_chat_list",
|
||||
},
|
||||
component: () => import("@/views/Chats/ChatsContainer.vue"),
|
||||
},
|
||||
@@ -110,29 +121,23 @@ const menu = [
|
||||
path: "/system",
|
||||
name: "System",
|
||||
meta: {
|
||||
title: "系统设置",
|
||||
title: "网站设置",
|
||||
icon: IconSettings,
|
||||
permission: "api_admin_config_get",
|
||||
},
|
||||
component: () => import("@/views/System/SystemContainer.vue"),
|
||||
},
|
||||
{
|
||||
path: "/loginLog",
|
||||
name: "LoginLog",
|
||||
path: "/sys",
|
||||
name: "Sys",
|
||||
meta: {
|
||||
title: "登录日志",
|
||||
icon: IconCalendar,
|
||||
},
|
||||
component: () => import("@/views/LoginLog.vue"),
|
||||
},
|
||||
{
|
||||
path: "/sysAdmin",
|
||||
name: "SysAdmin",
|
||||
meta: {
|
||||
title: "系统管理员",
|
||||
title: "系统设置",
|
||||
icon: IconRobot,
|
||||
},
|
||||
component: () => import("@/views/SysAdmin/SysAdminContainer.vue"),
|
||||
redirect: () => system[0].path,
|
||||
children: system
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export default menu;
|
||||
40
new-ui/projects/admin/src/router/system.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
const system: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "admin",
|
||||
name: "SysAdmin",
|
||||
meta: {
|
||||
title: "系统管理员",
|
||||
permission: "api_admin_sysUser_list",
|
||||
},
|
||||
component: () => import("@/views/SysAdmin/SysAdminContainer.vue"),
|
||||
},
|
||||
{
|
||||
path: "permission",
|
||||
name: "SysPermission",
|
||||
meta: {
|
||||
title: "权限配置",
|
||||
permission: "api_admin_sysPermission_list",
|
||||
},
|
||||
component: () => import("@/views/SysPermission/SysPermissionContainer.vue"),
|
||||
},
|
||||
{
|
||||
path: "role",
|
||||
name: "SysRole",
|
||||
meta: {
|
||||
title: "角色管理",
|
||||
permission: "api_admin_sysRole_list",
|
||||
},
|
||||
component: () => import("@/views/SysRole/SysRoleContainer.vue"),
|
||||
},
|
||||
{
|
||||
path: "loginLog",
|
||||
name: "LoginLog",
|
||||
meta: {
|
||||
title: "登录日志",
|
||||
},
|
||||
component: () => import("@/views/LoginLog.vue"),
|
||||
},
|
||||
]
|
||||
|
||||
export default system
|
||||
8
new-ui/projects/admin/src/router/type.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'vue-router'
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
title?: string
|
||||
icon?: any
|
||||
permission?: boolean | string | string[]
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,29 @@ import { Message } from '@arco-design/web-vue'
|
||||
import { userLogin, userLogout } from '@/http/login'
|
||||
import router from '@/router'
|
||||
|
||||
const defaultState: {
|
||||
token: string
|
||||
is_super_admin?: boolean;
|
||||
permissions?: string[]
|
||||
} = {
|
||||
token: null,
|
||||
is_super_admin: false,
|
||||
permissions: []
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore({
|
||||
id: __AUTH_KEY,
|
||||
state: () => ({ token: null } as { token: string | null }),
|
||||
id: Symbol(__AUTH_KEY).toString(),
|
||||
state: () => ({ ...defaultState }),
|
||||
actions: {
|
||||
init() {
|
||||
this.$state.token = localStorage.getItem(__AUTH_KEY);
|
||||
this.$state = JSON.parse(localStorage.getItem(__AUTH_KEY));
|
||||
},
|
||||
async login(params: any) {
|
||||
try {
|
||||
const { data } = await userLogin(params)
|
||||
if (data) {
|
||||
this.$state.token = data;
|
||||
localStorage.setItem(__AUTH_KEY, data)
|
||||
this.$state = data;
|
||||
localStorage.setItem(__AUTH_KEY, JSON.stringify(data))
|
||||
Message.success('登录成功');
|
||||
router.replace({ name: 'home' })
|
||||
return Promise.resolve(data)
|
||||
@@ -1,11 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { getList, save, deleting, setStatus } from "./api";
|
||||
import { ref } from "vue";
|
||||
import ApiKeyForm from "./ApiKeyForm.vue";
|
||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
// table 配置
|
||||
const columns = [
|
||||
{
|
||||
@@ -29,10 +28,13 @@ const columns = [
|
||||
title: "使用代理",
|
||||
dataIndex: "use_proxy",
|
||||
slotName: "proxy",
|
||||
align: "center",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "最后使用时间",
|
||||
dataIndex: "last_used_at",
|
||||
width: 180,
|
||||
render: ({ record }) => {
|
||||
return dateFormat(record.last_used_at);
|
||||
},
|
||||
@@ -41,24 +43,17 @@ const columns = [
|
||||
title: "启用状态",
|
||||
dataIndex: "enabled",
|
||||
slotName: "status",
|
||||
align: "center",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
slotName: "action",
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
},
|
||||
];
|
||||
|
||||
// 数据
|
||||
const tableData = ref([]);
|
||||
const getData = () => {
|
||||
getList().then(({ code, data }) => {
|
||||
if (code === 0) {
|
||||
tableData.value = data;
|
||||
}
|
||||
});
|
||||
};
|
||||
getData();
|
||||
|
||||
// 新增编辑
|
||||
const popup = useCustomFormPopup(ApiKeyForm, save, {
|
||||
popupProps: (arg) => ({ title: arg[0].record ? "编辑ApiKey" : "新增ApiKey" }),
|
||||
@@ -1,119 +1,119 @@
|
||||
<template>
|
||||
<a-alert type="warning">
|
||||
<div class="warning">
|
||||
{{
|
||||
`注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接\n注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接`
|
||||
}}
|
||||
</div>
|
||||
</a-alert>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:style="{ width: '600px', 'margin-top': '10px' }"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<a-form-item
|
||||
field="platform"
|
||||
label="所属平台"
|
||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="type"
|
||||
label="用途"
|
||||
:rules="[{ required: true, message: '请输入用途' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-select v-model="form.type" placeholder="请输入用途" :options="typeOPtions"> </a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="value"
|
||||
label="API KEY"
|
||||
:rules="[{ required: true, message: '请输入API KEY' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.value" placeholder="请输入API KEY" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="api_url"
|
||||
label="API URL"
|
||||
:rules="[{ required: true, message: '请输入API URL' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.api_url" placeholder="请输入API URL" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="use_proxy" label="使用代理">
|
||||
<a-switch v-model="form.use_proxy" />
|
||||
<a-tooltip
|
||||
content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
|
||||
position="right"
|
||||
>
|
||||
<icon-info-circle-fill />
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item field="enable" label="启用状态">
|
||||
<a-switch v-model="form.enable" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
|
||||
const typeOPtions = [
|
||||
{
|
||||
label: "聊天",
|
||||
value: "chart",
|
||||
},
|
||||
{
|
||||
label: "绘图",
|
||||
value: "img",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.warning {
|
||||
color: #e6a23c;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<a-alert type="warning">
|
||||
<div class="warning">
|
||||
{{
|
||||
`注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接\n注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接`
|
||||
}}
|
||||
</div>
|
||||
</a-alert>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:style="{ width: '600px', 'margin-top': '10px' }"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<a-form-item
|
||||
field="platform"
|
||||
label="所属平台"
|
||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="type"
|
||||
label="用途"
|
||||
:rules="[{ required: true, message: '请输入用途' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-select v-model="form.type" placeholder="请输入用途" :options="typeOPtions"> </a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="value"
|
||||
label="API KEY"
|
||||
:rules="[{ required: true, message: '请输入API KEY' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.value" placeholder="请输入API KEY" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="api_url"
|
||||
label="API URL"
|
||||
:rules="[{ required: true, message: '请输入API URL' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.api_url" placeholder="请输入API URL" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="use_proxy" label="使用代理">
|
||||
<a-switch v-model="form.use_proxy" />
|
||||
<a-tooltip
|
||||
content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
|
||||
position="right"
|
||||
>
|
||||
<icon-info-circle-fill />
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item field="enable" label="启用状态">
|
||||
<a-switch v-model="form.enable" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
|
||||
const typeOPtions = [
|
||||
{
|
||||
label: "聊天",
|
||||
value: "chart",
|
||||
},
|
||||
{
|
||||
label: "绘图",
|
||||
value: "img",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
.warning {
|
||||
color: #e6a23c;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
@@ -16,8 +16,9 @@ export const save = (data?: Record<string, unknown>) => {
|
||||
};
|
||||
export const deleting = (id: string | number) => {
|
||||
return http({
|
||||
url: `/api/admin/apikey/remove?id=${id}`,
|
||||
method: "get",
|
||||
url: `/api/admin/apikey/remove`,
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
};
|
||||
export const setStatus = (data) => {
|
||||
@@ -5,7 +5,7 @@ import ChatModelForm from "./ChatModelForm.vue";
|
||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
// table 配置
|
||||
const columns = [
|
||||
{
|
||||
@@ -44,6 +44,8 @@ const columns = [
|
||||
{
|
||||
title: "操作",
|
||||
slotName: "action",
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="platform"
|
||||
label="所属平台"
|
||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="value"
|
||||
label="模型值"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.value" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="weight"
|
||||
label="对话权重"
|
||||
:rules="[{ required: true, message: '请输入对话权重' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.weight" placeholder="请输入对话权重" />
|
||||
<a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
|
||||
<icon-info-circle-fill />
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="open" label="开放状态代理">
|
||||
<a-switch v-model="form.open" />
|
||||
</a-form-item>
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="form.enabled" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
|
||||
const typeOPtions = [
|
||||
{
|
||||
label: "聊天",
|
||||
value: "chart",
|
||||
},
|
||||
{
|
||||
label: "绘图",
|
||||
value: "img",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="platform"
|
||||
label="所属平台"
|
||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="名称"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="value"
|
||||
label="模型值"
|
||||
:rules="[{ required: true, message: '请输入名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.value" placeholder="请输入名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="weight"
|
||||
label="对话权重"
|
||||
:rules="[{ required: true, message: '请输入对话权重' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.weight" placeholder="请输入对话权重" />
|
||||
<a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
|
||||
<icon-info-circle-fill />
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="open" label="开放状态代理">
|
||||
<a-switch v-model="form.open" />
|
||||
</a-form-item>
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="form.enabled" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
|
||||
const typeOPtions = [
|
||||
{
|
||||
label: "聊天",
|
||||
value: "chart",
|
||||
},
|
||||
{
|
||||
label: "绘图",
|
||||
value: "img",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,8 +16,9 @@ export const save = (data?: Record<string, unknown>) => {
|
||||
};
|
||||
export const deleting = (id: string | number) => {
|
||||
return http({
|
||||
url: `/api/admin/model/remove?id=${id}`,
|
||||
method: "get",
|
||||
url: `/api/admin/model/remove`,
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
};
|
||||
export const setStatus = (data) => {
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, h } from "vue";
|
||||
import { Message, Modal } from "@arco-design/web-vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
||||
import app from "@/main";
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from "vue";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
import useRequest from "@/composables/useRequest";
|
||||
import { history } from "./api";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
||||
import { loginLog } from "@/http/login";
|
||||
11
new-ui/projects/admin/src/views/NoPermission.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<AResult status="403" subtitle="没有权限">
|
||||
<template #extra>
|
||||
<MButton preset="back" type="primary" />
|
||||
</template>
|
||||
</AResult>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { getList } from "./api";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
import { getList, remove } from "./api";
|
||||
|
||||
const columns: SearchTableColumns[] = [
|
||||
{
|
||||
@@ -71,6 +72,12 @@ const columns: SearchTableColumns[] = [
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
const handleRemove = async (id, reload) => {
|
||||
await remove({ id });
|
||||
Message.success("删除成功");
|
||||
await reload();
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<SearchTable :request="getList" :columns="columns">
|
||||
@@ -78,8 +85,15 @@ const columns: SearchTableColumns[] = [
|
||||
<a-tag v-if="!record.pay_time" color="blue">未支付</a-tag>
|
||||
<span v-else>{{ dateFormat(record.pay_time) }}</span>
|
||||
</template>
|
||||
<template #actions="{ record }">
|
||||
<a-link :key="record.id" status="danger">删除</a-link>
|
||||
<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>
|
||||
</SearchTable>
|
||||
</template>
|
||||
@@ -6,4 +6,12 @@ export const getList = (data?: Record<string, unknown>) => {
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export const remove = (data) => {
|
||||
return http({
|
||||
url: "/api/admin/order/remove",
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import ProductForm from "./ProductForm.vue";
|
||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
// table 配置
|
||||
const columns = [
|
||||
{
|
||||
@@ -40,10 +40,13 @@ const columns = [
|
||||
title: "启用状态",
|
||||
dataIndex: "enabled",
|
||||
slotName: "status",
|
||||
align: "center",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: "更新时间",
|
||||
dataIndex: "updated_at",
|
||||
width: 180,
|
||||
render: ({ record }) => {
|
||||
return dateFormat(record.updated_at);
|
||||
},
|
||||
@@ -51,6 +54,8 @@ const columns = [
|
||||
{
|
||||
title: "操作",
|
||||
slotName: "action",
|
||||
width: 120,
|
||||
fixed: "right",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="产品名称"
|
||||
:rules="[{ required: true, message: '请输入产品名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入产品名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="price"
|
||||
label="产品价格"
|
||||
:rules="[{ required: true, message: '请输入产品价格' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.price" placeholder="请输入产品价格" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="discount"
|
||||
label="优惠金额"
|
||||
:rules="[{ required: true, message: '请输入优惠金额' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.discount" placeholder="请输入优惠金额" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="days"
|
||||
label="有效期(天)"
|
||||
:rules="[{ required: true, message: '请输入有效期(天)' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.days" placeholder="请输入有效期(天)" />
|
||||
</a-form-item>
|
||||
<a-form-item field="calls" label="对话次数" :validate-trigger="['change', 'input']" showable>
|
||||
<a-input-number v-model="form.calls" placeholder="请输入对话次数" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="img_calls"
|
||||
label="绘图次数"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
|
||||
</a-form-item>
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="form.enabled" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="产品名称"
|
||||
:rules="[{ required: true, message: '请输入产品名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入产品名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="price"
|
||||
label="产品价格"
|
||||
:rules="[{ required: true, message: '请输入产品价格' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.price" placeholder="请输入产品价格" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="discount"
|
||||
label="优惠金额"
|
||||
:rules="[{ required: true, message: '请输入优惠金额' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.discount" placeholder="请输入优惠金额" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="days"
|
||||
label="有效期(天)"
|
||||
:rules="[{ required: true, message: '请输入有效期(天)' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.days" placeholder="请输入有效期(天)" />
|
||||
</a-form-item>
|
||||
<a-form-item field="calls" label="对话次数" :validate-trigger="['change', 'input']" showable>
|
||||
<a-input-number v-model="form.calls" placeholder="请输入对话次数" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="img_calls"
|
||||
label="绘图次数"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
|
||||
</a-form-item>
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="form.enabled" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { Message, type TableColumnData } from "@arco-design/web-vue";
|
||||
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||
import { getList, remove } from "./api";
|
||||
|
||||
@@ -8,10 +8,10 @@ export const getList = (params?: Record<string, unknown>) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const remove = (params?: Record<string, unknown>) => {
|
||||
export const remove = (data?: Record<string, unknown>) => {
|
||||
return http({
|
||||
url: "/api/admin/reward/remove",
|
||||
method: "get",
|
||||
params
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -1,116 +1,116 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="角色名称"
|
||||
:rules="[{ required: true, message: '请输入角色名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入角色名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="key"
|
||||
label="角色标志"
|
||||
:rules="[{ required: true, message: '请输入角色标志' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.key" placeholder="请输入角色标志" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="icon"
|
||||
label="角色图标"
|
||||
:rules="[{ required: true, message: '请输入角色图标' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.icon" placeholder="请输入角色图标" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="hello_msg"
|
||||
label="打招呼信息"
|
||||
:rules="[{ required: true, message: '请输入打招呼信息' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.hello_msg" placeholder="请输入打招呼信息" />
|
||||
</a-form-item>
|
||||
<a-form-item field="username" label="上下文信息" :validate-trigger="['change', 'input']">
|
||||
<a-table :data="form.context || []" :pagination="false">
|
||||
<template #columns>
|
||||
<a-table-column title="对话角色">
|
||||
<template #cell="{ record }">
|
||||
<a-input v-model="record.role" />
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column width="350">
|
||||
<template #title>
|
||||
<div class="content-title">
|
||||
<span>对话内容</span>
|
||||
<a-button @click="addContext" type="primary">增加一行</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell="{ record }">
|
||||
<div class="content-cell">
|
||||
<a-input v-model="record.content" /><icon-minus-circle
|
||||
@click="removeContext(record)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
<a-form-item field="enable" label="启用状态">
|
||||
<a-switch v-model="form.enable" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
const addContext = () => {
|
||||
form.value.context = form.value.context || [];
|
||||
form.value.context.push({
|
||||
role: "",
|
||||
content: "",
|
||||
});
|
||||
};
|
||||
|
||||
const removeContext = (record) => {
|
||||
const index = form.value.context.findIndex((item) => {
|
||||
return item === record;
|
||||
});
|
||||
if (index > -1) {
|
||||
form.value.context.splice(index, 1);
|
||||
}
|
||||
};
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<a-form ref="formRef" :model="form" :style="{ width: '600px' }" @submit="handleSubmit">
|
||||
<a-form-item
|
||||
field="name"
|
||||
label="角色名称"
|
||||
:rules="[{ required: true, message: '请输入角色名称' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.name" placeholder="请输入角色名称" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="key"
|
||||
label="角色标志"
|
||||
:rules="[{ required: true, message: '请输入角色标志' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
showable
|
||||
>
|
||||
<a-input v-model="form.key" placeholder="请输入角色标志" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="icon"
|
||||
label="角色图标"
|
||||
:rules="[{ required: true, message: '请输入角色图标' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.icon" placeholder="请输入角色图标" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="hello_msg"
|
||||
label="打招呼信息"
|
||||
:rules="[{ required: true, message: '请输入打招呼信息' }]"
|
||||
:validate-trigger="['change', 'input']"
|
||||
>
|
||||
<a-input v-model="form.hello_msg" placeholder="请输入打招呼信息" />
|
||||
</a-form-item>
|
||||
<a-form-item field="username" label="上下文信息" :validate-trigger="['change', 'input']">
|
||||
<a-table :data="form.context || []" :pagination="false">
|
||||
<template #columns>
|
||||
<a-table-column title="对话角色">
|
||||
<template #cell="{ record }">
|
||||
<a-input v-model="record.role" />
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column width="350">
|
||||
<template #title>
|
||||
<div class="content-title">
|
||||
<span>对话内容</span>
|
||||
<a-button @click="addContext" type="primary">增加一行</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell="{ record }">
|
||||
<div class="content-cell">
|
||||
<a-input v-model="record.content" /><icon-minus-circle
|
||||
@click="removeContext(record)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
<a-form-item field="enable" label="启用状态">
|
||||
<a-switch v-model="form.enable" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineExpose, defineProps } from "vue";
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref({});
|
||||
if (props.data?.id) {
|
||||
form.value = Object.assign({}, props.data);
|
||||
}
|
||||
|
||||
const addContext = () => {
|
||||
form.value.context = form.value.context || [];
|
||||
form.value.context.push({
|
||||
role: "",
|
||||
content: "",
|
||||
});
|
||||
};
|
||||
|
||||
const removeContext = (record) => {
|
||||
const index = form.value.context.findIndex((item) => {
|
||||
return item === record;
|
||||
});
|
||||
if (index > -1) {
|
||||
form.value.context.splice(index, 1);
|
||||
}
|
||||
};
|
||||
defineExpose({
|
||||
formRef,
|
||||
form,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.content-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
}
|
||||
.content-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
svg {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||