first commit
commit
c70c79dda8
|
@ -0,0 +1,36 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
environment {
|
||||||
|
APP_NAME="ocr-server-admin"
|
||||||
|
K8S_FILE="builds/ingress.yaml"
|
||||||
|
K8S_NAMESPACE="kube-qa"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stages {
|
||||||
|
|
||||||
|
stage('compile'){
|
||||||
|
steps {
|
||||||
|
nodejs('Node21') {
|
||||||
|
sh "npm install --registry=https://registry.npm.taobao.org && npm run build"
|
||||||
|
}
|
||||||
|
sh "mkdir -p /etc/nginx/html/${APP_NAME}/"
|
||||||
|
sh "rm -rf /etc/nginx/html/${APP_NAME}/*"
|
||||||
|
sh "mv dist/* /etc/nginx/html/${APP_NAME}/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('ack deploy') {
|
||||||
|
agent none
|
||||||
|
steps {
|
||||||
|
sh "cat builds/nginx.conf > /etc/nginx/conf.d/${APP_NAME}.conf"
|
||||||
|
configFileProvider([configFile(fileId: '87b5c827-bd51-40af-99f4-31a800614e92', targetLocation: 'K8S-CONFIG', variable: 'KUBECONFIG')]) {
|
||||||
|
sh 'export tag=$BUILD_ID && envsubst < $K8S_FILE | kubectl apply -f -'
|
||||||
|
sh "kubectl rollout restart deployment -n ${K8S_NAMESPACE} nginx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: ocr-server-admin-ingress
|
||||||
|
namespace: kube-qa
|
||||||
|
labels:
|
||||||
|
name: ocr-server-admin-ingress
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: "nginx"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: ocr-server-admin.bskies.cc
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- pathType: Prefix
|
||||||
|
path: /
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
port:
|
||||||
|
number: 80
|
|
@ -0,0 +1,13 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name ocr-server-admin.bskies.cc;
|
||||||
|
|
||||||
|
client_body_buffer_size 10m;
|
||||||
|
client_max_body_size 100m;
|
||||||
|
client_header_buffer_size 1m;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /etc/nginx/html/ocr-server-admin/;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
data/
|
||||||
|
data_test/
|
||||||
|
log/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1,39 @@
|
||||||
|
# houtai
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:unit
|
||||||
|
```
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "houtai",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"element-plus": "^2.9.3",
|
||||||
|
"pinia": "^2.3.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node22": "^22.0.0",
|
||||||
|
"@types/jsdom": "^21.1.7",
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"jsdom": "^25.0.1",
|
||||||
|
"npm-run-all2": "^7.0.2",
|
||||||
|
"typescript": "~5.6.3",
|
||||||
|
"vite": "^6.0.5",
|
||||||
|
"vite-plugin-vue-devtools": "^7.6.8",
|
||||||
|
"vitest": "^2.1.8",
|
||||||
|
"vue-tsc": "^2.1.10"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
|
@ -0,0 +1,86 @@
|
||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 276 B |
|
@ -0,0 +1,91 @@
|
||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-s {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-e {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-c {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-cc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
/* 上外边距 */
|
||||||
|
.mt30{
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
/* 左外边距 */
|
||||||
|
.ml10{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.ml20{
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
.mlr10{
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
/* 内边距 */
|
||||||
|
.p5{
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
/* 颜色 */
|
||||||
|
.white{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
/* @media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
} */
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
msg: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="greetings">
|
||||||
|
<h1 class="green">{{ msg }}</h1>
|
||||||
|
<h3>
|
||||||
|
You’ve successfully created a project with
|
||||||
|
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h1 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
position: relative;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.greetings h1,
|
||||||
|
.greetings h3 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,94 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import WelcomeItem from './WelcomeItem.vue'
|
||||||
|
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||||
|
import ToolingIcon from './icons/IconTooling.vue'
|
||||||
|
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||||
|
import CommunityIcon from './icons/IconCommunity.vue'
|
||||||
|
import SupportIcon from './icons/IconSupport.vue'
|
||||||
|
|
||||||
|
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<DocumentationIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Documentation</template>
|
||||||
|
|
||||||
|
Vue’s
|
||||||
|
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||||
|
provides you with all information you need to get started.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<ToolingIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Tooling</template>
|
||||||
|
|
||||||
|
This project is served and bundled with
|
||||||
|
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||||
|
recommended IDE setup is
|
||||||
|
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
||||||
|
+
|
||||||
|
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||||
|
you need to test your components and web pages, check out
|
||||||
|
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vite</a>
|
||||||
|
and
|
||||||
|
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||||
|
/
|
||||||
|
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
More instructions are available in
|
||||||
|
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||||
|
>.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<EcosystemIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Ecosystem</template>
|
||||||
|
|
||||||
|
Get official tools and libraries for your project:
|
||||||
|
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||||
|
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||||
|
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||||
|
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||||
|
you need more resources, we suggest paying
|
||||||
|
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||||
|
a visit.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<CommunityIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Community</template>
|
||||||
|
|
||||||
|
Got stuck? Ask your question on
|
||||||
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
||||||
|
(our official Discord server), or
|
||||||
|
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||||
|
>StackOverflow</a
|
||||||
|
>. You should also follow the official
|
||||||
|
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||||
|
Bluesky account or the
|
||||||
|
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||||
|
X account for latest news in the Vue world.
|
||||||
|
</WelcomeItem>
|
||||||
|
|
||||||
|
<WelcomeItem>
|
||||||
|
<template #icon>
|
||||||
|
<SupportIcon />
|
||||||
|
</template>
|
||||||
|
<template #heading>Support Vue</template>
|
||||||
|
|
||||||
|
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||||
|
us by
|
||||||
|
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||||
|
</WelcomeItem>
|
||||||
|
</template>
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</i>
|
||||||
|
<div class="details">
|
||||||
|
<h3>
|
||||||
|
<slot name="heading"></slot>
|
||||||
|
</h3>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
color: var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.item {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
left: -26px;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:before {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:after {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:first-of-type:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-of-type:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import HelloWorld from '../HelloWorld.vue'
|
||||||
|
|
||||||
|
describe('HelloWorld', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
||||||
|
expect(wrapper.text()).toContain('Hello Vitest')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
class="iconify iconify--mdi"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,36 @@
|
||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(ElementPlus)
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.config.globalProperties.$messageS = function msgError(content: any) {
|
||||||
|
ElMessage({ message: content, type: 'success', })
|
||||||
|
};
|
||||||
|
app.config.globalProperties.$messageW = function msgError(content: any) {
|
||||||
|
ElMessage({ message: content, type: 'warning', })
|
||||||
|
};
|
||||||
|
app.config.globalProperties.$messageI = function msgError(content: any) {
|
||||||
|
ElMessage({ message: content, type: 'info', })
|
||||||
|
};
|
||||||
|
app.config.globalProperties.$messageE = function msgError(content: any) {
|
||||||
|
ElMessage({ message: content, type: 'error', })
|
||||||
|
};
|
||||||
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('../views/login.vue'),
|
||||||
|
},
|
||||||
|
// 主页
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/index',
|
||||||
|
component: () => import('../views/home.vue'),
|
||||||
|
meta: {
|
||||||
|
comp: 'Home'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
// 租户管理
|
||||||
|
{
|
||||||
|
path: '/index',
|
||||||
|
component: () => import('../views/index.vue'),
|
||||||
|
meta: {
|
||||||
|
comp: 'index',
|
||||||
|
name: '租户管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
component: () => import('../views/about.vue'),
|
||||||
|
meta: {
|
||||||
|
comp: 'about',
|
||||||
|
name: '产品'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ceshi',
|
||||||
|
component: () => import('../views/ceshi.vue'),
|
||||||
|
meta: {
|
||||||
|
comp: 'ceshi',
|
||||||
|
name: '测试'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
// 全局路由导航
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.meta.name) {
|
||||||
|
document.title = to.meta.name
|
||||||
|
} else {
|
||||||
|
document.title = '管理系统'
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
export default router
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { GET, POST } from '@/utils/request';
|
||||||
|
/**
|
||||||
|
* @title 登录
|
||||||
|
* @param params account,password
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const userLogin = (params?: Object) => {
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: '/api/user/login',
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @title 获取产品
|
||||||
|
*/
|
||||||
|
export const getProduct = async () => {
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/product/list",
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @title 新增产品
|
||||||
|
*/
|
||||||
|
export const createProduct = async (params: Object) => {
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/product/create",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @title 修改产品
|
||||||
|
*/
|
||||||
|
export const updateProduct = async (params: Object) => {
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/product/update",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title 注册租户
|
||||||
|
*/
|
||||||
|
export const tenantRegister = async (params: Object)=>{
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/tenant/register",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @title 修改租户
|
||||||
|
*/
|
||||||
|
export const tenantUpdate = async (params: Object)=>{
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/tenant/update",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @title 租户列表
|
||||||
|
*/
|
||||||
|
export const tenantList = async (params: Object)=>{
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/api/tenant/page",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title 系统开放 新增订单
|
||||||
|
*/
|
||||||
|
export const create_payorder = async (params: Object)=>{
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/open/api/create_payorder",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title 系统开放
|
||||||
|
*/
|
||||||
|
export const id_card = async (params: Object)=>{
|
||||||
|
return POST(
|
||||||
|
{
|
||||||
|
url: "/open/api/ocr/id_card",
|
||||||
|
params
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
import axios from "axios";
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
interface requestType {
|
||||||
|
url: string
|
||||||
|
params?: any
|
||||||
|
}
|
||||||
|
const handleCode = async (code: number, msg: string) => {
|
||||||
|
switch (code) {
|
||||||
|
case 401:
|
||||||
|
ElMessage.error(msg || '登录失效')
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('登录失效')
|
||||||
|
location.href="/login"
|
||||||
|
}, 1500)
|
||||||
|
// 跳转登录
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ElMessage.error(msg || `后端接口${code}异常`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建axsio 赋给常量service
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: "/api",
|
||||||
|
timeout: 30000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=UTF-8',
|
||||||
|
"authorization": localStorage.getItem("token"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 添加请求拦截器
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config: any) => {
|
||||||
|
console.log(config, 'config')
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
// 对请求错误做些什么
|
||||||
|
console.log(error, 'error')
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 添加响应拦截器
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
//response参数是响应对象
|
||||||
|
// 对响应数据做点什么
|
||||||
|
const { data, config } = response
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
const { response } = error
|
||||||
|
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
const { status, data } = response
|
||||||
|
handleCode(status, data.msg)
|
||||||
|
|
||||||
|
// 对响应错误做点什么
|
||||||
|
return Promise.reject(error);
|
||||||
|
} else {
|
||||||
|
let { message } = error
|
||||||
|
if (message === 'Network Error') {
|
||||||
|
message = '后端接口连接异常'
|
||||||
|
}
|
||||||
|
if (message.includes('timeout')) {
|
||||||
|
message = '后端接口请求超时'
|
||||||
|
}
|
||||||
|
if (message.includes('Request failed with status code')) {
|
||||||
|
const code = message.substr(message.length - 3)
|
||||||
|
message = '后端接口' + code + '异常'
|
||||||
|
}
|
||||||
|
ElMessage.error(message || `后端接口未知异常`)
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description GET
|
||||||
|
*/
|
||||||
|
const GET = ({ url, params }: requestType) => {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: "GET",
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description POST
|
||||||
|
*/
|
||||||
|
const POST = ({ url, params }: requestType) => {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: "POST",
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description PUT
|
||||||
|
*/
|
||||||
|
const PUT = ({ url, params }: requestType) => {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: "PUT",
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description DELETE
|
||||||
|
*/
|
||||||
|
const DELETE = ({ url, params }: requestType) => {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: 'delete',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description PATCH
|
||||||
|
*/
|
||||||
|
const PATCH = ({ url, params }: requestType) => {
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
service
|
||||||
|
.put(url, params)
|
||||||
|
.then((res: any) => {
|
||||||
|
if (res && res.status == 200) {
|
||||||
|
resolve(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export { GET, POST, PUT, DELETE, PATCH }
|
|
@ -0,0 +1,172 @@
|
||||||
|
<template>
|
||||||
|
<el-form>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
@click="
|
||||||
|
dialogTableVisible = true;
|
||||||
|
createFrom.resetFields();
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
>新增</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table :data="tableData" border style="width: 100%">
|
||||||
|
<el-table-column prop="name" label="名称" width="180" />
|
||||||
|
<el-table-column prop="price" label="价格" width="180" />
|
||||||
|
<el-table-column prop="api_usage_limit" label="次数" />
|
||||||
|
<el-table-column prop="created_at" label="开通时间" />
|
||||||
|
<el-table-column prop="description" label="备注" />
|
||||||
|
<el-table-column label="操作">
|
||||||
|
<template #default="scope">
|
||||||
|
<div>
|
||||||
|
<el-button @click="editProduct(scope.row)" type="primary"
|
||||||
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-button type="danger">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 新增或修改弹框 start -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogTableVisible"
|
||||||
|
:title="id ? '修改产品' : '新增产品'"
|
||||||
|
width="800"
|
||||||
|
>
|
||||||
|
<el-form ref="createFrom" :model="createFormData" :rules="createFromRules">
|
||||||
|
<el-form-item label="名称" prop="name">
|
||||||
|
<el-input v-model="createFormData.name" placeholder="请输入名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="价格" prop="price">
|
||||||
|
<el-input v-model="createFormData.price" placeholder="请输入价格" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图片"> </el-form-item>
|
||||||
|
<el-form-item label="次数" prop="api_usage_limit">
|
||||||
|
<el-input
|
||||||
|
type="number"
|
||||||
|
v-model="createFormData.api_usage_limit"
|
||||||
|
placeholder="请输入可使用次数"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="createFormData.description"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入可使用次数"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="dialogTableVisible = false">取消</el-button>
|
||||||
|
<el-button @click="submitForm(createFrom)" type="primary"
|
||||||
|
>提交</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 新增或修改弹框 end -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, onMounted } from "vue";
|
||||||
|
import type { ComponentSize, FormInstance, FormRules } from "element-plus";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { getProduct, createProduct, updateProduct } from "@/utils/api.ts";
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
|
||||||
|
interface RuleForm {
|
||||||
|
id?: String;
|
||||||
|
name: String; //产品名
|
||||||
|
price: Number; //价格
|
||||||
|
description: String; //产品的详细描述,提供关于产品的更多信息。
|
||||||
|
image_url: String; //图片链接
|
||||||
|
api_usage_limit: String; //用户购买该产品后可以获得的API使用次数限制。
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = ref("");
|
||||||
|
|
||||||
|
const createFrom = ref(); //新增或修改产品时的DOM
|
||||||
|
|
||||||
|
const dialogTableVisible = ref(false); //是否显示新增&修改弹框
|
||||||
|
|
||||||
|
const createFormData = reactive({
|
||||||
|
id: "",
|
||||||
|
name: "", //产品名
|
||||||
|
price: "", //价格
|
||||||
|
description: "", //描述
|
||||||
|
image_url: "", //图片链接
|
||||||
|
api_usage_limit: "", //用户购买该产品后可以获得的API使用次数限制。
|
||||||
|
});
|
||||||
|
|
||||||
|
const createFromRules = reactive<FormRules<RuleForm>>({
|
||||||
|
name: [{ required: true, message: "请输入产品名称", trigger: "blur" }],
|
||||||
|
price: [{ required: true, message: "请输入产品价格", trigger: "blur" }],
|
||||||
|
api_usage_limit: [
|
||||||
|
{ required: true, message: "请输入可使用次数", trigger: "blur" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
//列表数据
|
||||||
|
const tableData = ref([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @titel 获取产品列表
|
||||||
|
*/
|
||||||
|
const getList = async () => {
|
||||||
|
const { code, body } = await getProduct();
|
||||||
|
if (code == 200) {
|
||||||
|
tableData.value = body;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @title 修改产品
|
||||||
|
*/
|
||||||
|
const editProduct = (row) => {
|
||||||
|
createFormData.id = row.id;
|
||||||
|
createFormData.api_usage_limit = row.api_usage_limit;
|
||||||
|
createFormData.description = row.description;
|
||||||
|
createFormData.name = row.name;
|
||||||
|
createFormData.price = row.price;
|
||||||
|
createFormData.image_url = row.image_url;
|
||||||
|
dialogTableVisible.value = true;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @title 创建产品
|
||||||
|
*/
|
||||||
|
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate(async (valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
createFormData.price = Number(createFormData.price);
|
||||||
|
createFormData.api_usage_limit = Number(createFormData.api_usage_limit);
|
||||||
|
// 修改
|
||||||
|
if (createFormData.id) {
|
||||||
|
const { code } = await updateProduct(createFormData);
|
||||||
|
if (code == 200) {
|
||||||
|
ElMessage.success("成功!");
|
||||||
|
dialogTableVisible.value = false;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
else {
|
||||||
|
const { code } = await createProduct(createFormData);
|
||||||
|
if (code == 200) {
|
||||||
|
ElMessage.success("成功!");
|
||||||
|
dialogTableVisible.value = false;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
::v-deep().el-form-item__content {
|
||||||
|
justify-content: flex-end !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,128 @@
|
||||||
|
<template>
|
||||||
|
<div class="tooltip-base-box">
|
||||||
|
<div class="row center">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Top Left prompts info"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<el-button>top-start</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Top Center prompts info"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-button>top</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Top Right prompts info"
|
||||||
|
placement="top-end"
|
||||||
|
>
|
||||||
|
<el-button>top-end</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Left Top prompts info"
|
||||||
|
placement="left-start"
|
||||||
|
>
|
||||||
|
<el-button>left-start</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Right Top prompts info"
|
||||||
|
placement="right-start"
|
||||||
|
>
|
||||||
|
<el-button>right-start</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Left Center prompts info"
|
||||||
|
placement="left"
|
||||||
|
>
|
||||||
|
<el-button class="mt-3 mb-3">left</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Right Center prompts info"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<el-button>right</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Left Bottom prompts info"
|
||||||
|
placement="left-end"
|
||||||
|
>
|
||||||
|
<el-button>left-end</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Right Bottom prompts info"
|
||||||
|
placement="right-end"
|
||||||
|
>
|
||||||
|
<el-button>right-end</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="row center">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Bottom Left prompts info"
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
|
<el-button>bottom-start</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Bottom Center prompts info"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<el-button>bottom</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="Bottom Right prompts info"
|
||||||
|
placement="bottom-end"
|
||||||
|
>
|
||||||
|
<el-button>bottom-end</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tooltip-base-box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tooltip-base-box .row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.tooltip-base-box .center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.tooltip-base-box .box-item {
|
||||||
|
width: 110px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,468 @@
|
||||||
|
<template>
|
||||||
|
<div class="common-layout">
|
||||||
|
<el-container>
|
||||||
|
<el-aside
|
||||||
|
:width="isCollapse ? '63px' : '200px'"
|
||||||
|
style="background: #1d3043"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="collapse white"
|
||||||
|
:style="{
|
||||||
|
fontSize: isCollapse ? '35px' : '20px',
|
||||||
|
background: '#18222c',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ isCollapse ? "管" : "管理系统" }}
|
||||||
|
</div>
|
||||||
|
<div class="flex-a p5">
|
||||||
|
<div class="person_img2" style="display: flex; align-items: center">
|
||||||
|
<el-avatar
|
||||||
|
:style="{
|
||||||
|
width: isCollapse ? '40px' : '80px',
|
||||||
|
height: isCollapse ? '40px' : '80px',
|
||||||
|
}"
|
||||||
|
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="f14 white" v-if="!isCollapse">
|
||||||
|
<div>管理员</div>
|
||||||
|
<div>(总经理)</div>
|
||||||
|
<div>17584958214</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-menu
|
||||||
|
:default-active="state.activePath"
|
||||||
|
class="el-menu-vertical-demo"
|
||||||
|
:collapse="isCollapse"
|
||||||
|
router
|
||||||
|
background-color="#1d3043"
|
||||||
|
active-color="red"
|
||||||
|
active-text-color="#ffd04b"
|
||||||
|
>
|
||||||
|
<div v-for="item in state.menuList" :key="item.id">
|
||||||
|
<el-menu-item
|
||||||
|
:index="'/' + item.browserUrl"
|
||||||
|
v-if="item.children.length == 0"
|
||||||
|
>
|
||||||
|
<el-icon><Link /></el-icon>
|
||||||
|
<template #title>{{ item.name }} </template>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-sub-menu v-else>
|
||||||
|
<template #title>
|
||||||
|
<el-icon><Link /></el-icon>
|
||||||
|
<span class="ml15">{{ item.name }}</span>
|
||||||
|
</template>
|
||||||
|
<div v-for="item2 in item.children" :key="item2.id">
|
||||||
|
<el-menu-item-group>
|
||||||
|
<el-menu-item :index="'/' + item2.browserUrl">
|
||||||
|
<el-icon><Link /></el-icon>{{ item2.name }}</el-menu-item
|
||||||
|
>
|
||||||
|
</el-menu-item-group>
|
||||||
|
</div>
|
||||||
|
</el-sub-menu>
|
||||||
|
</div>
|
||||||
|
</el-menu>
|
||||||
|
</el-aside>
|
||||||
|
<el-container
|
||||||
|
:style="{
|
||||||
|
height: '100vh',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-header
|
||||||
|
:style="{
|
||||||
|
height: '50px',
|
||||||
|
background: '#1d3043',
|
||||||
|
}"
|
||||||
|
class="flex-s white"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<el-radio-group v-model="isCollapse">
|
||||||
|
<el-radio-button :value="false" v-if="isCollapse"
|
||||||
|
><el-icon :size="25"><Fold /></el-icon
|
||||||
|
></el-radio-button>
|
||||||
|
<el-radio-button :value="true" v-if="!isCollapse"
|
||||||
|
><el-icon :size="25"><Fold /></el-icon
|
||||||
|
></el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<div @click="reload" class="flex mlr10">
|
||||||
|
<el-icon :size="20"><RefreshRight /></el-icon>刷新
|
||||||
|
</div>
|
||||||
|
<div @click="toggleFullScreen" class="flex ml20">
|
||||||
|
<el-icon :size="19" class=""><FullScreen /></el-icon>全屏
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="header_info">
|
||||||
|
<div class="flex" @click="state.userVisible = true">
|
||||||
|
<el-icon size="20"><EditPen /></el-icon>修改密码
|
||||||
|
</div>
|
||||||
|
<div class="flex ml20" @click="loginOut">
|
||||||
|
<el-icon size="20"><SwitchButton /></el-icon>退出
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-header>
|
||||||
|
<el-main class="p5">
|
||||||
|
<el-tabs
|
||||||
|
@tab-click="tabClick"
|
||||||
|
@tab-remove="tabRemove"
|
||||||
|
v-model="state.activeTab"
|
||||||
|
type="border-card"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="item in state.tabsItem"
|
||||||
|
:label="item.title"
|
||||||
|
:name="item.name"
|
||||||
|
:ref="item.ref"
|
||||||
|
:closable="item.closable"
|
||||||
|
>
|
||||||
|
<keepAlive>
|
||||||
|
<component
|
||||||
|
v-if="isRouterAlive"
|
||||||
|
:is="dom[state.tabsItem.content]"
|
||||||
|
:key="state.activeTab"
|
||||||
|
/>
|
||||||
|
</keepAlive>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-main>
|
||||||
|
<!-- <el-footer
|
||||||
|
:style="{
|
||||||
|
height: '30px',
|
||||||
|
lineHeight: '30px',
|
||||||
|
}"
|
||||||
|
><div>
|
||||||
|
<strong>
|
||||||
|
Copyright © 2016-2020
|
||||||
|
<a href="javascript:;">管理系统</a>
|
||||||
|
</strong>
|
||||||
|
. All rights reserved.
|
||||||
|
</div></el-footer
|
||||||
|
> -->
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
<el-dialog
|
||||||
|
title="修改密码"
|
||||||
|
v-model="state.userVisible"
|
||||||
|
@close="userDialog"
|
||||||
|
width="25%"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-width="100px"
|
||||||
|
ref="userRef"
|
||||||
|
:model="alterUserInfo"
|
||||||
|
size="mini"
|
||||||
|
:rules="rules"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<el-form-item
|
||||||
|
style="width: 89%"
|
||||||
|
prop="currentPassword"
|
||||||
|
label="旧密码"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
show-password
|
||||||
|
v-model="alterUserInfo.currentPassword"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item style="width: 89%" prop="password" label="新密码">
|
||||||
|
<el-input show-password v-model="alterUserInfo.password"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
style="width: 89%"
|
||||||
|
prop="confirmPassword"
|
||||||
|
label="再次确定"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
show-password
|
||||||
|
v-model="alterUserInfo.confirmPassword"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="state.userVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="alterPassword(userRef)">
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
shallowRef,
|
||||||
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
nextTick,
|
||||||
|
getCurrentInstance,
|
||||||
|
} from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import type { FormInstance, FormRules } from "element-plus";
|
||||||
|
import index from "./index.vue";
|
||||||
|
import about from "./about.vue";
|
||||||
|
import ceshi from "./ceshi.vue";
|
||||||
|
|
||||||
|
const { appContext } = getCurrentInstance();
|
||||||
|
const dom = shallowRef({
|
||||||
|
ceshi,
|
||||||
|
about,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
const proxy = appContext.config.globalProperties;
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
userVisible: false,
|
||||||
|
activePath: "/index",
|
||||||
|
activeTab: "index",
|
||||||
|
tabsItem: [
|
||||||
|
{
|
||||||
|
title: "租户管理",
|
||||||
|
name: "index",
|
||||||
|
// 导入的组件
|
||||||
|
content: "index",
|
||||||
|
ref: "tabs",
|
||||||
|
closable: false,
|
||||||
|
query: "",
|
||||||
|
path: "/index",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
menuList: [
|
||||||
|
{
|
||||||
|
action: "index",
|
||||||
|
appIcon: null,
|
||||||
|
browserUrl: "index",
|
||||||
|
children: [],
|
||||||
|
icon: "fa fa-link",
|
||||||
|
name: "租户管理",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "about",
|
||||||
|
appIcon: null,
|
||||||
|
browserUrl: "about",
|
||||||
|
children: [],
|
||||||
|
icon: "fa fa-link",
|
||||||
|
name: "关于",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "ceshi",
|
||||||
|
appIcon: null,
|
||||||
|
browserUrl: "ceshi",
|
||||||
|
children: [],
|
||||||
|
icon: "fa fa-link",
|
||||||
|
name: "测试",
|
||||||
|
},
|
||||||
|
] as any,
|
||||||
|
});
|
||||||
|
const validatePass = (rule: any, value: any, callback: any) => {
|
||||||
|
console.log(value);
|
||||||
|
if (value.length < 6) {
|
||||||
|
callback(new Error("密码长度不能小于6位"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const alterUserInfo = reactive({
|
||||||
|
currentPassword: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
});
|
||||||
|
const userRef = ref<FormInstance>();
|
||||||
|
const rules = {
|
||||||
|
currentPassword: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
{ validator: validatePass, trigger: "blur" },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
{ validator: validatePass, trigger: "blur" },
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: "请确认密码", trigger: "blur" },
|
||||||
|
{
|
||||||
|
validator: (rule: any, value: any, callback: any) => {
|
||||||
|
if (value !== alterUserInfo.password) {
|
||||||
|
callback(new Error("两次输入的密码不一致"));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const isRouterAlive = ref(true);
|
||||||
|
// 在组件挂载时获取用户信息
|
||||||
|
let isCollapse = ref<Boolean>(false);
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log(8888);
|
||||||
|
});
|
||||||
|
// 刷新
|
||||||
|
const reload = () => {
|
||||||
|
// router.go(0);
|
||||||
|
isRouterAlive.value = false;
|
||||||
|
nextTick(() => {
|
||||||
|
isRouterAlive.value = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 退出登录
|
||||||
|
const loginOut = () => {
|
||||||
|
router.push("/login");
|
||||||
|
};
|
||||||
|
// 关闭dialog
|
||||||
|
const userDialog = () => {
|
||||||
|
for (let i in alterUserInfo) {
|
||||||
|
alterUserInfo[i] = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//修改密码
|
||||||
|
const alterPassword = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid: any, fields: any) => {
|
||||||
|
if (valid) {
|
||||||
|
state.userVisible = false;
|
||||||
|
router.push("/login");
|
||||||
|
} else {
|
||||||
|
return proxy.$messageE("请完善信息");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 全屏
|
||||||
|
const isFullScreen = ref(false);
|
||||||
|
function toggleFullScreen() {
|
||||||
|
if (!document.fullscreenElement && !isFullScreen.value) {
|
||||||
|
// 进入全屏
|
||||||
|
document.documentElement.requestFullscreen().then(() => {
|
||||||
|
isFullScreen.value = true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 退出全屏
|
||||||
|
if (document.exitFullscreen) {
|
||||||
|
document.exitFullscreen().then(() => {
|
||||||
|
isFullScreen.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听全屏状态变化
|
||||||
|
document.addEventListener("fullscreenchange", () => {
|
||||||
|
isFullScreen.value = !!document.fullscreenElement;
|
||||||
|
});
|
||||||
|
// 点击tab
|
||||||
|
const tabClick = (tab: any) => {
|
||||||
|
let val = state.tabsItem.filter((item) => tab.props.label == item.title);
|
||||||
|
router.push(val[0].path);
|
||||||
|
state.activePath = val[0].path;
|
||||||
|
};
|
||||||
|
// 关闭tab
|
||||||
|
const tabRemove = (tab: any) => {
|
||||||
|
state.tabsItem.forEach((val: any, index: Number) => {
|
||||||
|
if (val.name === tab) {
|
||||||
|
state.tabsItem.splice(index, 1);
|
||||||
|
if (tab === state.activeTab) {
|
||||||
|
router.push(state.tabsItem[index - 1].path);
|
||||||
|
state.activePath = state.tabsItem[index - 1].path;
|
||||||
|
state.activeTab = state.tabsItem[index - 1].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 监听当前路由
|
||||||
|
watch(
|
||||||
|
() => router.currentRoute.value,
|
||||||
|
(val: any) => {
|
||||||
|
console.log(8888, val);
|
||||||
|
state.activePath = val.path;
|
||||||
|
state.tabsItem.title = val.meta.name;
|
||||||
|
state.tabsItem.name = val.meta.comp;
|
||||||
|
state.tabsItem.closable = true;
|
||||||
|
state.tabsItem.ref = "tabs";
|
||||||
|
state.tabsItem.content = val.meta.comp;
|
||||||
|
state.tabsItem.query = val.query.id;
|
||||||
|
state.tabsItem.path = val.path;
|
||||||
|
|
||||||
|
console.log(56696, state.tabsItem);
|
||||||
|
let result = state.tabsItem.some((item) => item.title == val.meta.name);
|
||||||
|
console.log(result);
|
||||||
|
state.activeTab = val.meta.comp;
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
state.tabsItem.push({
|
||||||
|
title: val.meta.name,
|
||||||
|
name: val.meta.comp,
|
||||||
|
// 导入的组件
|
||||||
|
content: val.meta.comp,
|
||||||
|
ref: "tabs",
|
||||||
|
closable: true,
|
||||||
|
query: "",
|
||||||
|
path: "/" + val.meta.comp,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header_info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.collapse {
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.person_img2 img {
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
/* background: #ffffff; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tab-pane {
|
||||||
|
max-height: calc(100vh - 150px);
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-radio-button__inner {
|
||||||
|
background: #1d3043;
|
||||||
|
color: white;
|
||||||
|
border-color: #1d3043;
|
||||||
|
}
|
||||||
|
.el-radio-button__original-radio:checked + .el-radio-button__inner {
|
||||||
|
background: #1d3043;
|
||||||
|
border-color: #1d3043;
|
||||||
|
}
|
||||||
|
.el-radio-button:first-child .el-radio-button__inner {
|
||||||
|
border-left: #1d3043;
|
||||||
|
}
|
||||||
|
.el-menu {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.el-menu-item-group__title {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.el-menu-item {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.el-main {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<el-form>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary">新增</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table :data="tableData" border style="width: 100%">
|
||||||
|
<el-table-column align="center" fixed prop="tenant_name" label="租户昵称" />
|
||||||
|
<el-table-column align="center" prop="account" label="账号" width="220" />
|
||||||
|
<el-table-column align="center" prop="enable" label="是否开启" width="120" />
|
||||||
|
<el-table-column align="center" prop="created_at" label="创建时间" width="220" />
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button @click="update(scope.row)" link type="primary" size="small">编辑</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="flex-c-c mt20">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[100, 200, 300, 400]"
|
||||||
|
layout=" prev, pager, next"
|
||||||
|
:total="count"
|
||||||
|
background
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { tenantList, tenantUpdate, tenantRegister } from "@/utils/api.ts"
|
||||||
|
const handleClick = () => {
|
||||||
|
console.log("click");
|
||||||
|
};
|
||||||
|
|
||||||
|
import { ref, onMounted, reactive } from "vue";
|
||||||
|
|
||||||
|
|
||||||
|
// 注册修改字段
|
||||||
|
const formData = reactive({
|
||||||
|
account: "",
|
||||||
|
password: "",
|
||||||
|
tenant_name: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
getList()
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
const handleSizeChange = (val: number) => {
|
||||||
|
console.log(`${val} items per page`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (val: number) => {
|
||||||
|
page.value=val
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 租户列表
|
||||||
|
const tableData = ref([]);
|
||||||
|
|
||||||
|
// 列表总条数
|
||||||
|
const count = ref(0)
|
||||||
|
/**
|
||||||
|
* @title 获取租户列表
|
||||||
|
*/
|
||||||
|
const getList = async () => {
|
||||||
|
const params = {
|
||||||
|
tenant_name: "",
|
||||||
|
page: page.value,
|
||||||
|
size: pageSize.value,
|
||||||
|
}
|
||||||
|
const { code, body:{records,total} } = await tenantList(params)
|
||||||
|
if (code == 200) {
|
||||||
|
tableData.value = records
|
||||||
|
count.value = total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title 注册租户
|
||||||
|
*/
|
||||||
|
const regesiter = async (params: Object) => {
|
||||||
|
const { code } = await tenantRegister(formData)
|
||||||
|
if(code==200){
|
||||||
|
ElMessage.success("成功!")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title 修改租户
|
||||||
|
*/
|
||||||
|
|
||||||
|
const update = async ()=>{
|
||||||
|
console.log(formData);
|
||||||
|
return
|
||||||
|
const {code} = await tenantUpdate(formData)
|
||||||
|
if(code==200){
|
||||||
|
ElMessage.success("修改成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.flex-c-c{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.mt20{
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div class="contain">
|
||||||
|
<el-card class="box-card" shadow="always">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex-c">欢迎登录</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="ruleFormRef" :rules="rules" :model="ruleForm">
|
||||||
|
<el-form-item label="账号" prop="account">
|
||||||
|
<el-input :prefix-icon="User" v-model="ruleForm.account" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input :prefix-icon="Lock" v-model="ruleForm.password" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div class="flex-s">
|
||||||
|
<div>
|
||||||
|
<el-checkbox v-model="state.isRemember">记住密码</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<div style="color: #606266; font-size: 14px">忘记密码?</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-c mt30">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="submitForm(ruleFormRef)"
|
||||||
|
:loading="state.loading"
|
||||||
|
style="width: 100%"
|
||||||
|
>登录</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, getCurrentInstance, ref } from "vue";
|
||||||
|
import { User, Lock } from "@element-plus/icons-vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
import { userLogin } from "@/utils/api.ts";
|
||||||
|
|
||||||
|
const { appContext } = getCurrentInstance();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const proxy = appContext.config.globalProperties;
|
||||||
|
const ruleFormRef = ref();
|
||||||
|
const ruleForm = reactive({
|
||||||
|
account: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
const state = reactive({
|
||||||
|
loading: false,
|
||||||
|
isRemember: false,
|
||||||
|
});
|
||||||
|
const rules = reactive<FormRules<ruleForm>>({
|
||||||
|
account: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||||||
|
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const submitForm = async (formEl) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate(async (valid, fields) => {
|
||||||
|
if (valid) {
|
||||||
|
const {
|
||||||
|
code,
|
||||||
|
body: { token },
|
||||||
|
} = await userLogin(ruleForm);
|
||||||
|
if (code == 200) {
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
}
|
||||||
|
ElMessage.success("登录成功")
|
||||||
|
router.push("/");
|
||||||
|
} else {
|
||||||
|
ElMessage.error("请完善信息")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.contain {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-card {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||||
|
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueDevTools(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
Loading…
Reference in New Issue