mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
Merge remote-tracking branch 'origin/ui' into ui
This commit is contained in:
commit
dca25dbb78
@ -65,6 +65,9 @@ importers:
|
|||||||
'@gpt-vue/packages':
|
'@gpt-vue/packages':
|
||||||
specifier: workspace:^1.0.0
|
specifier: workspace:^1.0.0
|
||||||
version: link:../../packages
|
version: link:../../packages
|
||||||
|
echarts:
|
||||||
|
specifier: ^5.5.0
|
||||||
|
version: 5.5.0
|
||||||
md-editor-v3:
|
md-editor-v3:
|
||||||
specifier: ^2.2.1
|
specifier: ^2.2.1
|
||||||
version: 2.11.3(vue@3.4.21)
|
version: 2.11.3(vue@3.4.21)
|
||||||
@ -93,6 +96,9 @@ importers:
|
|||||||
'@vitejs/plugin-vue-jsx':
|
'@vitejs/plugin-vue-jsx':
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0(vite@5.1.5)(vue@3.4.21)
|
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':
|
'@vue/eslint-config-typescript':
|
||||||
specifier: ^12.0.0
|
specifier: ^12.0.0
|
||||||
version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
|
version: 12.0.0(eslint-plugin-vue@9.22.0)(eslint@8.57.0)(typescript@5.3.3)
|
||||||
@ -1283,6 +1289,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
|
resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
|
||||||
dev: false
|
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):
|
/@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==}
|
resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==}
|
||||||
engines: {node: ^14.17.0 || >=16.0.0}
|
engines: {node: ^14.17.0 || >=16.0.0}
|
||||||
@ -1637,6 +1655,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/echarts@5.5.0:
|
||||||
|
resolution: {integrity: sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
zrender: 5.5.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/electron-to-chromium@1.4.692:
|
/electron-to-chromium@1.4.692:
|
||||||
resolution: {integrity: sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==}
|
resolution: {integrity: sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -1708,6 +1733,15 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
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-config-prettier@9.1.0(eslint@8.57.0):
|
/eslint-config-prettier@9.1.0(eslint@8.57.0):
|
||||||
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
|
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -1717,6 +1751,23 @@ packages:
|
|||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
dev: true
|
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-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5):
|
/eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5):
|
||||||
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
|
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
@ -2797,6 +2848,10 @@ packages:
|
|||||||
typescript: 5.3.3
|
typescript: 5.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tslib@2.3.0:
|
||||||
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tslib@2.6.2:
|
/tslib@2.6.2:
|
||||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3008,3 +3063,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/zrender@5.5.0:
|
||||||
|
resolution: {integrity: sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.0
|
||||||
|
dev: false
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-vue": "^2.54.6",
|
"@arco-design/web-vue": "^2.54.6",
|
||||||
"@gpt-vue/packages": "workspace:^1.0.0",
|
"@gpt-vue/packages": "workspace:^1.0.0",
|
||||||
|
"echarts": "^5.5.0",
|
||||||
"md-editor-v3": "^2.2.1",
|
"md-editor-v3": "^2.2.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"vue": "^3.4.15",
|
"vue": "^3.4.15",
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IconDown, IconExport } from "@arco-design/web-vue/es/icon";
|
import { IconDown, IconExport } from "@arco-design/web-vue/es/icon";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import useState from "@/composables/useState";
|
||||||
import Logo from "/images/logo.png";
|
import Logo from "/images/logo.png";
|
||||||
|
import avatar from "/images/user-info.jpg";
|
||||||
|
import donateImg from "/images/wechat-pay.png";
|
||||||
|
|
||||||
import SystemMenu from "./SystemMenu.vue";
|
import SystemMenu from "./SystemMenu.vue";
|
||||||
import PageWrapper from "./PageWrapper.vue";
|
import PageWrapper from "./PageWrapper.vue";
|
||||||
|
|
||||||
const logoWidth = "200px";
|
const logoWidth = "200px";
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ALayout class="custom-layout">
|
<ALayout class="custom-layout">
|
||||||
@ -19,18 +23,33 @@ const authStore = useAuthStore();
|
|||||||
<div class="action">
|
<div class="action">
|
||||||
<ADropdown>
|
<ADropdown>
|
||||||
<ASpace align="center" :size="4">
|
<ASpace align="center" :size="4">
|
||||||
<span></span>
|
<a-avatar class="user-avatar" :size="30">
|
||||||
|
<img :src="avatar" />
|
||||||
|
</a-avatar>
|
||||||
<IconDown />
|
<IconDown />
|
||||||
</ASpace>
|
</ASpace>
|
||||||
<template #content>
|
<template #content>
|
||||||
<ADoption value="changeOwnPwd">更改密码</ADoption>
|
<a
|
||||||
|
class="dropdown-link"
|
||||||
|
href="https://github.com/yangjian102621/chatgpt-plus"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<ADoption value="1">
|
||||||
|
<template #icon>
|
||||||
|
<icon-github />
|
||||||
|
</template>
|
||||||
|
<span>ChatPlus-AI 创作系统</span>
|
||||||
|
</ADoption>
|
||||||
|
</a>
|
||||||
|
<ADoption value="2" @click="setVisible(true)">
|
||||||
|
<template #icon>
|
||||||
|
<icon-wechatpay />
|
||||||
|
</template>
|
||||||
|
<span>打赏作者</span>
|
||||||
|
</ADoption>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<APopconfirm
|
<APopconfirm content="确认退出?" position="br" @ok="authStore.logout">
|
||||||
content="确认退出?"
|
|
||||||
position="br"
|
|
||||||
@ok="authStore.logout"
|
|
||||||
>
|
|
||||||
<ASpace align="center" class="logout-area">
|
<ASpace align="center" class="logout-area">
|
||||||
<IconExport size="16" />
|
<IconExport size="16" />
|
||||||
<span>退出</span>
|
<span>退出</span>
|
||||||
@ -49,6 +68,20 @@ const authStore = useAuthStore();
|
|||||||
</ALayoutContent>
|
</ALayoutContent>
|
||||||
</ALayout>
|
</ALayout>
|
||||||
</ALayout>
|
</ALayout>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
class="donate-dialog"
|
||||||
|
width="400px"
|
||||||
|
title="请作者喝杯咖啡"
|
||||||
|
:footer="false"
|
||||||
|
>
|
||||||
|
<a-alert :closable="false" :show-icon="false">
|
||||||
|
如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~
|
||||||
|
</a-alert>
|
||||||
|
<p>
|
||||||
|
<a-image :src="donateImg" />
|
||||||
|
</p>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.custom-layout {
|
.custom-layout {
|
||||||
@ -81,6 +114,14 @@ const authStore = useAuthStore();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dropdown-link {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.donate-dialog {
|
||||||
|
p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
.logout-area {
|
.logout-area {
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -80,8 +80,8 @@ const optionsEvent = {
|
|||||||
</slot>
|
</slot>
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
</AGridItem>
|
</AGridItem>
|
||||||
<AGridItem suffix>
|
<AGridItem>
|
||||||
<ASpace class="flex-end">
|
<ASpace>
|
||||||
<slot name="search-options" :option="optionsEvent">
|
<slot name="search-options" :option="optionsEvent">
|
||||||
<AButton type="primary" html-type="submit" :size="size" :loading="submitting">
|
<AButton type="primary" html-type="submit" :size="size" :loading="submitting">
|
||||||
<icon-search />
|
<icon-search />
|
||||||
@ -92,6 +92,10 @@ const optionsEvent = {
|
|||||||
<span>重置</span>
|
<span>重置</span>
|
||||||
</AButton>
|
</AButton>
|
||||||
</slot>
|
</slot>
|
||||||
|
</ASpace>
|
||||||
|
</AGridItem>
|
||||||
|
<AGridItem suffix>
|
||||||
|
<ASpace class="flex-end">
|
||||||
<slot name="search-extra" />
|
<slot name="search-extra" />
|
||||||
</ASpace>
|
</ASpace>
|
||||||
</AGridItem>
|
</AGridItem>
|
||||||
@ -100,7 +104,7 @@ const optionsEvent = {
|
|||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.search-form-conteiner {
|
.search-form-conteiner {
|
||||||
padding: 16px 0;
|
padding: 8px 0px 0px;
|
||||||
}
|
}
|
||||||
.flex-end {
|
.flex-end {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -19,11 +19,13 @@ const requestParams = computed(() => ({
|
|||||||
const [tableConfig, getList] = useAsyncTable(props.request, requestParams);
|
const [tableConfig, getList] = useAsyncTable(props.request, requestParams);
|
||||||
|
|
||||||
const _columns = computed(() => {
|
const _columns = computed(() => {
|
||||||
return props.columns.map((item) => ({
|
return props.columns
|
||||||
ellipsis: true,
|
.filter((item) => !item.hideInTable)
|
||||||
tooltip: true,
|
.map((item) => ({
|
||||||
...item,
|
ellipsis: true,
|
||||||
}));
|
tooltip: true,
|
||||||
|
...item,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSearch = async (tips?: boolean) => {
|
const handleSearch = async (tips?: boolean) => {
|
||||||
@ -46,7 +48,7 @@ onActivated(handleSearch);
|
|||||||
<FormSection
|
<FormSection
|
||||||
v-model="formData"
|
v-model="formData"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:submitting="(tableConfig.loading as boolean)"
|
:submitting="tableConfig.loading as boolean"
|
||||||
@request="handleSearch"
|
@request="handleSearch"
|
||||||
>
|
>
|
||||||
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
||||||
@ -60,7 +62,7 @@ onActivated(handleSearch);
|
|||||||
...tableConfig,
|
...tableConfig,
|
||||||
...props,
|
...props,
|
||||||
scroll: useTableScroll(_columns, tableContainerRef as HTMLElement),
|
scroll: useTableScroll(_columns, tableContainerRef as HTMLElement),
|
||||||
columns: _columns
|
columns: _columns,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
||||||
|
@ -33,12 +33,12 @@ const handleSearch = async (tips?: boolean) => {
|
|||||||
onActivated(handleSearch);
|
onActivated(handleSearch);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="simple-header">
|
|
||||||
<a-space>
|
|
||||||
<slot name="header" v-bind="{ reload: handleSearch }" />
|
|
||||||
</a-space>
|
|
||||||
</div>
|
|
||||||
<div class="simple-table">
|
<div class="simple-table">
|
||||||
|
<div class="simple-header">
|
||||||
|
<a-space class="flex-end">
|
||||||
|
<slot name="header" v-bind="{ reload: handleSearch }" />
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
<div ref="tableContainerRef" class="simple-table-container">
|
<div ref="tableContainerRef" class="simple-table-container">
|
||||||
<ATable
|
<ATable
|
||||||
v-bind="{
|
v-bind="{
|
||||||
@ -46,7 +46,7 @@ onActivated(handleSearch);
|
|||||||
...tableConfig,
|
...tableConfig,
|
||||||
...props,
|
...props,
|
||||||
scroll: useTableScroll(_columns || [], tableContainerRef as HTMLElement),
|
scroll: useTableScroll(_columns || [], tableContainerRef as HTMLElement),
|
||||||
columns: _columns
|
columns: _columns,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
<template v-for="slot in Object.keys($slots)" #[slot]="config">
|
||||||
@ -71,6 +71,10 @@ onActivated(handleSearch);
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.simple-header {
|
.simple-header {
|
||||||
padding: 16px 0;
|
padding: 8px 0px 16px;
|
||||||
|
}
|
||||||
|
.flex-end {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
10
gpt-vue/projects/vue-admin/src/composables/useState.ts
Normal file
10
gpt-vue/projects/vue-admin/src/composables/useState.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ref, type Ref } from "vue";
|
||||||
|
function useState<T>(defaultValue?: T): [Ref<T>, (newValue: T) => void] {
|
||||||
|
const state = ref<T>(defaultValue) as Ref<T>;
|
||||||
|
const setState = (newValue: T) => {
|
||||||
|
state.value = newValue;
|
||||||
|
};
|
||||||
|
return [state, setState];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useState;
|
@ -12,7 +12,7 @@ function useSubmit<T extends Record<string, any> = Record<string, any>, R = any>
|
|||||||
const hasError = await formRef.value?.validate();
|
const hasError = await formRef.value?.validate();
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
const { data, message } = await api({ ...formData ?? {}, ...unref(params) });
|
const { data, message } = await api({ ...formData ?? {}, ...unref(params) });
|
||||||
Message.success(message);
|
message && Message.success(message);
|
||||||
return Promise.resolve({ formData, data });
|
return Promise.resolve({ formData, data });
|
||||||
}
|
}
|
||||||
return Promise.reject(false);
|
return Promise.reject(false);
|
||||||
|
@ -3,7 +3,7 @@ import { Notification } from "@arco-design/web-vue";
|
|||||||
import createInstance from "@gpt-vue/packages/request"
|
import createInstance from "@gpt-vue/packages/request"
|
||||||
import type { BaseResponse } from "@gpt-vue/packages/type";
|
import type { BaseResponse } from "@gpt-vue/packages/type";
|
||||||
|
|
||||||
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/upload";
|
export const uploadUrl = import.meta.env.VITE_PROXY_BASE_URL + "/api/admin/upload";
|
||||||
|
|
||||||
export const instance = createInstance()
|
export const instance = createInstance()
|
||||||
|
|
||||||
|
@ -33,3 +33,10 @@ export const loginLog = (params?: Record<string, unknown>) => {
|
|||||||
params
|
params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const captcha = () => {
|
||||||
|
return http({
|
||||||
|
url: "/api/admin/login/captcha",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -19,6 +19,7 @@ const columns = [
|
|||||||
{
|
{
|
||||||
title: "key",
|
title: "key",
|
||||||
dataIndex: "value",
|
dataIndex: "value",
|
||||||
|
slotName: "value",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "用途",
|
title: "用途",
|
||||||
@ -96,7 +97,14 @@ const handleStatusChange = ({ filed, value, record, reload }) => {
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
<template #header="{ reload }">
|
<template #header="{ reload }">
|
||||||
<a-button @click="popup({ reload })" size="small"><icon-plus />新增</a-button>
|
<a-button @click="popup({ reload })" size="small" type="primary"
|
||||||
|
><template #icon> <icon-plus /> </template>新增
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template #value="{ record, column }">
|
||||||
|
<a-typography-text copyable ellipsis style="margin: 0">
|
||||||
|
{{ record[column.dataIndex] }}
|
||||||
|
</a-typography-text>
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record, reload }">
|
<template #status="{ record, reload }">
|
||||||
<a-switch
|
<a-switch
|
||||||
|
@ -96,7 +96,9 @@ const handleStatusChange = ({ filed, value, record, reload }) => {
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
<template #header="{ reload }">
|
<template #header="{ reload }">
|
||||||
<a-button @click="popup({ reload })" size="small"><icon-plus />新增</a-button>
|
<a-button @click="popup({ reload })" size="small" type="primary"
|
||||||
|
><template #icon> <icon-plus /> </template>新增</a-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record, reload }">
|
<template #status="{ record, reload }">
|
||||||
<a-switch
|
<a-switch
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, h } from "vue";
|
import { ref, h } from "vue";
|
||||||
import { Message, Modal } from "@arco-design/web-vue";
|
import { Message, Modal } from "@arco-design/web-vue";
|
||||||
|
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
||||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
||||||
import app from "@/main";
|
import app from "@/main";
|
||||||
@ -51,6 +52,7 @@ const columns: SearchTableColumns[] = [
|
|||||||
search: {
|
search: {
|
||||||
valueType: "range",
|
valueType: "range",
|
||||||
},
|
},
|
||||||
|
render: ({ record }) => dateFormat(record.created_at),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import http from "@/http/config";
|
import http from "@/http/config";
|
||||||
import { ref } from "vue";
|
import { ref, nextTick } from "vue";
|
||||||
|
import * as echarts from "echarts/core";
|
||||||
const dataSet = {
|
import { GridComponent, TitleComponent } from "echarts/components";
|
||||||
users: "今日新增用户",
|
import { LineChart } from "echarts/charts";
|
||||||
chats: "今日新增对话",
|
import { UniversalTransition } from "echarts/features";
|
||||||
tokens: "今日消耗 Tokens",
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
income: "今日入账",
|
|
||||||
};
|
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
users: "icon-user",
|
users: "icon-user",
|
||||||
@ -15,24 +13,81 @@ const icons = {
|
|||||||
tokens: "icon-computer",
|
tokens: "icon-computer",
|
||||||
income: "icon-wechatpay",
|
income: "icon-wechatpay",
|
||||||
};
|
};
|
||||||
|
const dataSet = {
|
||||||
|
users: "今日新增用户",
|
||||||
|
chats: "今日新增对话",
|
||||||
|
tokens: "今日消耗 Tokens",
|
||||||
|
income: "今日入账",
|
||||||
|
};
|
||||||
|
|
||||||
const data = ref<Record<string, number>>({});
|
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
http({
|
http({
|
||||||
url: "api/admin/dashboard/stats",
|
url: "api/admin/dashboard/stats",
|
||||||
method: "get",
|
method: "get",
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
data.value = res.data;
|
data.value = res.data;
|
||||||
|
handeChartData(res.data.chart);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getData();
|
getData();
|
||||||
|
// 图表
|
||||||
|
const chartTitle = {
|
||||||
|
historyMessage: "对话",
|
||||||
|
orders: "订单",
|
||||||
|
users: "用户数",
|
||||||
|
};
|
||||||
|
echarts.use([GridComponent, LineChart, CanvasRenderer, UniversalTransition, TitleComponent]);
|
||||||
|
const chartDomRefs = [];
|
||||||
|
const chartData = ref({});
|
||||||
|
const data = ref<Record<string, number>>({});
|
||||||
|
const handeChartData = (data) => {
|
||||||
|
const _chartData = {};
|
||||||
|
for (let key in data) {
|
||||||
|
const type = data[key];
|
||||||
|
_chartData[key] = {
|
||||||
|
series: [],
|
||||||
|
xAxis: [],
|
||||||
|
};
|
||||||
|
for (let date in type) {
|
||||||
|
_chartData[key].series.push(type[date]);
|
||||||
|
_chartData[key].xAxis.push(date);
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
const myChart = echarts.init(chartDomRefs.pop());
|
||||||
|
myChart.setOption(createOption(_chartData[key], key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
chartData.value = _chartData;
|
||||||
|
};
|
||||||
|
const createOption = (data, key) => {
|
||||||
|
const { xAxis, series } = data;
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
left: "center",
|
||||||
|
text: chartTitle[key],
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: "category",
|
||||||
|
data: xAxis,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: "value",
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: series,
|
||||||
|
type: "line",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<a-grid :cols="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }" :colGap="12" :rowGap="16" class="grid">
|
<a-grid :cols="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }" :colGap="12" :rowGap="16" class="grid">
|
||||||
<a-grid-item v-for="(value, key) in dataSet" :key="key">
|
<a-grid-item v-for="(value, key) in dataSet" :key="key">
|
||||||
<div class="data-card">
|
<div class="data-card">
|
||||||
<span :class="key" class="icon"><icon-user /></span>
|
<span :class="key" class="icon"><component :is="icons[key]" /> </span>
|
||||||
<span class="count"
|
<span class="count"
|
||||||
><a-statistic :extra="value" :value="data[key]" :precision="0"
|
><a-statistic :extra="value" :value="data[key]" :precision="0"
|
||||||
/></span>
|
/></span>
|
||||||
@ -40,6 +95,27 @@ getData();
|
|||||||
</a-grid-item>
|
</a-grid-item>
|
||||||
</a-grid>
|
</a-grid>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="chart">
|
||||||
|
<a-grid
|
||||||
|
:cols="{ xs: 1, sm: 1, md: 1, lg: 2, xl: 2, xxl: 3 }"
|
||||||
|
:colGap="12"
|
||||||
|
:rowGap="16"
|
||||||
|
class="grid"
|
||||||
|
>
|
||||||
|
<a-grid-item v-for="(value, key, index) in chartData" :key="key">
|
||||||
|
<div
|
||||||
|
:ref="
|
||||||
|
(el) => {
|
||||||
|
chartDomRefs[index] = el;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
class="chartDom"
|
||||||
|
>
|
||||||
|
{{ key }}
|
||||||
|
</div>
|
||||||
|
</a-grid-item>
|
||||||
|
</a-grid>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.dashboard {
|
.dashboard {
|
||||||
@ -80,7 +156,16 @@ getData();
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
background: #f3f3f3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.chart {
|
||||||
|
margin-top: 15px;
|
||||||
|
.chartDom {
|
||||||
|
width: 450px;
|
||||||
|
height: 500px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -57,7 +57,7 @@ const handleRemove = async (id, reload) => {
|
|||||||
<template>
|
<template>
|
||||||
<SimpleTable :request="getList" :columns="columns" :pagination="false">
|
<SimpleTable :request="getList" :columns="columns" :pagination="false">
|
||||||
<template #header="{ reload }">
|
<template #header="{ reload }">
|
||||||
<a-button @click="openFormModal(reload, {})">
|
<a-button type="primary" @click="openFormModal(reload, {})">
|
||||||
<template #icon> <icon-plus /> </template>
|
<template #icon> <icon-plus /> </template>
|
||||||
新增
|
新增
|
||||||
</a-button>
|
</a-button>
|
||||||
|
@ -1,24 +1,82 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive } from "vue";
|
import { onMounted, reactive } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { captcha } from "@/http/login";
|
||||||
|
import useState from "@/composables/useState";
|
||||||
import useRequest from "@/composables/useRequest";
|
import useRequest from "@/composables/useRequest";
|
||||||
|
|
||||||
const route = useRoute();
|
// 表单
|
||||||
const authStore = useAuthStore();
|
function useFormData() {
|
||||||
|
const formData = reactive({
|
||||||
const [loginRequest, _, loading] = useRequest(authStore.login);
|
username: "",
|
||||||
const formData = reactive({
|
password: "",
|
||||||
username: "",
|
captcha: "",
|
||||||
password: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleSubmit({ errors, values }: any) {
|
|
||||||
if (errors) return;
|
|
||||||
await loginRequest({
|
|
||||||
...values,
|
|
||||||
...route.query,
|
|
||||||
});
|
});
|
||||||
|
const rules = {
|
||||||
|
username: [{ required: true, message: "请输入您的账号" }],
|
||||||
|
password: [{ required: true, message: "请输入您的密码" }],
|
||||||
|
captcha: [{ required: true, message: "请输入验证码" }],
|
||||||
|
};
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const [loginRequest, _, submitting] = useRequest(authStore.login);
|
||||||
|
return { formData, loginRequest, submitting, rules };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
function useCaptcha() {
|
||||||
|
const captchaImage = reactive({
|
||||||
|
pic_path: "",
|
||||||
|
captcha_id: "",
|
||||||
|
});
|
||||||
|
const getCaptchaImage = async () => {
|
||||||
|
const { data } = await captcha();
|
||||||
|
Object.assign(captchaImage, data);
|
||||||
|
};
|
||||||
|
onMounted(getCaptchaImage);
|
||||||
|
return { captchaImage, getCaptchaImage };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记住密码
|
||||||
|
function useRemeberPWD(formData) {
|
||||||
|
const storageKey = "r-f";
|
||||||
|
const [isRemember, setIsRemember] = useState(false);
|
||||||
|
const onIsRememberChange = (v) => {
|
||||||
|
if (v) {
|
||||||
|
const value = {
|
||||||
|
username: formData.username,
|
||||||
|
password: formData.password,
|
||||||
|
};
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(storageKey);
|
||||||
|
}
|
||||||
|
setIsRemember(v);
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
const getter = localStorage.getItem(storageKey);
|
||||||
|
if (getter) {
|
||||||
|
setIsRemember(true);
|
||||||
|
Object.assign(formData, JSON.parse(getter));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { isRemember, onIsRememberChange };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { formData, loginRequest, submitting, rules } = useFormData();
|
||||||
|
const { captchaImage, getCaptchaImage } = useCaptcha();
|
||||||
|
const { isRemember, onIsRememberChange } = useRemeberPWD(formData);
|
||||||
|
|
||||||
|
// 表单提交
|
||||||
|
async function handleSubmit({ errors }: any) {
|
||||||
|
if (errors) return;
|
||||||
|
try {
|
||||||
|
await loginRequest({
|
||||||
|
...formData,
|
||||||
|
captcha_id: captchaImage.captcha_id,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
getCaptchaImage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
@ -33,44 +91,54 @@ async function handleSubmit({ errors, values }: any) {
|
|||||||
<div class="form-box">
|
<div class="form-box">
|
||||||
<div class="title">ChatGPT Plus Admin</div>
|
<div class="title">ChatGPT Plus Admin</div>
|
||||||
<a-form
|
<a-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
class="form"
|
class="form"
|
||||||
size="medium"
|
size="medium"
|
||||||
auto-label-width
|
auto-label-width
|
||||||
@submit="handleSubmit"
|
:label-col-props="{ span: 0 }"
|
||||||
|
:wrapper-col-props="{ span: 24 }"
|
||||||
|
:rules="rules"
|
||||||
|
@submit="handleSubmit"
|
||||||
>
|
>
|
||||||
<a-space direction="vertical" style="width: 100%">
|
<a-space direction="vertical" style="width: 100%">
|
||||||
<a-form-item
|
<a-form-item field="username" label="账号">
|
||||||
field="username"
|
<a-input v-model="formData.username" placeholder="请输入您的账号" class="input" />
|
||||||
label="账号"
|
|
||||||
hide-label
|
|
||||||
hide-asterisk
|
|
||||||
:rules="[{ required: true, message: '请输入您的账号' }]"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model="formData.username"
|
|
||||||
placeholder="请输入您的账号"
|
|
||||||
class="input"
|
|
||||||
></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item field="password" label="密码">
|
||||||
field="password"
|
|
||||||
label="密码"
|
|
||||||
hide-label
|
|
||||||
hide-asterisk
|
|
||||||
:rules="[{ required: true, message: '请输入您的密码' }]"
|
|
||||||
>
|
|
||||||
<a-input-password
|
<a-input-password
|
||||||
v-model="formData.password"
|
v-model="formData.password"
|
||||||
placeholder="请输入您的密码"
|
placeholder="请输入您的密码"
|
||||||
class="input"
|
class="input"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="captcha" label="验证码">
|
||||||
|
<a-input v-model="formData.captcha" placeholder="请输入验证码" class="input">
|
||||||
|
<template #append>
|
||||||
|
<img
|
||||||
|
class="captcha-image"
|
||||||
|
:src="captchaImage.pic_path"
|
||||||
|
alt="验证码"
|
||||||
|
title="点击刷新验证码"
|
||||||
|
@click="getCaptchaImage()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item hide-label>
|
||||||
|
<a-checkbox :model-value="isRemember" @change="onIsRememberChange"
|
||||||
|
>记住密码</a-checkbox
|
||||||
>
|
>
|
||||||
</a-input-password>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-form-item hide-label>
|
<a-form-item hide-label>
|
||||||
<a-button :disabled="loading" html-type="submit" long type="primary" class="sign-in-btn">
|
<a-button
|
||||||
|
:loading="submitting"
|
||||||
|
html-type="submit"
|
||||||
|
long
|
||||||
|
type="primary"
|
||||||
|
class="sign-in-btn"
|
||||||
|
>
|
||||||
登录
|
登录
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
import SearchTable from "@/components/SearchTable/SearchTable.vue";
|
||||||
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
import type { SearchTableColumns } from "@/components/SearchTable/type";
|
||||||
|
import { dateFormat } from "@gpt-vue/packages/utils";
|
||||||
import { getList } from "./api";
|
import { getList } from "./api";
|
||||||
|
|
||||||
const columns: SearchTableColumns[] = [
|
const columns: SearchTableColumns[] = [
|
||||||
@ -10,6 +11,7 @@ const columns: SearchTableColumns[] = [
|
|||||||
search: {
|
search: {
|
||||||
valueType: "input",
|
valueType: "input",
|
||||||
},
|
},
|
||||||
|
width: 280,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
@ -30,6 +32,8 @@ const columns: SearchTableColumns[] = [
|
|||||||
{
|
{
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
title: "下单时间",
|
title: "下单时间",
|
||||||
|
render: ({ record }) => dateFormat(record.created_at),
|
||||||
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
@ -37,6 +41,7 @@ const columns: SearchTableColumns[] = [
|
|||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
search: {
|
search: {
|
||||||
valueType: "select",
|
valueType: "select",
|
||||||
|
defaultValue: -1,
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
options: [
|
options: [
|
||||||
{ label: "全部", value: -1 },
|
{ label: "全部", value: -1 },
|
||||||
@ -52,6 +57,8 @@ const columns: SearchTableColumns[] = [
|
|||||||
search: {
|
search: {
|
||||||
valueType: "range",
|
valueType: "range",
|
||||||
},
|
},
|
||||||
|
slotName: "pay_time",
|
||||||
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "pay_way",
|
dataIndex: "pay_way",
|
||||||
@ -60,11 +67,17 @@ const columns: SearchTableColumns[] = [
|
|||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
slotName: "actions",
|
slotName: "actions",
|
||||||
|
fixed: "right",
|
||||||
|
width: 80,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SearchTable :request="getList" :columns="columns">
|
<SearchTable :request="getList" :columns="columns">
|
||||||
|
<template #pay_time="{ record }">
|
||||||
|
<a-tag v-if="!record.pay_time" color="blue">未支付</a-tag>
|
||||||
|
<span v-else>{{ dateFormat(record.pay_time) }}</span>
|
||||||
|
</template>
|
||||||
<template #actions="{ record }">
|
<template #actions="{ record }">
|
||||||
<a-link :key="record.id" status="danger">删除</a-link>
|
<a-link :key="record.id" status="danger">删除</a-link>
|
||||||
</template>
|
</template>
|
||||||
|
@ -102,7 +102,9 @@ const handleStatusChange = ({ value, record, reload }) => {
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
<template #header="{ reload }">
|
<template #header="{ reload }">
|
||||||
<a-button @click="popup({ reload })" size="small"><icon-plus />新增</a-button>
|
<a-button @click="popup({ reload })" size="small" type="primary"
|
||||||
|
><template #icon> <icon-plus /> </template>新增
|
||||||
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record, reload }">
|
<template #status="{ record, reload }">
|
||||||
<a-switch
|
<a-switch
|
||||||
|
@ -105,7 +105,9 @@ const handleStatusChange = ({ value, record, reload }) => {
|
|||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
<template #header="{ reload }">
|
<template #header="{ reload }">
|
||||||
<a-button @click="popup({ reload })" size="small"><icon-plus />新增</a-button>
|
<a-button @click="popup({ reload })" size="small" type="primary"
|
||||||
|
><template #icon> <icon-plus /> </template>新增</a-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record, reload }">
|
<template #status="{ record, reload }">
|
||||||
<a-switch
|
<a-switch
|
||||||
|
@ -148,7 +148,7 @@ onMounted(async () => {
|
|||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
|
<a-button type="primary" :loading="submitting" @click="handleSave">保存</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
@ -141,7 +141,7 @@ onMounted(reload);
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
|
<a-button type="primary" :loading="submitting" @click="handleSave">保存</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
@ -61,7 +61,7 @@ onMounted(reload);
|
|||||||
<md-editor v-model="formData.content" @on-upload-img="onUploadImg" />
|
<md-editor v-model="formData.content" @on-upload-img="onUploadImg" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" :loading="submitting" @click="handleSave">提交</a-button>
|
<a-button type="primary" :loading="submitting" @click="handleSave">保存</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { Message } from "@arco-design/web-vue";
|
||||||
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
|
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
|
||||||
import { uploadUrl } from "@/http/config";
|
import { uploadUrl } from "@/http/config";
|
||||||
|
|
||||||
@ -18,20 +19,21 @@ const uploadProps = computed<UploadInstance["$props"]>(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const handleChange = (_, file: FileItem) => {
|
const handleChange = (_, file: FileItem) => {
|
||||||
console.log(file.response);
|
if (file?.response) {
|
||||||
|
emits("update:modelValue", file?.response?.data?.url);
|
||||||
|
Message.success("上传成功");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a-space>
|
<a-upload v-bind="uploadProps" style="width: 100%" @change="handleChange">
|
||||||
<a-input-group>
|
<template #upload-button>
|
||||||
<a-input :model-value="modelValue" :placeholder="placeholder" readonly />
|
<a-input-group style="width: 100%">
|
||||||
<a-upload v-bind="uploadProps" @change="handleChange">
|
<a-input :model-value="modelValue" :placeholder="placeholder" readonly />
|
||||||
<template #upload-button>
|
<a-button type="primary" style="width: 100px">
|
||||||
<a-button type="primary">
|
<icon-cloud />
|
||||||
<icon-cloud />
|
</a-button>
|
||||||
</a-button>
|
</a-input-group>
|
||||||
</template>
|
</template>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
</a-input-group>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -80,9 +80,9 @@ const handleDelete = async ({ id }: { id: string }, reload) => {
|
|||||||
<a-link @click="password({ record, reload })">重置密码</a-link>
|
<a-link @click="password({ record, reload })">重置密码</a-link>
|
||||||
</template>
|
</template>
|
||||||
<template #search-extra="{ reload }">
|
<template #search-extra="{ reload }">
|
||||||
<a-button @click="editModal({ reload })" status="success" size="small"
|
<a-button @click="editModal({ reload })" size="small" type="primary">
|
||||||
><icon-plus />新增用户</a-button
|
<template #icon> <icon-plus /> </template>新增
|
||||||
>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</SearchTable>
|
</SearchTable>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user