上传代码

This commit is contained in:
2025-08-18 09:48:53 +08:00
commit a8ad83477f
61 changed files with 8934 additions and 0 deletions

24
CampusExpressDelivery/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7gB1IUC1Utw3ghZ3Do2VJaBC+vAJUapVPCm4UuBZBvjziMnU
feqhe1OPAkwUC1CDtuh5+xacknsheY6CrdfOV5qkMqCvg4opavcCWuj9d5wppO1U
oBjsbYNUslYzwq2F3X0/N1sFUOQM56V69fIY+Kedos6FHsZD/ErX7yhDBY4H4Se2
Q5NQjL12eWVys3Jn07ATTTAfVbsewJAxeMh58j51kVU0XhijemuibvtgiCSKMa4J
TUM2+tldxC9Xfb4Et3xFTWdAJnp945oqWAzoDa9YszNGYJrs3TFJKsCxuXe8wBBT
4iRLb+hcyVm8Wekax04HmJhl1NZhT+pg7mHnoQIDAQABAoIBAAQrkEHVdrvhgYw7
KSnBTbPFoqjPkgU7HCBYTYtPtKWicO90lF9isCLHXngMhdUjK3Sl7wW5EiQfqdDt
wCDMSUh4DGCZ98tQ7Vv7QSB/SLoq24CxGyCNQT7DvGUdzS1AAbqFWFlHpDCkN0zr
hYuXyuvX8YNV1aep8wzhkZYmXSLErztMiFUvUYscIqQLkuVO8c0ydiA1ALPhvtB2
Sv6PrEHeXMvi8ESTh/BMxrCHZZrDDodCMh0goY212UCAhBY/CG2z15s1GmgjdENt
Fh88Ic5ZUTLq5BRM3tDRlErb/6/9lQLPxpfExPKC4eOyKHMpGvvPrzQUEWaUSlu2
rzI6uQECgYEA/32Q7mYmvkIoAUmNPi7f8G65NKTVi6eUhkrbVKUksx78dqLg2Ay1
156YJwA+VFdwnw3OkMlJYIhXXF7wQ6MvFB21wXkEkMQj/J/l3f/LoeVrR+Md2lyF
M3P04zWkQoCRdTz9L3U08szlk7bj4RJxbLYxJ/0jJcO5H8afN1BkOSECgYEA7nn2
h7jwvEZkTN1sAsYROQKssxBhZYQ4+M4wJ+QdGB4t6KNbO2PH+l1U8Mg2yFq++QRq
uP9Q+qr89oTUYggmXzGseV5SeArMA5VN7py5Jg8L8eJGVEXALLXaC3ywTNY33A2C
ff55HT7TRDoI638T4k7iYJ1VkfaDsexEgi8HXoECgYBK2H8fx98rt3e+wMxW3iaO
afFpwawpaGNzX/SW/HYe30H4g0i5IigXTYenTUP1M1Rz0/iio8USOX7WOZ3LQr/k
9bssPYaf3kXomPMfMPN3rxzZh2hUcuw2oY6pDSrcrItwO/iz8XMceff7aQWjBuMh
hNIrs9WbF5Zg/6/e5Xcm4QKBgCAM9D05deFX9JMAD0wwIpBu85b9VJm4M4/85iv2
VJKxO6pQiippNq9Ha+sQfYxf5drB5TYH2nJWGLlpEMI8JiwVGQEW1C6eBN1Wa6ru
FVQwIYLYzmr1FObtaeixUWCCSe+hQTB9yvlLQEmjIx/DbIC16WbivmVnpDt9bZex
imQBAoGARrA+XSwSytsdeM1JTIBwOhS8IprraxfVqhaqg7zneypy54Q3V9+inVeK
rvVmEazUVtZsTg/J3uKxHv+LX+mFdwAV8lDElz/p1K1stMfsSUnNKLLDmRjrN586
RBcp36sTnMVYikffNqxfowzymmvrJAKOny3sovZICzPkgzoPTm8=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,62 @@
-----BEGIN CERTIFICATE-----
MIIGEjCCBPqgAwIBAgIQBbg2EGUqRzMeBv4XlQPfPzANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzIwHhcNMjUwMzEzMDAwMDAwWhcNMjUwNjEwMjM1OTU5WjAc
MRowGAYDVQQDExF4aWFva3VhaXNvbmcuc2hvcDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAO4AdSFAtVLcN4IWdw6NlSWgQvrwCVGqVTwpuFLgWQb484jJ
1H3qoXtTjwJMFAtQg7boefsWnJJ7IXmOgq3XzleapDKgr4OKKWr3Alro/XecKaTt
VKAY7G2DVLJWM8Kthd19PzdbBVDkDOelevXyGPinnaLOhR7GQ/xK1+8oQwWOB+En
tkOTUIy9dnllcrNyZ9OwE00wH1W7HsCQMXjIefI+dZFVNF4Yo3prom77YIgkijGu
CU1DNvrZXcQvV32+BLd8RU1nQCZ6feOaKlgM6A2vWLMzRmCa7N0xSSrAsbl3vMAQ
U+IkS2/oXMlZvFnpGsdOB5iYZdTWYU/qYO5h56ECAwEAAaOCAvwwggL4MB8GA1Ud
IwQYMBaAFHjfkZBf7t6s9sV169VMVVPvJEq2MB0GA1UdDgQWBBQXPuvYOoWcOMyK
b3LR/m5M+SEqOjAzBgNVHREELDAqghF4aWFva3VhaXNvbmcuc2hvcIIVd3d3Lnhp
YW9rdWFpc29uZy5zaG9wMD4GA1UdIAQ3MDUwMwYGZ4EMAQIBMCkwJwYIKwYBBQUH
AgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCBaAw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIGABggrBgEFBQcBAQR0MHIw
JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBKBggrBgEFBQcw
AoY+aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0VuY3J5cHRpb25FdmVyeXdo
ZXJlRFZUTFNDQS1HMi5jcnQwDAYDVR0TAQH/BAIwADCCAX8GCisGAQQB1nkCBAIE
ggFvBIIBawFpAHcATnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8AAAGV
jmc97gAABAMASDBGAiEAoYWQ8iavGTLIH4jqUngz50C0ZjZAnlTxgg8/YwjsQUoC
IQDgojkRl8fgVYI3jC0u/n8MNQEnoKTJI8Y9q3cAsm0+XgB3AHMgIg8IFor588Sm
iwqyappKAO71d4WKCE0FANSlQkRZAAABlY5nPhAAAAQDAEgwRgIhAPR2D8STMyua
aO386zeiuK50P3zUM7hXC6hkrRTq6HJaAiEAhNZWFG2XG93jldcd76I+o74Xl/3i
OS+6IgavCD15cYQAdQDm0jFjQHeMwRBBBtdxuc7B0kD2loSG+7qHMh39HjeOUAAA
AZWOZz4gAAAEAwBGMEQCIBDSrtuoMLTcOXWkLUd2wE1b/rOyJhrOpNzuyVSjiJl8
AiA+lA9UqElkIXJ7dVbv6+9Y3OviuCpFlhcL0HChF+C0gzANBgkqhkiG9w0BAQsF
AAOCAQEA0CZyVm1PtZh5EZoMu0vheFjgEeefq2qvqU5b0/aQaCXnLnBu694tBlL2
YZz+L0Q2wnzjBMczoJFBROEyauvHT4rtq/xkyUOtLl9XB4jD+H0pNWwJ1m69m9ED
7Tqam3PKTc4k7ICLRZtLTHk/3hW0EuAYpf8Ncm2Bqr8RHGVkQ4+5TyVuK3TzDrtG
ICX1+k8JQxupEWHzmZkcCf5qd1m+yv/qzkCQ2bqe48zFqMx+1Nw2mc82VBp+TXup
Gk9rNNStjj7+pDYNGBQDR35WXKgy1Nq8npqMLCZcFHLMld9hCfoRkjoYiINbCqBM
dpfu6Ss9Ap9isZv9IqW33n0WY/8HVw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
-----END CERTIFICATE-----

10
CampusExpressDelivery/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

51
CampusExpressDelivery/components.d.ts vendored Normal file
View File

@ -0,0 +1,51 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AAvatar: typeof import('ant-design-vue/es')['Avatar']
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
ATable: typeof import('ant-design-vue/es')['Table']
ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
AUpload: typeof import('ant-design-vue/es')['Upload']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

Binary file not shown.

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>校快送</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

3952
CampusExpressDelivery/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "campusexpressdelivery",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"ant-design-vue": "^4.2.6",
"axios": "^1.7.7",
"echarts": "^5.6.0",
"element-plus": "^2.8.8",
"pinia": "^2.2.6",
"pinia-plugin-persistedstate": "^4.1.3",
"vue": "^3.5.12",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"sass-embedded": "^1.81.0",
"typescript": "~5.6.2",
"unplugin-auto-import": "^0.18.5",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.10",
"vue-tsc": "^2.1.8"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,13 @@
<template>
<a-config-provider :locale="zhCN">
<router-view/>
</a-config-provider>
</template>
<script setup lang="ts">
import zhCN from "ant-design-vue/es/locale/zh_CN";
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
</script>

View File

@ -0,0 +1,31 @@
// 创建实例时配置默认值
import axios from "axios";
import router from "../router";
//const viteEnv = import.meta.env;
const myAxios = axios.create({
withCredentials: true,
//baseURL:'http://39.101.78.35:6271/api'
// baseURL:'http://localhost:9999'
// baseURL:'http://39.101.78.35:6445/api'
// baseURL:'https://xiaokuaisong.shop:6448/api'
baseURL:'https://xiaokuaisong.shop/api'
});
myAxios.interceptors.request.use(function (config) {
return config;
}, function (error) {
return Promise.reject(error);
});
myAxios.interceptors.response.use(function (response: any) {
if (response.data.code === 40100) {
router.replace('/')
}
return response.data;
}, function (error) {
return Promise.reject(error);
});
export default myAxios;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,48 @@
<template>
<div id="manage">
<a-layout has-sider>
<a-layout-sider :style="{ width: '200px' }">
<ManageSidebar/>
</a-layout-sider>
<a-layout>
<a-layout-header :style="{ background: '#fff', padding: 0}">
<ManageHeader/>
</a-layout-header>
<a-layout-content class="main">
<router-view :key="key" class="main-card"/>
</a-layout-content>
</a-layout>
</a-layout>
</div>
</template>
<script setup lang="ts">
import ManageHeader from "./manage/ManageHeader.vue";
import ManageSidebar from "./manage/ManageSidebar.vue";
import {useRoute} from "vue-router";
const route = useRoute();
const key = () => {
return route.path + Math.random();
}
</script>
<style scoped>
#manage {
height: 100%;
width: 100%;
position: fixed;
}
.main {
padding: 5px;
box-sizing: border-box;
display: block;
flex: 1;
flex-basis: 0;
overflow: auto;
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<div class="manage-header">
<div class="header-left">
<a-breadcrumb class="breadcrumb">
<a-breadcrumb-item>校快送后台管理系统</a-breadcrumb-item>
<a-breadcrumb-item class="routeName">{{ route.name }}</a-breadcrumb-item>
</a-breadcrumb>
</div>
<div class="header-right">
<!-- <div class="bell">-->
<!-- <BellTwoTone/>-->
<!-- </div>-->
<div class="name">
{{ store.loginUser.userRole === "notLogin" ? '未登录' : store.loginUser.userRole }}
</div>
<div class="user">
<a-dropdown>
<a-avatar :size="40" class="user-avatar" :src="store.loginUser.avatarUrl"/>
<template #overlay>
<a-menu>
<a-menu-item @click="logout">
<LogoutOutlined />
退出登录
</a-menu-item>
<a-menu-item @click="showDrawer">
<UserOutlined />
个人中心
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</div>
<a-drawer
title="个人中心"
:placement="placement"
:closable="false"
:open="open"
@close="onClose"
class="custom-class"
>
<a-avatar :size="64" :src="store.loginUser.avatarUrl"></a-avatar>
<div class="message">
<p class="firstmessage">用户名{{ store.loginUser.username }}</p>
<p>账号ID{{ store.loginUser.username }}</p>
<p>注册时间2024-06-20</p>
<p>账号权限{{store.loginUser.userRole}}</p>
<p>联系方式{{store.loginUser.phone}}</p>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import {useRoute, useRouter} from "vue-router";
import {userStore} from "../../store/userStore.ts";
import {UserOutlined,LogoutOutlined} from '@ant-design/icons-vue';
import myAxios from "../../api/myAxios.ts";
import {message} from "ant-design-vue";
import { ref } from 'vue';
import type { DrawerProps } from 'ant-design-vue';
const router = useRouter();
const route = useRoute();
const store = userStore()
const placement = ref<DrawerProps['placement']>('right');
const open = ref<boolean>(false);
const showDrawer = () => {
open.value = true;
};
const onClose = () => {
open.value = false;
};
/**
* 登出
*/
const logout = async () => {
const res: any = await myAxios.post("/user/logout", {})
if (res.code === 0 && res.data) {
// 清除用户角色
store.$reset()
await router.replace('/')
message.success('登出成功');
} else {
message.error(`登出失败:${res.message}`);
}
}
</script>
<style scoped>
.manage-header {
display: flex;
height: 100%;
justify-content: space-between;
background: #fff;
border-bottom: 2px solid #ccced7;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
.header-left {
display: flex;
margin-left: 20px;
}
.header-left .breadcrumb {
color: black;
display: flex;
align-items: center;
font-size: 20px;
font-weight: bold;
}
.routeName {
font-size: 13px;
padding-top: 10px;
font-weight: 400;
}
.header-right {
display: flex;
margin-right: 30px;
}
.header-right .bell {
display: flex;
align-items: center;
margin-right: 14px;
}
.header-right .name {
display: flex;
align-items: center;
margin-right: 14px;
}
.header-right .user {
display: flex;
align-items: center;
}
.user-avatar {
cursor: pointer;
//transition: all 0.5s;
user-select: none;
}
.user-avatar:hover {
transform: rotate(360deg);
}
.custom-class img{
float: left;
}
.message {
float: right;
margin-right: 100px;
}
.firstmessage {
margin-top: 0;
}
</style>

View File

@ -0,0 +1,91 @@
<template>
<a-menu
v-model:selectedKeys="selectedKeys"
mode="inline"
theme="dark"
style="max-height: 200vh; min-height: 100vh"
@click="handleClick"
>
<a-menu-item key="/index">
<PieChartOutlined />
<span>首页</span>
</a-menu-item>
<a-sub-menu>
<template #title>
<span>
<UserOutlined />
<span>用户管理</span>
</span>
</template>
<a-menu-item key="/userList">用户列表</a-menu-item>
</a-sub-menu>
<a-sub-menu>
<template #title>
<span>
<UserOutlined />
<span>快送员管理</span>
</span>
</template>
<a-menu-item key="/errandList">快送员列表</a-menu-item>
<a-menu-item key="/runList">订单列表</a-menu-item>
</a-sub-menu>
<a-sub-menu>
<template #title>
<span>
<ShopOutlined />
<span>店铺管理</span>
</span>
</template>
<a-menu-item key="/businessManage">门店管理</a-menu-item>
<a-menu-item key="/storeList">订单列表</a-menu-item>
<a-menu-item key="/log">店铺操作日志</a-menu-item>
</a-sub-menu>
<a-sub-menu>
<template #title>
<span>
<SettingOutlined />
<span>系统管理</span>
</span>
</template>
<a-menu-item key="/info">管理员公告管理</a-menu-item>
<!-- <a-menu-item key="/businessInfoManage">商家公告管理</a-menu-item>-->
<a-menu-item key="/category">分类管理</a-menu-item>
<a-menu-item key="/carousel">轮播图管理</a-menu-item>
</a-sub-menu>
<a-menu-item key="/CustomerService">
<AlipayOutlined />
<span>人工客服</span>
</a-menu-item>
</a-menu>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {UserOutlined, ShopOutlined, SettingOutlined,AlipayOutlined,PieChartOutlined} from '@ant-design/icons-vue';
import {useRoute, useRouter} from "vue-router";
const route = useRoute();
const router = useRouter();
// 选中侧边栏
const selectedKeys = ref<string[]>(['/userList']);
onMounted(() => {
setSelectedKey()
})
// 根据路由设置当前选中侧边栏
const setSelectedKey = () => {
selectedKeys.value = [route.path];
}
// 路由跳转
const handleClick = (item: any) => {
router.push(item.key)
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,14 @@
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'
import pinia from "./store";
import router from "./router";
const app = createApp(App)
// 配置路由
app.use(router)
// 配置pinia
app.use(pinia)
// 挂在实例
app.mount('#app')

View File

@ -0,0 +1,11 @@
import {createRouter, createWebHashHistory} from "vue-router";
import {routes} from "./routes";
// 创建路由实例并传递 `routes` 配置
const router = createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
export default router

View File

@ -0,0 +1,121 @@
// 将路由规则 routes 导出
export const routes = [
// 全局路由(无需嵌套上左右整体布局)
{
path: '/',
name: 'Login',
component: () => import("../view/Login.vue")
},
{
path: '/test',
name: '全局测试页面',
component: () => import("../view/Test.vue"),
},
// 管理端
{
path: '/manage',
component: () => import("../layout/ManageLayout.vue"),
children: [
// 首页
{
path: '/index',
name: '首页',
component: () => import("../view/Index.vue"),
},
// 账户管理
{
path: '/userList',
name: '用户列表',
component: () => import("../view/user/UserList.vue"),
},
//快送员管理
{
path: '/errandList',
name: '快送员列表',
component: () => import("../view/errand/errandList.vue"),
},
{
path: '/errandInfo',
name: '快送员审核',
component: () => import("../view/errand/errandInfo.vue"),
},
{
path: '/runList',
name: '快送员订单',
component: () => import("../view/errand/runList.vue"),
},
{
path: '/errandOrderList',
name: '快送员订单列表',
component: () => import("../view/errand/errandOrderList.vue"),
},
// 店铺管理
{
path: '/businessManage',
name: '门店管理',
component: () => import("../view/shop/business/BusinessManage.vue"),
},
{
path: '/businessInfo',
name: '门店详情',
component: () => import("../view/shop/business/BusinessInfo.vue"),
},
{
path: '/DishGroup',
name: '菜品分组详情',
component: () => import("../view/shop/business/DishGroup.vue"),
},
{
path: '/Dish',
name: '菜品详情',
component: () => import("../view/shop/business/Dish.vue"),
},
{
path: '/storeList',
name: '店铺列表',
component: () => import("../view/shop/order/StoreList.vue"),
},
{
path: '/orderList',
name: '订单列表',
component: () => import("../view/shop/order/OrderList.vue"),
},
{
path: '/orderDetail',
name: '订单详情',
component: () => import("../view/shop/order/OrderDetail.vue"),
},
{
path: '/log',
name: '门店操作日志',
component: () => import("../view/shop/BusinessLog.vue"),
},
// 系统管理
{
path: '/info',
name: '公告管理',
component: () => import("../view/system/Info.vue"),
},
{
path: '/businessInfoManage',
name: '商家公告管理',
component: () => import("../view/system/BusinessInfo.vue"),
},
{
path: '/category',
name: '分类管理',
component: () => import("../view/system/Category.vue"),
},
{
path: '/carousel',
name: '轮播图管理',
component: () => import("../view/system/Carousel/Carousel.vue"),
},
{
path: '/CustomerService',
name: '人工客服',
component: () => import("../view/Service/CustomerService.vue"),
}
]
},
]

View File

@ -0,0 +1,10 @@
import {createPinia} from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate)
export default pinia

View File

@ -0,0 +1,32 @@
import {defineStore} from 'pinia'
import myAxios from "../api/myAxios";
export const userStore = defineStore('user', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
loginUser: {
username: '未登录',
avatarUrl: '',
userRole: 'notLogin',
phone:''
}
}
}, persist: true,
actions: {
// 获取登录用户信息
async getLoginUser() {
// 请求登录信息
const res: any = await myAxios.get('/user/current');
if (res.code === 0 && res.data) {
console.log("56765765756567")
this.updateUser(res.data)
}
},
// 更新用户信息
updateUser(payLoad: any) {
this.loginUser = payLoad;
}
}
})

View File

@ -0,0 +1,48 @@
html {
/* 滚动时采用平滑过渡 */
scroll-behavior: smooth;
}
body {
padding: 0;
margin: 0;
}
/* 管理页面全局样式 */
.main-card {
min-height: calc(100vh - 100px);
}
.search-box {
display: flex;
background-color: white;
height: auto;
box-shadow: 0 0 1px 1px #dedede;
border-radius: 5px;
padding: 20px;
}
.middle-button {
display: flex;
justify-content: flex-end;
margin: 20px;
}
.data-box {
margin-top: 20px;
padding: 20px;
box-shadow: 0 0 1px 1px #dedede;
border-radius: 5px;
}
.pagination {
margin: 20px 0 20px 0;
float: right;
}
.txt {
font-weight: 900;
font-size: 500px;
}

View File

@ -0,0 +1,11 @@
// 时间处理
import router from "../router";
export const formatTime = (row: any) => {
let data = row.value;
return data.slice(0, 16).replace('T', '~');
}
export const backPage = () => {
router.back()
}

View File

@ -0,0 +1,338 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import BrokenLine from "./echarts/BrokenLine.vue";
import myAxios from "../api/myAxios.ts";
import { ElDatePicker,ElAlert} from 'element-plus'
import 'element-plus/dist/index.css'
const isLoading = ref(true)
const dailyTransactions = ref('')
const fiscalRevenue = ref('')
const value1:any = ref<[Date, Date] | null>(null)
const defaultTime1 = new Date(2000, 1, 1, 12, 0, 0)
// 日期格式化函数
const formatDate:any = (date: Date | null | undefined): string => {
if (!date) return ""
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 监听日期选择变化
watch(value1, (newVal) => {
let startTime = ""
let endTime = ""
if (newVal && newVal.length === 2) {
startTime = formatDate(newVal[0])
endTime = formatDate(newVal[1])
}
fetchRevenue(startTime, endTime)
totalNumber(startTime, endTime)
})
// 获取财政收入数据
const fetchRevenue:any = async (startTime: string = "", endTime: string = "") => {
try {
const response:any = await myAxios.post('/orders/count', {
businessId: "",
businessName: "",
businessState: 0,
categoryId: 0,
endTime: endTime,
startTime: startTime,
state: 0,
type: "money"
}, {
headers: {
'Content-Type': 'application/json'
}
})
if (response.code === 0) {
fiscalRevenue.value = parseFloat(response.data).toFixed(2)
}
} catch (error) {
console.error('Error fetching revenue:', error)
}
}
// 获取交易单量数据
const totalNumber:any = async (startTime: string = "", endTime: string = "") => {
try {
const response:any = await myAxios.post('/orders/count', {
businessId: "",
businessName: "",
businessState: 0,
categoryId: 0,
endTime: endTime,
startTime: startTime,
state: 0,
type: "number"
}, {
headers: {
'Content-Type': 'application/json'
}
})
if (response.code === 0) {
dailyTransactions.value = response.data
}
} catch (error) {
console.error('Error fetching total number:', error)
}
}
const refundList = ref<string[]>([]) // 明确类型为字符串数组
const getReasonRefund = async () => {
try {
const response:any = await myAxios.post('/refund/list/reson', {}, { // 添加空请求体
headers: {
'Content-Type': 'application/json'
}
})
if (response.code === 0) {
console.log(response)
// 确保数据是数组格式(根据你的接口返回结构调整)
refundList.value = Array.isArray(response.data) ? response.data : []
}
} catch (error: any) {
console.error('请求失败:', error)
}
}
onMounted(async () => {
try {
await Promise.all([
fetchRevenue(),
totalNumber(),
getReasonRefund()
])
} catch (error) {
console.error('数据加载失败:', error)
} finally {
isLoading.value = false
}
})
</script>
<template>
<a-spin v-if="isLoading" class="loading-spinner" />
<div v-if="!isLoading">
<div class="container">
<div class="box">
<div class="number">每日交易量</div>
<div class="number">{{ dailyTransactions }}</div>
<div class="number">食堂总共交易量为{{ dailyTransactions }}</div>
</div>
<div class="box">
<div class="number">财政收入</div>
<div class="number">{{ fiscalRevenue }}</div>
<div class="number">每日财政收入金额为{{ fiscalRevenue }}</div>
</div>
<div class="boxTime">
<div class="boxTime-number">时间选择器</div>
<el-date-picker
v-model="value1"
type="datetimerange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="defaultTime1"
/>
</div>
</div>
<div class="alert-chart-container">
<div class="alert-container">
<div class="boxTime-number">退款原因申请</div>
<template v-if="refundList.length === 0">
<a-empty />
</template>
<template v-else>
<div class="refund-list">
<el-alert
v-for="(item, index) in refundList"
:key="index"
:title="`退款原因 ${index + 1}`"
type="error"
:description="item"
show-icon
:closable="false"
class="alert-item"
/>
</div>
</template>
</div>
<div class="chart-container">
<BrokenLine />
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.container {
overflow: auto;
}
.box {
float: left;
width: 30%;
margin: 1%;
background-color: #fff;
padding: 20px;
box-sizing: border-box;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
.boxTime {
float: left;
margin: 1%;
background-color: #fff;
padding: 20px;
box-sizing: border-box;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
border-radius: 15px;
}
.boxTime-number {
font-weight: bold;
font-size: 20px;
margin-bottom: 15px;
}
.number:first-child {
font-weight: bold;
}
.number:nth-child(2) {
font-size: 24px;
color: #4CAF50; /* Green color for the revenue amount */
}
.number:nth-child(3) {
color: #4CAF50; /* Green color for the change since last week */
font-size: 16px;
line-height: 1.5;
margin-top: 10px;
}
.brokenLine {
margin: 0 auto;
width: 60%;
height: 40%;
}
.ordermethod {
width: 100%;
height: 40%;
display: flex;
justify-content: start;
box-sizing: border-box;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
.el-alert {
margin: 20px 0 0;
}
.el-alert:first-child {
margin: 0;
}
.alertMessage {
width: 50%;
background-color: #fff;
padding: 20px;
border-radius: 15px;
margin: 1%;
box-sizing: border-box;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
/* 新增布局样式 */
.alert-chart-container {
display: flex;
gap: 20px;
margin: 1%;
height: 500px;
width: calc(100% - 2%); /* 补偿左右margin */
}
.alert-container {
flex: 0 0 30%;
display: flex;
flex-direction: column;
background: #fff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.16);
height: 500px;
.refund-list {
flex: 1;
overflow-y: auto;
padding-right: 8px;
.alert-item {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
}
/* 自定义滚动条样式 */
.refund-list::-webkit-scrollbar {
width: 6px;
}
.refund-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.refund-list::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.refund-list::-webkit-scrollbar-thumb:hover {
background: #555;
}
.chart-container {
flex: 1;
background: #fff;
padding: 20px;
border-radius: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.16);
min-width: 0;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.alert-chart-container {
flex-direction: column;
height: auto;
}
.alert-container {
flex: none;
width: 100%;
}
.chart-container {
height: 400px;
}
}
.loading-spinner {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div id="login">
<form>
<div class="box" @submit.prevent>
<h2>校快送后台管理系统</h2>
<div class="input-box">
<label>账号</label>
<input type="text" placeholder="账号" v-model="userAccount"/>
</div>
<div class="input-box">
<label>密码</label>
<div class="password-container">
<input
class="password"
:type="showPassword ? 'text' : 'password'"
placeholder="密码"
v-model="userPassword"
autocomplete="new-password"
/>
<div @click="togglePassword" class="password-toggle">
<img
src="../assets/login/hidePassword.png"
v-show="!showPassword"
alt="隐藏密码"
/>
<img
src="../assets/login/showPassword.png"
v-show="showPassword"
alt="显示密码"
/>
</div>
</div>
</div>
<div class="btn-box">
<div>
<button @click="onLogin" @keyup.enter="onLogin" type="button">登录</button>
</div>
</div>
</div>
</form>
</div>
</template>
<script setup lang="ts">
import {useRouter} from 'vue-router'
import {ref} from 'vue'
import {userStore} from "../store/userStore.ts";
import myAxios from "../api/myAxios";
import {message} from "ant-design-vue";
const store = userStore()
const router = useRouter()
//默认闭眼图标
let showPassword = ref(false)
const userAccount = ref('');
const userPassword = ref('');
/**
* 登录
*/
const onLogin = async () => {
try {
const res: any = await myAxios.post("/user/login", {
appName: 'admin',
userAccount: userAccount.value,
userPassword: userPassword.value
});
if (res.code === 0 && res?.data) {
console.log(res);
// 存储用户角色
await store.getLoginUser();
// 添加调试信息
console.log('Redirecting to /index');
//await router.replace('/index');
router.push('/index');
message.success('登录成功');
} else {
message.error(`登录失败:${res.description}`);
}
} catch (error) {
console.error('Login error:', error);
message.error('登录失败,请稍后再试');
}
}
/**
* 切换密码显示/隐藏
*/
const togglePassword = () => {
showPassword.value = !showPassword.value
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
#login {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
background-image: url('../assets/background.png');
background-repeat: no-repeat;
background-size: 100% 100%;
}
.box {
width: 350px;
height: 350px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
border-left: 1px solid rgba(255, 255, 255, 0.5);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
border-right: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
background: rgba(50, 50, 50, 0.2);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.box > h2 {
color: rgba(255, 255, 255, 0.9);
margin-bottom: 30px;
}
.box .input-box {
display: flex;
flex-direction: column;
box-sizing: border-box;
margin-bottom: 10px;
}
.box .input-box label {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 5px;
}
.box .input-box input {
letter-spacing: 1px;
font-size: 14px;
box-sizing: border-box;
width: 250px;
height: 35px;
border-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.2);
outline: none;
padding: 0 12px;
color: rgba(255, 255, 255, 0.9);
transition: 0.2s;
}
/* 密码输入框容器 */
.password-container {
position: relative;
display: inline-block;
width: 250px;
}
.password-container .password {
width: 100%;
padding-right: 40px; /* 为图标留出空间 */
box-sizing: border-box;
}
/* 禁用浏览器默认的密码显示功能 */
.password::-ms-reveal,
.password::-ms-clear {
display: none;
}
.password-toggle {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
user-select: none;
z-index: 1;
}
.password-toggle img {
width: 30px;
height: 30px;
display: block;
}
.box .input-box input:focus {
border: 1px solid rgba(255, 255, 255, 0.8);
}
.box .btn-box {
width: 250px;
display: flex;
flex-direction: column;
align-items: start;
}
.box .btn-box > a {
outline: none;
display: block;
width: 250px;
text-align: end;
text-decoration: none;
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.box .btn-box > a:hover {
color: rgba(255, 255, 255, 1);
}
.box .btn-box > div {
margin-top: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.box .btn-box > div > button {
outline: none;
margin-top: 10px;
display: block;
font-size: 14px;
border-radius: 5px;
transition: 0.2s;
}
.box .btn-box > div > button:nth-of-type(1) {
width: 250px;
height: 35px;
color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(64, 149, 229, 0.7);
background: rgba(64, 149, 229, 0.5);
}
.box .btn-box > div > button:nth-of-type(2) {
width: 120px;
height: 35px;
margin-left: 10px;
color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(64, 149, 229, 0.7);
background: rgba(64, 149, 229, 0.5);
}
.box .btn-box > div > button:hover {
border: 1px solid rgba(64, 149, 229, 0.7);
background: rgba(64, 149, 229, 0.5);
}
@media (max-width: 1440px) {
.container {
width: 28%;
}
}
@media (max-width: 1366px) {
.container {
width: 30%;
}
}
@media (max-width: 1280px) {
.container {
width: 33%;
}
.agileheader h1 {
font-size: 41px;
}
.container h2 {
font-size: 27px;
}
}
@media (max-width: 1080px) {
.container {
width: 49%;
}
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div style="width: 100%; height: 100%;">
<iframe
:src="chatUrl"
frameborder="0"
style="width: 100%; height: 100%; border: none;"
></iframe>
</div>
</template>
<script setup lang="ts">
const chatUrl = "https://ccs.cloud.alipay.com/bench/online";
</script>
<style scoped>
/* 样式部分保持不变 */
</style>

View File

@ -0,0 +1,3 @@
<template>
<div>123</div>
</template>

View File

@ -0,0 +1,202 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import * as echarts from "echarts";
import myAxios from "../../api/myAxios.ts";
const main = ref();
const orderData = ref<any[]>([]);
onMounted(() => {
init();
getNumberOrder();
});
const getNumberOrder = async () => {
try {
const response:any = await myAxios.post(
"/orders/list/numnber",
{},
{
headers: {
"Content-Type": "application/json",
},
}
);
if (response.code === 0 && Array.isArray(response.data)) {
orderData.value = response.data.reverse(); // 反转数组顺序以匹配日期排列
updateChart();
}
} catch (error: any) {
console.error("请求失败:", error);
}
};
const init = () => {
const myChart = echarts.init(main.value);
// 初始空配置
myChart.setOption(getChartOption());
};
const updateChart = () => {
const myChart = echarts.getInstanceByDom(main.value);
if (myChart) {
myChart.setOption(getChartOption());
}
};
const getChartOption = () => {
// 提取数据并处理为图表需要的格式
const dates = orderData.value.map((item) => item.date);
const takeOutData = orderData.value.map((item) => parseInt(item.takeOutNum));
const eatInData = orderData.value.map((item) => parseInt(item.eatInNum));
const selfLiftData = orderData.value.map((item) => parseInt(item.selfLiftNum));
return {
color: ["#80FFA5", "#00DDFF", "#37A2FF", "#FF0087", "#FFBF00"],
title: {
text: "订单数据趋势图",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
label: {
backgroundColor: "#6a7985",
},
},
},
legend: {
data: ["外卖订单", "堂食订单", "自提订单"], // 修正中文名称
},
toolbox: {
feature: {
saveAsImage: {},
},
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: [
{
type: "category",
boundaryGap: false,
data: dates, // 动态日期数据
},
],
yAxis: [
{
type: "value",
name: "订单数量",
},
],
series: [
{
name: "外卖订单",
type: "line",
stack: "Total",
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgb(128, 255, 165)",
},
{
offset: 1,
color: "rgb(1, 191, 236)",
},
]),
},
emphasis: {
focus: "series",
},
data: takeOutData, // 动态数据
},
{
name: "堂食订单",
type: "line",
stack: "Total",
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgb(0, 221, 255)",
},
{
offset: 1,
color: "rgb(77, 119, 255)",
},
]),
},
emphasis: {
focus: "series",
},
data: eatInData, // 动态数据
},
{
name: "自提订单",
type: "line",
stack: "Total",
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgb(255, 0, 135)",
},
{
offset: 1,
color: "rgb(135, 0, 157)",
},
]),
},
emphasis: {
focus: "series",
},
data: selfLiftData, // 动态数据
},
],
};
};
</script>
<template>
<div class="echarts">
<div style="width: 100%; height: 100%; margin-top: 1rem">
<div ref="main" style="width: 100%; height: 100%"></div>
</div>
</div>
</template>
<style scoped>
.echarts {
width: 90%;
height: 80%;
border-radius: 12px;
margin: 1rem;
padding: 1rem;
background-color: #ffffff;
min-width: 0;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
</style>

View File

@ -0,0 +1,80 @@
<script setup lang="ts">
import {onMounted, ref} from "vue";
import * as echarts from "echarts";
const main = ref();
onMounted(() => {
init();
});
const init = () => {
const myChart = echarts.init(main.value);
let option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
padAngle: 5,
itemStyle: {
borderRadius: 10
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 735, name: '跑腿类' },
{ value: 580, name: '自提类' },
{ value: 484, name: '外卖类' },
]
}
]
};
myChart.setOption(option);
}
</script>
<template>
<div class="echarts">
<div style="width: 100%; height: 100%; margin-top: 1rem">
<div ref="main" style="width: 100%; height: 100%"></div>
</div>
</div>
</template>
<style scoped>
.echarts {
width: 50%;
height: 80%;
border-radius: 12px;
margin: 1rem;
padding: 1rem;
background-color: #ffffff;
min-width: 0;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<a-card :loading="loading">
<div >
<a-descriptions size="default" bordered class="txt">
<a-descriptions-item label="头像">
<template v-slot>
<a-avatar :src="errandInfo.errandAvatarUrl" style="width: 100px;height: 100px;"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="用户名称">{{ errandInfo?.errandName }}</a-descriptions-item>
<a-descriptions-item label="用户性别">
<div v-if="errandInfo.gender === 1">
<a-tag color="blue"></a-tag>
</div>
<div v-else-if="errandInfo.gender === 0">
<a-tag color="pink"></a-tag>
</div>
</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ errandInfo?.errandPhone }}</a-descriptions-item>
<a-descriptions-item label="配送范围">{{ errandInfo?.distributionScope }}</a-descriptions-item>
<a-descriptions-item label="银行卡号">{{ errandInfo?.bankCard }}</a-descriptions-item>
<a-descriptions-item label="身份证正面">
<template v-slot>
<a-image :src="errandInfo.frontIdCard" style="width: 158.5px; height: 100px;"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="身份证反面">
<template v-slot>
<a-image :src="errandInfo.backIdCard" style="width: 158.5px; height: 100px"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="当前状态">
<div v-if="errandInfo.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="errandInfo.state === 1">
<a-tag color="green">快送员正常</a-tag>
</div>
<div v-else-if="errandInfo.state === 2">
<a-tag color="red">快送员驳回</a-tag>
</div>
</a-descriptions-item>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar :src="record.businessAvatar"/>
</template>
<template v-else-if="column.key === 'license'">
<a-image :src="record.license" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-else-if="column.key === 'frontIdCard'">
<a-image :src="record.frontIdCard" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-else-if="column.key === 'backIdCard'">
<a-image :src="record.backIdCard" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="green">已通过</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">未通过</a-tag>
</div>
</template>
</template>
</a-descriptions>
</div>
<a-space>
<div class="button-box">
<a-button
class="button-s"
size="small"
type="default"
style="color: blue"
@click="backPage">返回
</a-button>
<a-button
class="button-s"
size="small" v-if="state === 0"
type="primary"
style="color: white; margin-left: 15px"
danger
@click="update(2)">未通过
</a-button>
<a-button
class="button-s"
size="small" v-if="state === 0"
type="primary"
style="color: white; margin-left: 15px"
@click="update(1)">通过
</a-button>
</div>
</a-space>
</a-card>
</template>
<script setup lang="ts">
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import myAxios from "../../api/myAxios";
import {backPage} from "../../utils/TableConfig";
import {message} from "ant-design-vue";
import router from "../../router";
const state = ref(0)
const route = useRoute();
const loading = ref(false);
const errandInfo: any = ref({});
const errandId = ref<string | null>(null);
if (typeof route.query.id === 'string') {
errandId.value = route.query.id;
}
console.log(errandId.value);
onMounted(() => {
if(errandId.value !== null) {
getErrandVO(errandId.value);
}
});
const getErrandVO = async (id: string) => {
loading.value = true;
try {
// 修改参数传递方式为query params
const res: any = await myAxios.post('/errand/get/id', null, {
params: {
errandId1: id // 直接使用参数id
}
});
console.log(res)
if (res.code === 0 && res.data) {
errandInfo.value = res.data || {};
state.value = res.data.state ?? 0;
}else if(res.code===40000){
message.error(res.description);
router.go(-1)
}else {
message.error(`获取数据失败:${res.message}`);
}
} catch (error) {
console.error("Error fetching data:", error);
message.error('请求过程中出现错误,请稍后再试');
} finally {
loading.value = false;
}
}
const update = async (newState: number) => {
try {
// 构建请求体,仅包含'id'和'state'
const requestBody = {
id: errandId.value || '',
state: newState,
};
const res: any = await myAxios.post("/errand/update", requestBody, {
headers: {
'Content-Type': 'application/json', // 修改为JSON格式如果你的后端接受JSON的话
}
});
if (res.code === 0) {
message.success("已审核");
state.value = newState; // 更新本地状态
} else {
message.error(`审核失败:${res.message}`);
}
} catch (error) {
console.error("更新状态失败:", error);
message.error('请求过程中出现错误');
}
}
</script>

View File

@ -0,0 +1,228 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="快送员名称">
<a-input-search
style="width: 300px"
placeholder="请输入快送员名称"
enter-button
allow-clear
@search="onSearch"
/>
</a-form-item>
<a-form-item label="状态">
<a-select
style="width: 200px;"
placeholder="默认查询全部状态"
allowClear
v-model:value="searchParams.state"
@change="handleChange"
>
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未审核</a-select-option>
<a-select-option value="1">已审核</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="手机号">
<a-input-search
style="width: 300px"
placeholder="请输入手机号"
enter-button
allow-clear
@search="onSearchPhone"
/>
</a-form-item>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination="false"
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'errandAvatarUrl'">
<a-avatar :src="record.errandAvatarUrl"/>
</template>
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="green">已审核</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">封禁</a-tag>
</div>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button v-if="record.state === 0" size="small" type="link" @click="toInfoPage(record.id)">审核</a-button>
<a-button v-else size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="toInfoPage(record.id)">查看
</a-button>
<a-button :disabled="record.state === 0" size="small" type="link" danger
@click="doBan(record.id, record.state === 1)">
{{ record.state === 1 ? '禁用' : '启用' }}
</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total} 个快送员`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import myAxios from "../../api/myAxios";
import { formatTime } from "../../utils/TableConfig";
import { message } from "ant-design-vue";
import router from "../../router";
const tableData: any = ref([]);
const loading = ref(false);
// 分页
const total: any = ref(0);
// 请求参数
const searchParams: any = ref({
sortField: "createTime",
sortOrder: "descend",
current: 1,
pageSize: 10,
errandName: "",
state: "", // 默认为空,表示查询所有状态
errandPhone:""
});
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
customRender: ({ index }: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{ title: '快送员名称', dataIndex: 'errandName', width: 100, fixed: 'left', align: 'center' },
{ title: '快送员头像', key: 'errandAvatarUrl', width: 50, align: 'center' },
{ title: '手机号', dataIndex: 'errandPhone', width: 150, align: 'center' },
{ title: '状态', key: 'state', width: 70, align: 'center' },
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
align: 'center',
customRender: (row: any) => {
return formatTime(row);
}
},
{ title: '操作', key: '操作', fixed: 'right', width: 100, align: 'center' },
];
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getErrandList(1);
});
/**
* 获取快送员数据
*/
const getErrandList = async (current: number) => {
searchParams.value.current = current;
loading.value = true;
const res: any = await myAxios.post("/errand/page", { ...searchParams.value });
if (res.code === 0 && res.data) {
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
};
/**
* 操作
*/
const doBan = async (id: string, isBan: boolean) => {
const res: any = await myAxios.post("/errand/update", { id: id, state: isBan ? 2 : 1 });
if (res.code === 0 && res.data) {
message.success(`操作成功`);
await getErrandList(1);
} else {
message.error(`封禁商家失败:${res.message}`);
}
};
/**
* 搜索功能
*/
const onSearch = (errandName: string) => {
searchParams.value.errandName = errandName;
searchParams.value.current = 1;
getErrandList(1);
};
/**
* 搜索功能
*/
const onSearchPhone = (errandPhone: string) => {
searchParams.value.errandPhone = errandPhone;
searchParams.value.current = 1;
getErrandList(1);
};
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getErrandList(pageNumber);
};
/**
* 跳转到快送员信息
*/
const toInfoPage = (id: string) => {
console.log(id); // 打印传递的ID值
router.push({
name: '快送员审核',
// path: '/errandInfo',
query: {
id: String(id) // 确保ID值是字符串类型
}
});
};
/**
* 当状态选择器值改变时触发
*/
const handleChange = () => {
getErrandList(1);
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,371 @@
<template>
<a-card>
<a-button style="margin-bottom: 30px" type="primary" @click="back">返回</a-button>
快送员订单
<div class="data-box">
<div style="display: flex;justify-content: space-around">
<a-descriptions :column=3 style="width: 85%">
<a-descriptions-item label="快送员姓名">{{ errandData?.errandName }}</a-descriptions-item>
<a-descriptions-item label="配送单位">{{ errandData?.distributionScope }}</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ errandData?.errandPhone }}</a-descriptions-item>
<a-descriptions-item label="银行卡号">{{ errandData?.bankCard }}</a-descriptions-item>
<a-descriptions-item label="当前接单量">{{ errandData?.totalOrders }}</a-descriptions-item>
<a-descriptions-item label="最大送单量">{{ errandData?.maxOrders }}</a-descriptions-item>
</a-descriptions>
<div style="width: 15%;display: flex;white-space: nowrap">
图片
<div style="width: 45%">
<a-image :src="errandData.errandAvatarUrl"/>
</div>
</div>
</div>
</div>
<div class="search-box" style="margin-top: 2%;display: inline-block;width: 100%">
<div style="display: flex;justify-content: space-between;margin-bottom: 2%">
<!-- 查询信息 -->
<a-form layout="inline">
<a-space>
<a-input-search
style="width: 400px"
placeholder="请输入订单号"
enter-button
allow-clear
@search="onSearch"
/>
<a-select
v-model:value="searchParams.state"
style="width: 120px"
@change="getOrdersList(1)"
>
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未支付</a-select-option>
<a-select-option value="1">已支付</a-select-option>
<a-select-option value="2">已退款</a-select-option>
<a-select-option value="3">已取消</a-select-option>
<a-select-option value="4">已出餐</a-select-option>
<a-select-option value="5">已完成</a-select-option>
</a-select>
</a-space>
</a-form>
<a-space>
<!-- 导出信息 -->
<a-button type="primary" @click="download">EXCEL导出</a-button>
<!--筛选时间-->
<a-range-picker @change="searchTime" format="YYYY-MM-DD"/>
</a-space>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 800, y: 1000 }"
:data-source="tableData"
:columns="columns"
:rowKey="(record: any) => record.id"
:pagination=false
:loading="loading"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="purple">未支付</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="blue">已支付</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">已退款</a-tag>
</div>
<div v-else-if="record.state === 3">
<a-tag color="gray">已取消</a-tag>
</div>
<div v-else-if="record.state === 4">
<a-tag color="blue">已出餐</a-tag>
</div>
<div v-else-if="record.state === 5">
<a-tag color="green">已完成</a-tag>
</div>
</template>
<template v-if="column.key === 'totalPrice'">
<a-tag color="purple">{{ record.totalPrice }}</a-tag>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="router.push({path:'/orderDetail',query:{id:record.id}})">查看
</a-button>
<a-button v-if="[1, 4, 5].includes(record.state)"
size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="modalOpen = true; recordId=record.orderNo">退款
</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
<!--订单金额信息 -->
<div style="margin: 20px 0 20px 0;float: left;">
<span class="ant-pagination-total-text">总金额{{ ordersCount }}</span>
</div>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-size-changer
show-quick-jumper
:total="total"
:show-total="() => `${total} 条订单`"
@change="onChange"
@showSizeChange="onShowSizeChange"
/>
</div>
<!--退款弹出框-->
<a-modal v-model:open="modalOpen" title="退款" destroyOnClose @ok="refund()">
<span>确定退款吗</span>
</a-modal>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import {useRoute} from "vue-router";
import {formatTime} from "../../utils/TableConfig";
import router from "../../router";
import myAxios from "../../api/myAxios.ts";
const route = useRoute();
//订单信息
const tableData: any = ref([])
//店铺信息
const errandData: any = ref({})
//订单总金额
const ordersCount: any = ref<Number>()
//加载按钮
const loading = ref(false)
//弹出框开关
const modalOpen = ref<boolean>(false);
//退款id
const recordId = ref<Number>();
// 分页
const total: any = ref(0);
//查询条件
const searchParams: any = ref({
current: 1,
pageSize: 10,
orderId: null,
searchTime: null,
state: ''
})
const columns: any = [
{title: '订单号', dataIndex: 'id', key: 'id', width: 100, fixed: 'left', align: 'center'},
{
title: '下单时间', dataIndex: 'createTime', key: 'createTime', width: 100, align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '卖家昵称', dataIndex: 'userName', key: 'userName', width: 150, align: 'center'},
{title: '支付状态', dataIndex: 'state', key: 'state', width: 150, align: 'center'},
{title: '订单金额', dataIndex: 'totalPrice', key: 'totalPrice', width: 150, align: 'center'},
{title: '操作', key: '操作', fixed: 'right', width: 80, align: 'center'},
];
onMounted(() => {
getOrdersList(1)
getBusinessVO()
getOrdersCount()
})
/**
* 获取订单信息列表
*/
console.log(route.query.id)
const getOrdersList = async (current: any) => {
let errandId = route.query.id; //商家id
loading.value = true;
const res: any = await myAxios.post('/orders/list/page', {
errandId: errandId,
businessId: "",
current: current,
pageSize: searchParams.value.pageSize,
id: searchParams.value.orderId,
sortField: "createTime",
sortOrder: "descend",
state: searchParams.value.state,
startTime: searchParams.value.searchTime == null ? '' : searchParams.value.searchTime[0],
endTime: searchParams.value.searchTime == null ? '' : searchParams.value.searchTime[1]
})
if (res.code === 0 && res.data) {
searchParams.value.current = current
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
console.log("没有获取订单")
console.log(res.data)
message.error(`获取数据失败:${res.description}`);
back()
}
}
/**
* 获取店铺信息
*/
const getBusinessVO = async () => {
let errandId:any = route.query.id;
if (!errandId) {
console.error("商家ID未定义或为空");
return;
}
const formData = new URLSearchParams();
formData.append('errandId1', errandId);
try {
const res:any = await myAxios.post('/errand/get/id', formData.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (res.code === 0 && res.data) {
console.log(res)
errandData.value = res.data;
} else {
console.log(res)
message.error(`获取数据失败:${res.description}`);
back();
}
} catch (error) {
console.error('请求异常:', error);
message.error('接口请求异常,请检查控制台');
}
}
/**
* 获取该店铺收入
*/
const getOrdersCount = async () => {
let errandId = route.query.id; //商家id
const res: any = await myAxios.post('/orders/count', {
"errandId": errandId,
"type": "money",
"state": 5
})
if (res.code === 0 && res.data) {
console.log(res)
ordersCount.value = res.data;
} else {
message.error(`获取数据失败:${res.description}`);
back()
}
}
/**
* 导出Excel表
*/
const download = async () => {
let errandId = route.query.id //商家id
const res: any = await myAxios.post('/orders/download/errand', {
errandId: errandId,
// 添加时间参数(与列表查询保持一致)
startTime: searchParams.value.searchTime?.[0] || '',
endTime: searchParams.value.searchTime?.[1] || '',
// 保留其他必要参数
current: searchParams.value.current,
pageSize: searchParams.value.pageSize,
sortField: "createTime",
sortOrder: "descend"
},
{
responseType: "blob"
}
)
downloadCallback(res, '快送员订单信息表.xlsx')
}
/**
* 退款
*/
// const refund = async () => {
// const res: any = await myAxios.get('/Alipay/test/refund', {orderNo: recordId.value})
// if (res.code === 0 && res.data) {
// message.success('退款成功');
// modalOpen.value = false
// await getOrdersList(1)
// await getOrdersCount()
// } else {
// message.error(`退款失败:${res.description}`);
// }
// }
const refund = async () => {
const res: any = await myAxios.get(`/Alipay/test/refund?orderNo=${recordId.value}`);
if (res.code === 0 && res.data) {
message.success('退款成功');
modalOpen.value = false;
await getOrdersList(1);
await getOrdersCount();
} else {
message.error(`退款失败:${res.description}`);
}
}
/**
* 查询
*/
const onSearch = async (ordersId: number) => {
searchParams.value.orderId = ordersId
await getOrdersList(1)
}
/**
* 查询时间
*/
const searchTime = async (dateTime: any) => {
searchParams.value.searchTime = dateTime
await getOrdersList(1)
}
/**
* 分页
*/
const onShowSizeChange = (current: number, pageSize: number) => {
searchParams.value.pageSize = pageSize;
getOrdersList(current);
}
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getOrdersList(pageNumber);
}
/**
* 返回
*/
const back = () => {
router.back()
}
//生成下载文件
const downloadCallback = (res: any, fileName: any) => {
const blob = new Blob([res]);
if ("download" in document.createElement("a")) {
// 非IE下载
const elink = document.createElement("a");
elink.download = fileName;
elink.style.display = "none";
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
document.body.removeChild(elink);
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,177 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space warp>
<a-form-item
label="快送员名"
>
<a-input allow-clear @change="change" @pressEnter="getBusinessList(1)"
v-model:value="searchParams.errandName"
placeholder="请输入快送员名"/>
</a-form-item>
<a-form-item label="配送范围">
<a-select
style="width: 200px;"
placeholder="默认查询全部快送员"
allowClear
v-model:value="searchParams.distributionScope"
@change="handleChange"
>
<a-select-option value='1公寓'>1公寓</a-select-option>
<a-select-option value='2公寓'>2公寓</a-select-option>
<a-select-option value='3公寓'>3公寓</a-select-option>
<a-select-option value='4公寓'>4公寓</a-select-option>
<a-select-option value='5公寓'>5公寓</a-select-option>
<a-select-option value='6公寓'>6公寓</a-select-option>
<a-select-option value='7公寓'>7公寓</a-select-option>
<a-select-option value='8公寓'>8公寓</a-select-option>
<a-select-option value='9公寓'>9公寓</a-select-option>
<a-select-option value='10公寓'>10公寓</a-select-option>
<a-select-option value='11公寓'>11公寓</a-select-option>
<a-select-option value='12公寓'>12公寓</a-select-option>
<a-select-option value='育才大厦'>育才大厦</a-select-option>
</a-select>
</a-form-item>
<a-button type="primary" @click="getBusinessList(1)">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1000, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination=false
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar :src="record.errandAvatarUrl"/>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="router.push({path: '/errandOrderList',query: {id: record.id}})">查看
</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total} 位快送员`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import myAxios from "../../api/myAxios";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import router from "../../router";
//表单数据
const tableData: any = ref([]);
//加载按钮
const loading = ref(false)
// 分页
const total: any = ref(0);
//查询条件
const searchParams: any = ref({
current: 1,
pageSize: 10,
errandName: '',
distributionScope: null
})
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
customRender: ({index}: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{title: '图片', dataIndex: 'businessAvatar', key: 'businessAvatar', width: 50, align: 'center'},
{title: '快送员名', dataIndex: 'errandName', key: 'errandName', width: 50, align: 'center'},
{title: '当前接单量', dataIndex: 'totalOrders', key: 'totalOrders', width: 50, align: 'center'},
{title: '最大接单量', dataIndex: 'maxOrders', key: 'maxOrders', width: 50, align: 'center'},
{title: '配送范围', dataIndex: 'distributionScope', key: 'distributionScope', width: 50, align: 'center'},
{title: '操作', key: '操作', fixed: 'right', width: 50, align: 'center'},
];
onMounted(() => {
getBusinessList(1)
})
/**
* 获取商家信息列表
*/
const getBusinessList = async (current: number) => {
searchParams.value.current = current
loading.value = true;
const res: any = await myAxios.post('/errand/page', {...searchParams.value})
console.log(res)
if (res.code === 0 && res.data) {
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 选择用户身份
*/
const handleChange = (distributionScope: string) => {
searchParams.value.distributionScope = distributionScope;
getBusinessList(1)
};
/**
* 重置查询条件
*/
const reset = () => {
searchParams.value.errandName = ''
searchParams.value.distributionScope = null
getBusinessList(1)
}
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getBusinessList(pageNumber);
}
/**
* 清除输入框的回调
*/
const change = (row: any) => {
if (row.type === 'click') getBusinessList(1)
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,169 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="手机号">
<a-input-search
style="width: 300px"
placeholder="请输入手机号"
enter-button
allow-clear
@search="onSearchByPhone"
/>
</a-form-item>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination=false
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar v-if="record.business!=null" :src="record?.business.businessAvatar"/>
<div v-else>
<a-tag color="blue">Boss账号</a-tag>
</div>
</template>
<template v-if="column.key === 'businessName'">
<div v-if="record.business!=null">{{ record?.business.businessName }}</div>
<div v-else>
<a-tag color="blue">Boss账 </a-tag>
</div>
</template>
<template v-if="column.key === 'businessPhone'">
<div v-if="record.business!=null">{{ record?.business.businessPhone }}</div>
<div v-else>
<a-tag color="blue">Boss账号</a-tag>
</div>
</template>
<template v-if="column.key === 'content'">
<a-tag :color="getTagColor(record.content)">{{ record.content }}</a-tag>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total} 条数据`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {formatTime} from "../../utils/TableConfig";
import myAxios from "../../api/myAxios";
import {message} from "ant-design-vue";
const getTagColor = (content: string) => {
const blueOperations = ['修改菜品', '修改菜品分组'];
const redOperations = [ '删除菜品', '删除菜品分组'];
const greenOperations = ['添加菜品', '添加菜品分组'];
if (blueOperations.includes(content)) return 'blue';
if (greenOperations.includes(content)) return 'green';
if (redOperations.includes(content)) return 'red';
return 'red'; // 其他操作保持红色
};
const tableData: any = ref([]);
const loading = ref(false)
// 分页
const total: any = ref(0);
// 请求参数
const searchParams: any = ref({
sortField: "createTime",
sortOrder: "descend",
current: 1,
pageSize: 10,
businessPhone: '',
startTime: '',
endTime: ''
})
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
customRender: ({index}: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{title: '门店名称', key: 'businessName', width: 100, align: 'center'},
{title: '门店头像', key: 'businessAvatar', width: 50, align: 'center'},
{title: '手机号', key: 'businessPhone', width: 150, align: 'center'},
{title: '执行操作', key: 'content', width: 150, align: 'center'},
{title: 'ip地址', dataIndex: 'ip', key: 'ip', width: 150, align: 'center'},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
];
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getBusinessLogList();
})
/**
* 获取商家操作日志
*/
const getBusinessLogList = async () => {
loading.value = true;
const res: any = await myAxios.post("/log/business/logs", {...searchParams.value})
if (res.code === 0 && res.data) {
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 根据手机号搜索功能
*/
const onSearchByPhone = (businessPhone: number) => {
searchParams.value.businessPhone = businessPhone;
searchParams.value.current = 1;
getBusinessLogList();
}
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getBusinessLogList();
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,155 @@
<template>
<a-card title="商家信息" :loading="loading">
<div class="main-card" >
<a-descriptions size="default" bordered class="txt">
<a-descriptions-item label="头像">
<template v-slot>
<a-avatar :src="businessInfo.businessAvatar" style="width: 100px;height: 100px;"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="店铺名称">{{ businessInfo?.businessName }}</a-descriptions-item>
<a-descriptions-item label="店主姓名">{{ businessInfo?.shopkeeper }}</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ businessInfo?.businessPhone }}</a-descriptions-item>
<a-descriptions-item label="店铺地址">{{ businessInfo.address }}</a-descriptions-item>
<a-descriptions-item label="银行卡号">{{ businessInfo?.bankCard }}</a-descriptions-item>
<a-descriptions-item label="营业执照">
<template v-slot>
<a-image :src="businessInfo.license" style="width: 158.5px; height: 100px;"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="身份证正面">
<template v-slot>
<a-image :src="businessInfo.frontIdCard" style="width: 158.5px; height: 100px;"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="身份证反面">
<template v-slot>
<a-image :src="businessInfo.backIdCard" style="width: 158.5px; height: 100px"/>
</template>
</a-descriptions-item>
<a-descriptions-item label="当前状态">
<div v-if="businessInfo.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="businessInfo.state === 1">
<a-tag color="green">正常营业</a-tag>
</div>
<div v-else-if="businessInfo.state === 2">
<a-tag color="red">暂定营业</a-tag>
</div>
</a-descriptions-item>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar :src="record.businessAvatar"/>
</template>
<template v-else-if="column.key === 'license'">
<a-image :src="record.license" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-else-if="column.key === 'frontIdCard'">
<a-image :src="record.frontIdCard" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-else-if="column.key === 'backIdCard'">
<a-image :src="record.backIdCard" style="width: 100px;height: 100px;"></a-image>
</template>
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="green">已通过</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">未通过</a-tag>
</div>
</template>
</template>
</a-descriptions>
</div>
<a-space>
<div class="button-box">
<a-button
class="button-s"
size="small"
type="default"
style="color: blue"
@click="backPage">返回
</a-button>
<a-button
class="button-s"
size="small" v-if="state === 0"
type="primary"
style="color: white; margin-left: 15px"
danger
@click="update(2)">未通过
</a-button>
<a-button
class="button-s"
size="small" v-if="state === 0"
type="primary"
style="color: white; margin-left: 15px"
@click="update(1)">通过
</a-button>
</div>
</a-space>
</a-card>
<!-- 菜品列表展示区 -->
<a-divider />
</template>
<script setup lang="ts">
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import myAxios from "../../../api/myAxios";
import {backPage} from "../../../utils/TableConfig";
const state = ref(0)
const route = useRoute();
const loading = ref(false);
const businessInfo: any = ref({});
const businessId = route.query.businessId; //商家id
onMounted(() => {
getBusinessVO();
// listDishGroup();
})
const getBusinessVO = async () => {
loading.value = true;
const res: any = await myAxios.get('/business/getById', {params: {"id": businessId}})
console.log(res)
if (res.code === 0 && res.data) {
businessInfo.value = res.data;
state.value = res.data.state
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
backPage()
}
}
const update = async (state: number) => {
const res: any = await myAxios.post("/business/update", {id: businessId, state: state})
if (res.code === 0) {
message.success("已审核")
} else {
message.error(`审核失败:${res.description}`);
}
backPage()
}
</script>
<style scoped>
.button-box {
margin: 100px 0 500px 1100px;
display: flex;
justify-content: space-around;
}
.button-s {
flex: 1;
}
</style>

View File

@ -0,0 +1,203 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="门店名称">
<a-input-search
style="width: 300px"
placeholder="请输入门店名称"
enter-button
allow-clear
@search="onSearch"
/>
</a-form-item>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination=false
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar :src="record.businessAvatar"/>
</template>
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="orange">未审核</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="green">正常营业</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">封禁</a-tag>
</div>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button v-if="record.state === 0" size="small" type="link" @click="toInfoPage(record.id)">审核</a-button>
<a-button v-else size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="toInfoPage(record.id)">查看
</a-button>
<a-button :disabled="record.state === 0" size="small" type="link" danger
@click="doBan(record.id, record.state === 1)">
{{ record.state === 1 ? '禁用' : '启用' }}
</a-button>
<a-button type="link"
style="color: rgba(71, 214, 213);"
@click="toDishGroupPage(record.id)">查看菜品
</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total} 家店铺`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import myAxios from "../../../api/myAxios";
import {formatTime} from "../../../utils/TableConfig";
import {message} from "ant-design-vue";
import router from "../../../router";
const tableData: any = ref([]);
const loading = ref(false);
// 分页
const total: any = ref(0);
// 请求参数
const searchParams: any = ref({
sortField: "createTime",
sortOrder: "descend",
current: 1,
pageSize: 10,
businessName: '',
})
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
customRender: ({index}: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{title: '门店名称', dataIndex: 'businessName', width: 100, fixed: 'left', align: 'center'},
{title: '门店头像', key: 'businessAvatar', width: 50, align: 'center'},
{title: '手机号', dataIndex: 'businessPhone', width: 150, align: 'center'},
{title: '状态', key: 'state', width: 70, align: 'center'},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 150,
align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '操作', key: '操作', fixed: 'right', width: 100, align: 'center'},
];
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getBusinessList(1);
})
/**
* 获取商家数据
*/
const getBusinessList = async (current: number) => {
searchParams.value.current = current
loading.value = true;
const res: any = await myAxios.post("/business/list/page", {...searchParams.value})
if (res.code === 0 && res.data) {
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 操作
*/
const doBan = async (id: string, isBan: boolean) => {
const res: any = await myAxios.post("/business/update", {id: id, state: isBan ? 2 : 1})
if (res.code === 0 && res.data) {
message.success(`操作成功`);
await getBusinessList(1);
} else {
message.error(`封禁商家失败:${res.message}`);
}
}
/**
* 搜索功能
*/
const onSearch = (businessName: string) => {
searchParams.value.businessName = businessName;
searchParams.value.current = 1;
getBusinessList(1);
}
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getBusinessList(pageNumber);
}
/**
* 跳转到商家信息
*/
const toInfoPage = (businessId: any) => {
router.push({
name: '门店详情',
query: {
businessId: businessId
}
})
}
const toDishGroupPage = (businessId: any) => {
router.push({
name: '菜品分组详情',
query: {
businessId: businessId
}
})
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,123 @@
<script setup lang="ts">
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import myAxios from "../../../api/myAxios";
import {backPage} from "../../../utils/TableConfig";
import dayjs from "dayjs";
const route = useRoute();
const businessId = route.query.businessId;
const dishGroupId = route.query.dishGroupId;
const dishGroup = ref([]);
// const loading = ref(false);
const searchParams: any = ref({
businessId: businessId,
current: 1,
dishesGroupId: dishGroupId,
dishesName: "",
pageSize: 10,
sortField: "",
sortOrder: "",
status: ""
})
const getDishGroup = async () => {
console.log(businessId);
try {
// loading.value = true;
const res: any = await myAxios.post('/dishes/list/dishes', {
...searchParams.value
})
if (res.code === 0 && res.data) {
console.log(res)
dishGroup.value = res.data
} else {
message.error(res.description)
backPage()
}
}
catch (error) {
console.error(error);
message.error("请求失败,请检查网络或后端服务");
backPage();
}
}
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'id',
},
{
title: '菜品名称',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'dishesName',
},
{
title: '菜品图片',
width: 60,
fixed: 'left',
align: 'center',
key: 'dishesImage',
},
{
title: '菜品价格',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'dishesPrice',
},
{
title: '菜品打包费',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'packPrice',
},
{
title: '创建时间',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'createTime',
customRender: ({ text }: { text: string }) => text ? dayjs(text).format('YYYY-MM-DD') : '-',
}
];
onMounted(() => {
getDishGroup();
});
</script>
<template>
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="dishGroup"
:columns="columns"
:pagination=false
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'dishesImage'">
<a-avatar :src="record.dishesImage"></a-avatar>
</template>
</template>
</a-table>
<a-button
class="button-s"
size="small"
type="default"
style="color: blue"
@click="backPage">返回
</a-button>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,109 @@
<script setup lang="ts">
import {useRoute} from "vue-router";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import myAxios from "../../../api/myAxios";
import {backPage} from "../../../utils/TableConfig";
import dayjs from "dayjs";
import router from "../../../router";
const route = useRoute();
const businessId = route.query.businessId;
const dishGroup = ref([]);
// const loading = ref(false);
const getDishGroup = async () => {
console.log(businessId);
try {
// loading.value = true;
const res: any = await myAxios.post('/dishesGroup/list/dishesGroup', {
businessId: businessId,
})
if (res.code === 0 && res.data) {
console.log(res)
dishGroup.value = res.data
} else {
message.error(res.description)
backPage()
}
}
catch (error) {
console.error(error);
message.error("请求失败,请检查网络或后端服务");
backPage();
}
}
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'id',
},
{
title: '菜品分组名称',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'groupName',
},
{
title: '创建时间',
width: 60,
fixed: 'left',
align: 'center',
dataIndex: 'createTime',
customRender: ({ text }: { text: string }) => text ? dayjs(text).format('YYYY-MM-DD') : '-',
},
{title: '操作', key: '操作', fixed: 'right', width: 100, align: 'center'},
];
const toDishPage = (businessId: any, dishGroupId:any) => {
router.push({
name: '菜品详情',
query: {
businessId: businessId,
dishGroupId: dishGroupId
}
})
}
onMounted(() => {
getDishGroup();
});
</script>
<template>
<a-card>
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="dishGroup"
:columns="columns"
:pagination="false"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === '操作'">
<a-button
type="link"
style="color: rgba(71, 214, 213);"
@click="toDishPage(businessId, record.id)">查看菜品
</a-button>
</template>
</template>
</a-table>
<a-button
class="button-s"
size="small"
type="default"
style="color: blue"
@click="backPage">返回
</a-button>
</a-card>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,102 @@
<template>
<a-card>
<!--订单详情-->
<a-descriptions :column=2>
<a-descriptions-item>
<div style="display: flex;align-items: center">
<span>商家信息</span>
<a-avatar :src="businessData.businessAvatar"/>
<span>{{ businessData?.businessName }}</span>
</div>
</a-descriptions-item>
<a-descriptions-item label="取餐码">
<a-tag color="processing" v-if="orderData?.pickupCode">{{ orderData?.pickupCode }}</a-tag>
<a-tag color="red" v-else>暂未支付</a-tag>
</a-descriptions-item>
<a-descriptions-item label="支付方式">
<div v-if="orderData.payMethod === 0">微信支付</div>
</a-descriptions-item>
<a-descriptions-item label="用户电话">{{ orderData?.phone }}</a-descriptions-item>
<a-descriptions-item label="支付金额">{{ orderData?.totalPrice }}</a-descriptions-item>
<a-descriptions-item label="订单编号">{{ orderData.id }}</a-descriptions-item>
<a-descriptions-item label="下单时间">{{ orderData?.createTime }}</a-descriptions-item>
<a-descriptions-item label="支付时间">{{ orderData?.updateTime }}</a-descriptions-item>
<a-descriptions-item label="订单备注">{{ orderData?.notes }}</a-descriptions-item>
</a-descriptions>
<a-list :loading="loading" size="default" bordered :data-source="orderDetail">
<template #header>
<div>订单列表</div>
</template>
<template #renderItem="{ item }">
<a-list-item style="display: flex">
<a-list-item-meta :description="item.attributeNames === '' ? '未选择规格' : item.attributeNames">
<template #title>
<div>{{ item.dishesVO.dishesName }}</div>
</template>
<template #avatar>
<a-image style="width: 4rem" :src="item.dishesVO.dishesImage"/>
</template>
</a-list-item-meta>
<a-list-item-meta style="display: flex; align-items: end">
<template #title>
<div style="float: right;margin-left: 10%">{{ item.subtotal }}</div>
<div style="float: right">×{{ item.quantity }}</div>
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
<a-button style="margin-top: 30px" type="primary" @click="back">返回</a-button>
</a-card>
</template>
<script lang="ts" setup>
import myAxios from "../../../api/myAxios";
import {onMounted, ref} from "vue";
import {useRoute} from "vue-router";
import {message} from "ant-design-vue";
import router from "../../../router";
const route = useRoute();
//加载按钮
const loading = ref(false);
//订单信息
const orderData: any = ref({});
//商家信息
const businessData: any = ref({});
//订单详情
const orderDetail: any = ref([]);
onMounted(() => {
getOrderDetail()
})
const getOrderDetail = async () => {
loading.value = true;
const res: any = await myAxios.get('/orders/get', {
params: {id: route.query.id}
})
if (res.code === 0 && res.data)
{
console.log(res)
loading.value = false;
orderData.value = res.data
businessData.value = res.data.businessVO
orderDetail.value = res.data.orderDetailsVOList
orderData.value.createTime = orderData.value.createTime.slice(0, 16).replace('T', '~')
orderData.value.updateTime = orderData.value.updateTime.slice(0, 16).replace('T', '~')
} else {
message.error(`获取数据失败:${res.message}`);
back()
}
}
/**
* 返回
*/
const back = () => {
router.back()
}
</script>
<style scoped></style>

View File

@ -0,0 +1,349 @@
<template>
<a-card>
<a-button style="margin-bottom: 30px" type="primary" @click="back">返回</a-button>
店铺信息
<div class="data-box">
<div style="display: flex;justify-content: space-around">
<a-descriptions :column=3 style="width: 85%">
<a-descriptions-item label="店铺名">{{ businessData?.businessName }}</a-descriptions-item>
<a-descriptions-item label="店主姓名">{{ businessData?.shopkeeper }}</a-descriptions-item>
<a-descriptions-item label="店铺地">{{ businessData.address }}</a-descriptions-item>
<a-descriptions-item label="联系方式">{{ businessData?.businessPhone }}</a-descriptions-item>
<a-descriptions-item label="银行卡号">{{ businessData?.bankCard }}</a-descriptions-item>
</a-descriptions>
<div style="width: 15%;display: flex;white-space: nowrap">
图片
<div style="width: 45%">
<a-image :src="businessData.businessAvatar"/>
</div>
</div>
</div>
</div>
<div class="search-box" style="margin-top: 2%;display: inline-block;width: 100%">
<div style="display: flex;justify-content: space-between;margin-bottom: 2%">
<!-- 查询信息 -->
<a-form layout="inline">
<a-space>
<a-input-search
style="width: 400px"
placeholder="请输入订单号"
enter-button
allow-clear
@search="onSearch"
/>
<a-select
v-model:value="searchParams.state"
style="width: 120px"
@change="getOrdersList(1)"
>
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未支付</a-select-option>
<a-select-option value="1">已支付</a-select-option>
<a-select-option value="2">已退款</a-select-option>
<a-select-option value="3">已取消</a-select-option>
<a-select-option value="4">已出餐</a-select-option>
<a-select-option value="5">已完成</a-select-option>
</a-select>
</a-space>
</a-form>
<a-space>
<!-- 导出信息 -->
<a-button type="primary" @click="download">EXCEL导出</a-button>
<!--筛选时间-->
<a-range-picker @change="searchTime" format="YYYY-MM-DD"/>
</a-space>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 800, y: 1000 }"
:data-source="tableData"
:columns="columns"
:rowKey="(record: any) => record.id"
:pagination=false
:loading="loading"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="purple">未支付</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="blue">已支付</a-tag>
</div>
<div v-else-if="record.state === 2">
<a-tag color="red">已退款</a-tag>
</div>
<div v-else-if="record.state === 3">
<a-tag color="gray">已取消</a-tag>
</div>
<div v-else-if="record.state === 4">
<a-tag color="blue">已出餐</a-tag>
</div>
<div v-else-if="record.state === 5">
<a-tag color="green">已完成</a-tag>
</div>
</template>
<template v-if="column.key === 'totalPrice'">
<a-tag color="purple">{{ record.totalPrice }}</a-tag>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="router.push({path:'/orderDetail',query:{id:record.id}})">查看
</a-button>
<a-button v-if="record.state === 1"
size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="modalOpen = true; recordId=record.orderNo">退款
</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
<!--订单金额信息 -->
<div style="margin: 20px 0 20px 0;float: left;">
<span class="ant-pagination-total-text">总金额{{ ordersCount }}</span>
</div>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-size-changer
show-quick-jumper
:total="total"
:show-total="() => `${total} 条订单`"
@change="onChange"
@showSizeChange="onShowSizeChange"
/>
</div>
<!--退款弹出框-->
<a-modal v-model:open="modalOpen" title="退款" destroyOnClose @ok="refund()">
<span>确定退款吗</span>
</a-modal>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import {useRoute} from "vue-router";
import {formatTime} from "../../../utils/TableConfig";
import router from "../../../router";
import myAxios from "../../../api/myAxios.ts";
const route = useRoute();
//订单信息
const tableData: any = ref([])
//店铺信息
const businessData: any = ref({})
//订单总金额
const ordersCount: any = ref<Number>()
//加载按钮
const loading = ref(false)
//弹出框开关
const modalOpen = ref<boolean>(false);
//退款id
const recordId = ref<Number>();
// 分页
const total: any = ref(0);
//查询条件
const searchParams: any = ref({
current: 1,
pageSize: 10,
orderId: null,
searchTime: null,
state: ''
})
const columns: any = [
{title: '订单号', dataIndex: 'id', key: 'id', width: 100, fixed: 'left', align: 'center'},
{
title: '下单时间', dataIndex: 'createTime', key: 'createTime', width: 100, align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '卖家昵称', dataIndex: 'userName', key: 'userName', width: 150, align: 'center'},
{title: '支付状态', dataIndex: 'state', key: 'state', width: 150, align: 'center'},
{title: '订单金额', dataIndex: 'totalPrice', key: 'totalPrice', width: 150, align: 'center'},
{title: '操作', key: '操作', fixed: 'right', width: 80, align: 'center'},
];
onMounted(() => {
getOrdersList(1)
getBusinessVO()
getOrdersCount()
})
/**
* 获取订单信息列表
*/
const getOrdersList = async (current: any) => {
let businessId = route.query.id; //商家id
loading.value = true;
const res: any = await myAxios.post('/orders/list/page', {
businessId: businessId,
errandId: "",
current: current,
pageSize: searchParams.value.pageSize,
id: searchParams.value.orderId,
sortField: "createTime",
sortOrder: "descend",
state: searchParams.value.state,
startTime: searchParams.value.searchTime == null ? '' : searchParams.value.searchTime[0],
endTime: searchParams.value.searchTime == null ? '' : searchParams.value.searchTime[1]
})
if (res.code === 0 && res.data) {
searchParams.value.current = current
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
back()
}
}
/**
* 获取店铺信息
*/
const getBusinessVO = async () => {
let businessId = route.query.id; //商家id
const res: any = await myAxios.get('/business/getById', {params: {"id": businessId}})
if (res.code === 0 && res.data) {
console.log(res)
businessData.value = res.data;
} else {
message.error(`获取数据失败:${res.message}`);
console.log(res)
back()
}
}
/**
* 获取该店铺收入
*/
const getOrdersCount = async () => {
let businessId = route.query.id; //商家id
const res: any = await myAxios.post('/orders/count', {
"businessId": businessId,
"type": "money",
"state": 5
})
if (res.code === 0 && res.data) {
ordersCount.value = res.data;
} else {
message.error(`获取数据失败:${res.message}`);
back()
}
}
/**
* 导出Excel表
*/
const download = async () => {
let businessId = route.query.id //商家id
const res: any = await myAxios.post('/orders/download', {
businessId: businessId,
startTime: searchParams.value.searchTime?.[0] || '',
endTime: searchParams.value.searchTime?.[1] || '',
current: searchParams.value.current,
pageSize: searchParams.value.pageSize,
sortField: "createTime",
sortOrder: "descend"
},
{
responseType: "blob"
}
)
console.log(res)
downloadCallback(res, '商家订单信息表.xlsx')
}
/**
* 退款
*/
// const refund = async () => {
// const res: any = await myAxios.get('/Alipay/test/refund', {orderNo: recordId.value})
// if (res.code === 0 && res.data) {
// message.success('退款成功');
// modalOpen.value = false
// await getOrdersList(1)
// await getOrdersCount()
// } else {
// message.error(`退款失败:${res.message}`);
// }
// }
const refund = async () => {
const res: any = await myAxios.get(`/Alipay/test/refund?orderNo=${recordId.value}`);
if (res.code === 0 && res.data) {
message.success('退款成功');
modalOpen.value = false;
await getOrdersList(1);
await getOrdersCount();
} else {
message.error(`退款失败:${res.message}`);
}
}
/**
* 查询
*/
const onSearch = async (ordersId: number) => {
searchParams.value.orderId = ordersId
await getOrdersList(1)
}
/**
* 查询时间
*/
const searchTime = async (dateTime: any) => {
searchParams.value.searchTime = dateTime
await getOrdersList(1)
}
/**
* 分页
*/
const onShowSizeChange = (current: number, pageSize: number) => {
searchParams.value.pageSize = pageSize;
getOrdersList(current);
}
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getOrdersList(pageNumber);
}
/**
* 返回
*/
const back = () => {
router.back()
}
//生成下载文件
const downloadCallback = (res: any, fileName: any) => {
const blob = new Blob([res]);
if ("download" in document.createElement("a")) {
// 非IE下载
const elink = document.createElement("a");
elink.download = fileName;
elink.style.display = "none";
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
document.body.removeChild(elink);
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,179 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space warp>
<a-form-item
label="店铺名"
>
<a-input allow-clear @change="change" @pressEnter="getBusinessList(1)"
v-model:value="searchParams.businessName"
placeholder="请输入店铺名"/>
</a-form-item>
<a-form-item
label="店铺地"
>
<a-input allow-clear @change="change" @pressEnter="getBusinessList(1)" v-model:value="searchParams.address"
placeholder="请输入店铺地"/>
</a-form-item>
<a-form-item label="状态">
<a-select
style="width: 200px;"
placeholder="默认查询全部商家"
allowClear
v-model:value="searchParams.state"
@change="handleChange"
>
<a-select-option value=0>休业</a-select-option>
<a-select-option value=1>营业</a-select-option>
</a-select>
</a-form-item>
<a-button type="primary" @click="getBusinessList(1)">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1000, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination=false
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'businessAvatar'">
<a-avatar :src="record.businessAvatar"/>
</template>
<template v-if="column.key === 'state'">
<div v-if="record.state === 0">
<a-tag color="purple">休业中</a-tag>
</div>
<div v-else-if="record.state === 1">
<a-tag color="blue">营业中</a-tag>
</div>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small"
type="link"
style="color: rgba(255, 141, 26, 1);"
@click="router.push({path: '/orderList',query: {id: record.id}})">查看
</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total} 家店铺`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import myAxios from "../../../api/myAxios";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
import router from "../../../router";
//表单数据
const tableData: any = ref([]);
//加载按钮
const loading = ref(false)
// 分页
const total: any = ref(0);
//查询条件
const searchParams: any = ref({
current: 1,
pageSize: 10,
businessName: '',
address: '',
state: null
})
const columns: any = [
{
title: '序号',
width: 60,
fixed: 'left',
align: 'center',
customRender: ({index}: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{title: '图片', dataIndex: 'businessAvatar', key: 'businessAvatar', width: 30, align: 'center'},
{title: '店铺名', dataIndex: 'businessName', key: 'businessName', width: 50, align: 'center'},
{title: '店铺地', dataIndex: 'address', key: 'address', width: 150, align: 'center'},
{title: '状态', dataIndex: 'state', key: 'state', width: 150, align: 'center'},
{title: '操作', key: '操作', fixed: 'right', width: 50, align: 'center'},
];
onMounted(() => {
getBusinessList(1)
})
/**
* 获取商家信息列表
*/
const getBusinessList = async (current: number) => {
searchParams.value.current = current
loading.value = true;
const res: any = await myAxios.post('/business/list/page', {...searchParams.value})
if (res.code === 0 && res.data) {
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 选择用户身份
*/
const handleChange = (state: string) => {
searchParams.value.state = state;
getBusinessList(1)
};
/**
* 重置查询条件
*/
const reset = () => {
searchParams.value.businessName = ''
searchParams.value.address = ''
searchParams.value.state = null
getBusinessList(1)
}
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getBusinessList(pageNumber);
}
/**
* 清除输入框的回调
*/
const change = (row: any) => {
if (row.type === 'click') getBusinessList(1)
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,3 @@
<template>
<div>管理员根据商家发布的公告进行查看管理</div>
</template>

View File

@ -0,0 +1,160 @@
<template>
<a-card>
<a-card :loading="loading">
<div style="display: flex;justify-content: right;margin-bottom: 1%">
<a-button type="primary" @click="showModal">增加</a-button>
</div>
<div style="display: flex;flex-wrap: wrap;">
<div v-for="item in carouselData" style="margin: 1.8%">
<a-card>
<template #cover>
<a-image :src="item?.content" style="width: 200px;height: 100px"/>
</template>
<template #actions>
<a-button type="primary" danger @click="deleteCarousel(item.id)">删除</a-button>
</template>
</a-card>
</div>
</div>
</a-card>
<div>
<a-modal v-model:open="modalOpen" :afterClose="onClose" keyboard title="新增轮播图" @ok="submit">
<a-upload
style="margin: 5%"
:max-count="1"
name="avatar"
:show-upload-list="false"
:customRequest="handleChange"
list-type="picture-card"
class="avatar-uploader"
>
<img v-if="imageUrl" :src="imageUrl" alt="example" style="width: 100%"/>
<div v-else>
<div class="ant-upload-text">+</div>
</div>
</a-upload>
</a-modal>
</div>
</a-card>
</template>
<script setup lang="ts">
import myAxios from "../../../api/myAxios.ts";
import {onMounted, ref} from "vue";
import {message} from "ant-design-vue";
//加载按钮
const loading = ref(false)
//轮播图信息
const carouselData: any = ref({})
//弹出框开关
const modalOpen = ref<boolean>(false);
//图片地址
const imageUrl = ref('')
onMounted(() => {
getCarousel()
})
/**
* 获取轮播图列表
*/
const getCarousel = async () => {
loading.value = true;
const res: any = await myAxios.post('/system/list', {
type: 1
})
console.log(res)
if (res.code === 0 && res.data) {
loading.value = false;
carouselData.value = res.data
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 删除轮播图
*/
const deleteCarousel = async (id: number) => {
const res: any = await myAxios.post('/system/delete', {
id: id
})
if (res.code !== 0) {
message.error(`删除失败:${res.message}`);
}
await getCarousel()
}
/**
* 打开弹出框
*/
const showModal = () => {
if (carouselData.value.length > 7) {
message.error(`添加失败,轮播图不能大于8张`);
return
}
modalOpen.value = true;
};
/**
* 关闭弹出框
*/
const onClose = () => {
imageUrl.value = ""
modalOpen.value = false;
};
/**
* 上传图片
*/
const handleChange = async (row: any) => {
let formData = new FormData()
formData.append("file", row.file)
const result = await myAxios({
url: '/file/upload/server',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
//biz: "user_avatar",
biz: "system",
// 需根据名字取出,否则为空
file: formData.get("file")
}
})
// 传出图片地址
if (result.data) {
imageUrl.value = result.data
} else {
message.error(`图片上传失败`);
}
}
/**
* 确认添加
*/
const submit = async () => {
if (imageUrl.value === '' || undefined) {
message.error(`请上传图片`);
return
}
const res: any = await myAxios.post('/system/add', {
type: 1,
content: imageUrl.value
})
if (res.code !== 0) {
message.error(`更新失败:${res.message}`);
}
await getCarousel()
modalOpen.value = false;
imageUrl.value = '';
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,193 @@
<template>
<a-card>
<div class="middle-button">
<a-button type="primary" @click="showDrawer">添加</a-button>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:rowKey="(record: any) => record.id"
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-avatar :src="record.image"/>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small" type="link" danger @click="handleDelete(record.id)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
<a-drawer
:destroyOnClose="true"
title="添加分类"
placement="right"
:closable="false"
:visible="visible"
@close="onClose"
>
<a-form :model="drawerData" :rules="rules" layout="vertical">
<a-form-item name="content">
<a-input v-model:value="drawerData.name" placeholder="请输入分类名称"/>
</a-form-item>
<a-form-item>
<a-upload
style="margin: 5%"
:max-count="1"
name="avatar"
:show-upload-list="false"
:customRequest="upload"
list-type="picture-card"
class="avatar-uploader"
>
<img v-if="drawerData.image" :src="drawerData.image" alt="example" style="width: 100%"/>
<div v-else>
<div class="ant-upload-text">+</div>
</div>
</a-upload>
</a-form-item>
</a-form>
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
<a-button type="primary" @click="onSubmit">确认</a-button>
</a-drawer>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {formatTime} from "../../utils/TableConfig";
import myAxios from "../../api/myAxios";
import {message} from "ant-design-vue";
const tableData: any = ref([]);
const loading = ref(false)
// 抽屉开关
const visible = ref<boolean>(false);
// 抽屉数据
const drawerData: any = ref({})
const columns: any = [
{title: '分类名', dataIndex: 'name', width: 30, align: 'center'},
{title: '分类图片', key: 'image', width: 30, align: 'center'},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 30,
align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '操作', key: '操作', fixed: 'right', width: 30, align: 'center'},
];
const rules = {
name: [{required: true, message: '请输入分类名称'}],
image: [{required: true, message: '请输入分类图片'}],
};
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getCategoryList();
})
/**
* 获取分类列表
*/
const getCategoryList = async () => {
loading.value = true;
const res: any = await myAxios.get("/category/list");
console.log(res)
if (res.code === 0 && res.data) {
tableData.value = res.data;
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 上传图片
*/
const upload = async (row: any) => {
let formData = new FormData()
formData.append("file", row.file)
const result = await myAxios({
url: '/file/upload/server',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: {
biz: "system",
// 需根据名字取出,否则为空
file: formData.get("file")
}
})
// 传出图片地址
if (result.data) {
drawerData.value.image = result.data
console.log(drawerData.value.image)
} else {
message.error(`图片上传失败`);
}
}
/**
* 抽屉
*/
const showDrawer = () => {
visible.value = true;
};
/**
* 关闭弹出框
*/
const onClose = () => {
drawerData.value = ref({})
visible.value = false;
};
const onSubmit = async () => {
const res: any = await myAxios.post('/category/add', {
name: drawerData.value.name,
image: drawerData.value.image
})
console.log(res)
if (res.code === 0) {
drawerData.value = ref('')
await getCategoryList()
message.success('添加分类成功')
} else {
message.error(`添加分类失败:${res.message}`);
}
visible.value = false
}
/**
* 删除
*/
const handleDelete = async (id: any) => {
const res: any = await myAxios.post("/category/delete", {id: id});
if (res.code === 0 && res.data) {
await getCategoryList();
message.success(`删除成功`);
} else {
message.error(`删除分类失败:${res.message}`);
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,145 @@
<template>
<a-card>
<div class="middle-button">
<a-button type="primary" @click="showDrawer">添加</a-button>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:rowKey="(record: any) => record.id"
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === '操作'">
<a-space>
<a-button size="small" type="link" danger @click="handleDelete(record.id)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
<a-drawer
:destroyOnClose="true"
title="添加公告"
placement="right"
:closable="false"
:visible="visible"
@close="visible=false;drawerData = {}"
>
<a-form :model="drawerData" :rules="rules" layout="vertical">
<a-form-item name="content">
<a-textarea
v-model:value="drawerData.content"
placeholder="请输入公告信息"
:auto-size="{ minRows: 2, maxRows: 5 }"
/>
</a-form-item>
</a-form>
<a-button style="margin-right: 8px" @click="onClose">取消</a-button>
<a-button type="primary" @click="onSubmit">确认</a-button>
</a-drawer>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {formatTime} from "../../utils/TableConfig";
import myAxios from "../../api/myAxios";
import {message} from "ant-design-vue";
const tableData: any = ref([]);
const loading = ref(false)
// 抽屉开关
const visible = ref<boolean>(false);
// 抽屉数据
const drawerData: any = ref({})
const columns: any = [
{title: '公告内容', dataIndex: 'content', width: 100, align: 'center'},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 50,
align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '操作', key: '操作', fixed: 'right', width: 30, align: 'center'},
];
const rules = {
content: [{required: true, message: '请输入公告信息'}],
};
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getInfoList();
})
/**
* 获取公告列表
*/
const getInfoList = async () => {
loading.value = true;
const res: any = await myAxios.post("/system/list", {type: 0});
if (res.code === 0 && res.data) {
tableData.value = res.data;
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 抽屉
*/
const showDrawer = () => {
visible.value = true;
};
const onClose = () => {
drawerData.value = {}
visible.value = false;
};
const onSubmit = async () => {
if (drawerData.value.content?.indexOf(' ') !== -1 || drawerData.value.content == '') {
message.error(`格式错误,不允许存在空格`);
return
}
const res: any = await myAxios.post('/system/add', {
type: 0,
content: drawerData.value.content
})
if (res.code === 0) {
drawerData.value = {}
await getInfoList()
message.success('添加公告成功')
} else {
message.error(`添加公告失败:${res.message}`);
}
visible.value = false
}
/**
* 删除
*/
const handleDelete = async (id: any) => {
const res: any = await myAxios.post("/system/delete", {id: id});
if (res.code === 0 && res.data) {
await getInfoList();
message.success(`删除成功`);
} else {
message.error(`删除公告失败:${res.message}`);
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,222 @@
<template>
<a-card>
<!-- 搜索框 -->
<div class="search-box">
<a-form layout="inline">
<a-space>
<a-form-item label="身份">
<a-select
style="width: 200px;"
placeholder="默认查询全部用户"
allowClear
v-model:value="searchParams.userRole"
@change="handleChange"
>
<a-select-option value="user">普通用户</a-select-option>
<a-select-option value="business">商家用户</a-select-option>
<a-select-option value="errand">快送员</a-select-option>
<a-select-option value="admin">管理员</a-select-option>
<a-select-option value="ban">已封禁</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="用户昵称">
<a-input-search
style="width: 300px"
placeholder="请输入用户昵称"
enter-button
allow-clear
@search="onSearch"
/>
</a-form-item>
<a-form-item label="手机号">
<a-input-search
style="width: 300px"
placeholder="请输入手机号"
enter-button
allow-clear
@search="onSearchPhone"
/>
</a-form-item>
</a-space>
</a-form>
</div>
<!-- 数据展示部分 -->
<a-table
:scroll="{ x: 1500, y: 1000 }"
:data-source="tableData"
:columns="columns"
:pagination=false
:loading="loading"
bordered
class="data-box"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'avatarUrl'">
<a-avatar :src="record.avatarUrl"/>
</template>
<template v-if="column.key === 'userRole'">
<div v-if="record.userRole === 'boss'">
<a-tag color="purple">Boss</a-tag>
</div>
<div v-else-if="record.userRole === 'admin'">
<a-tag color="blue">管理员</a-tag>
</div>
<div v-else-if="record.userRole === 'user'">
<a-tag color="green">普通用户</a-tag>
</div>
<div v-else-if="record.userRole === 'errand'">
<a-tag color="yellow">快送员</a-tag>
</div>
<div v-else-if="record.userRole === 'business'">
<a-tag color="orange">商家</a-tag>
</div>
<div v-else-if="record.userRole === 'ban'">
<a-tag color="red">已封禁</a-tag>
</div>
</template>
<template v-if="column.key === '操作'">
<a-space>
<a-button :disabled="validRole(record.userRole)"
size="small" type="link" danger @click="doBan(record.id, record.userRole === 'user')">
{{ record.userRole === 'ban' ? '解封' : '封禁' }}
</a-button>
</a-space>
</template>
</template>
</a-table>
<!-- 分页部分 -->
<div class="pagination">
<a-pagination v-model:current="searchParams.current"
v-model:pageSize="searchParams.pageSize"
show-quick-jumper
show-size-changer
:show-total="(total: any) => `${total}`"
:total="total"
@change="onChange"
/>
</div>
</a-card>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import myAxios from "../../api/myAxios.ts";
import {message} from "ant-design-vue";
import {formatTime} from "../../utils/TableConfig.ts";
const tableData: any = ref([]);
const loading = ref(false)
// 分页
const total: any = ref(0);
// 请求参数
const searchParams: any = ref({
current: 1,
pageSize: 10,
userRole: null,
})
const columns: any = [
{
title: '序号',
width: 40,
fixed: 'left',
align: 'center',
customRender: ({index}: any) => {
return `${(searchParams.value.current - 1) * searchParams.value.pageSize + index + 1}`;
}
},
{title: '用户昵称', dataIndex: 'userAccount', width: 50, key: 'userAccount', fixed: 'left', align: 'center'},
{title: '头像', dataIndex: 'avatarUrl', key: 'avatarUrl', width: 30, align: 'center'},
{title: '身份', dataIndex: 'userRole', key: 'userRole', width: 50, align: 'center'},
{title: '手机号', dataIndex: 'phone', key: 'phone', width: 50, align: 'center'},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 100,
align: 'center',
customRender: (row: any) => {
return formatTime(row)
}
},
{title: '操作', key: '操作', fixed: 'right', width: 30, align: 'center'},
];
/**
* 页面加载时,请求数据
*/
onMounted(() => {
getUserList();
})
/**
* 获取用户数据
*/
const getUserList = async () => {
loading.value = true;
const res: any = await myAxios.post("/user/list/page", {...searchParams.value})
if (res.code === 0 && res.data) {
console.log(res)
tableData.value = res.data.records;
total.value = parseInt(res.data.total);
loading.value = false;
} else {
message.error(`获取数据失败:${res.message}`);
}
}
/**
* 操作
*/
const doBan = async (id: string, isBan: boolean) => {
const res: any = await myAxios.post("/user/update", {id: id, userRole: isBan ? 'ban' : 'user'})
if (res.code === 0 && res.data) {
message.success(`操作成功`);
await getUserList();
} else {
message.error(`封禁用户失败:${res.message}`);
}
}
/**
* 选择用户身份
*/
const handleChange = (userRole: string) => {
searchParams.value.userRole = userRole;
searchParams.value.current = 1;
getUserList();
};
/**
* 搜索功能
*/
const onSearch = (data: string) => {
searchParams.value.userAccount = data;
searchParams.value.current = 1;
getUserList();
}
const onSearchPhone = (data: string) => {
searchParams.value.phone = data;
searchParams.value.current = 1;
getUserList();
}
/**
* 分页
*/
const onChange = (pageNumber: number) => {
searchParams.value.current = pageNumber;
getUserList();
}
// 判断用户权限
const validRole = (userRole: string) => {
return !(userRole === 'user' || userRole === 'ban');
}
</script>
<style scoped>
</style>

10
CampusExpressDelivery/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type {DefineComponent} from 'vue'
const vueComponent: DefineComponent<{}, {}, any>
export default vueComponent
}
declare module '*.mjs'
declare module 'dayjs'

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/myaxios.ts","./src/router/index.ts","./src/router/routes.ts","./src/store/index.ts","./src/store/userstore.ts","./src/utils/tableconfig.ts","./src/app.vue","./src/layout/managelayout.vue","./src/layout/manage/manageheader.vue","./src/layout/manage/managesidebar.vue","./src/view/index.vue","./src/view/login.vue","./src/view/test.vue","./src/view/service/customerservice.vue","./src/view/echarts/brokenline.vue","./src/view/echarts/ordermethod.vue","./src/view/errand/errandinfo.vue","./src/view/errand/errandlist.vue","./src/view/errand/errandorderlist.vue","./src/view/errand/runlist.vue","./src/view/shop/businesslog.vue","./src/view/shop/business/businessinfo.vue","./src/view/shop/business/businessmanage.vue","./src/view/shop/business/dish.vue","./src/view/shop/business/dishgroup.vue","./src/view/shop/order/orderdetail.vue","./src/view/shop/order/orderlist.vue","./src/view/shop/order/storelist.vue","./src/view/system/businessinfo.vue","./src/view/system/category.vue","./src/view/system/info.vue","./src/view/system/carousel/carousel.vue","./src/view/user/userlist.vue"],"version":"5.6.3"}

View File

@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

View File

@ -0,0 +1,40 @@
// import {defineConfig} from 'vite'
// import vue from '@vitejs/plugin-vue'
// import Components from 'unplugin-vue-components/vite';
// import {AntDesignVueResolver} from "unplugin-vue-components/resolvers";
//
//
// export default defineConfig({
// plugins: [vue(),
// Components({
// resolvers: [
// AntDesignVueResolver({
// importStyle: false, // css in js
// }),
// ],
// }),],
// })
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
// server: {
// https: true,
// https: {
// key: fs.readFileSync('/SSL/xiaokuaisong.shop.key'),
// cert: fs.readFileSync('/SSL/xiaokuaisong.shop.pem'),
// },
// },
});

View File

@ -0,0 +1,41 @@
// import {defineConfig} from 'vite'
// import vue from '@vitejs/plugin-vue'
// import Components from 'unplugin-vue-components/vite';
// import {AntDesignVueResolver} from "unplugin-vue-components/resolvers";
//
//
// export default defineConfig({
// plugins: [vue(),
// Components({
// resolvers: [
// AntDesignVueResolver({
// importStyle: false, // css in js
// }),
// ],
// }),],
// })
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
// server: {
// https: true,
// https: {
// key: fs.readFileSync('/SSL/xiaokuaisong.shop.key'),
// cert: fs.readFileSync('/SSL/xiaokuaisong.shop.pem'),
// },
// },
})