保存进度!

This commit is contained in:
2022-12-15 09:25:51 +08:00
parent 5c72437e2d
commit 5dcff64bf5
130 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
> 1%
last 2 versions
not dead
not ie 11
safari >= 7

View File

@@ -0,0 +1,27 @@
/*
* @Author: Kane
* @Date: 2022-12-14 15:12:46
* @LastEditors: Kane
* @LastEditTime: 2022-12-14 15:20:20
* @FilePath: \admin_system\.eslintrc.js
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,24 @@
# admin_system
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
{
"name": "admin_system",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"element-plus": "^2.2.26",
"sass": "^1.56.2",
"sass-loader": "^13.2.0",
"scss": "^0.2.4",
"scss-loader": "^0.0.1",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-cli-plugin-element-plus": "~0.0.13"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't
work properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app" v-cloak></div>
<!-- built files will be auto injected -->
</body>
<style>
.v-cloak {
display: none;
}
</style>
</html>

View File

@@ -0,0 +1,27 @@
<!--
* @Author: Kane
* @Date: 2022-12-14 15:12:46
* @LastEditors: Kane
* @LastEditTime: 2022-12-14 17:03:58
* @FilePath: \admin_system\src\App.vue
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
-->
<template>
<router-view></router-view>
</template>
<script>
// import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
// HelloWorld,
},
};
</script>
<style>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,60 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,15 @@
/*
* @Author: Kane
* @Date: 2022-11-12 23:32:20
* @LastEditors: Kane
* @LastEditTime: 2022-11-29 13:21:49
* @FilePath: \hello-cli\src\assets\css\app.css
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
@import url("colors.css");
body {
background-color: #f4f5f7ff;
}

View File

@@ -0,0 +1,24 @@
/*
* @Author: Kane
* @Date: 2022-11-12 23:22:59
* @LastEditors: Kane
* @LastEditTime: 2022-12-05 01:07:18
* @FilePath: \hello-cli\src\assets\css\colors.css
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
html {
--backupground-color: #f4f5f7ff;
--btn-color-blue: #307dbe;
--btn-color-yellow: #f7b24d;
--btn-color-green: #5bad60;
--btn-color-red: #e56651;
--btn-font-color: #fff;
--input-focus-color: #e56651;
/* 标题栏背景色 */
--banner-background-color: #1d74b2;
}

View File

@@ -0,0 +1,17 @@
/*
* @Author: Kane
* @Date: 2022-12-05 00:07:49
* @LastEditors: Kane
* @LastEditTime: 2022-12-05 00:48:04
* @FilePath: \hello-cli\src\assets\css\global.css
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
html,
body,
#app {
/* padding: 0px; */
margin: 0px;
overflow: hidden;
}

View File

@@ -0,0 +1,57 @@
/*
* @Author: Kane
* @Date: 2022-10-12 08:49:14
* @LastEditors: Kane
* @LastEditTime: 2022-11-12 23:34:06
* @FilePath: \car_dealer\css\kane.css
* @Description: vue学习用的样式表
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
html {
--backupground-color: #f4f5f7ff;
--btn-color-blue: #307dbe;
--btn-color-yellow: #f7b24d;
--btn-color-green: #5bad60;
--btn-color-red: #e56651;
--btn-font-color: #fff;
}
#root,
#app {
padding: 50px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
hr {
background-color: steelblue;
border: none;
height: 3px;
/* width: 100% */
}
/* .test {
width : 110vw;
height : 100vh;
border : 1px solid red;
overflow : auto;
} */
input[type="text"] {
border: none;
outline: solid 2px #e56651;
/* font-size: 2rem; */
}
label {
display: inline-block;
font-size: 2rem;
margin-top: 15px;
}

View File

@@ -0,0 +1,379 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none;
/* 1 */
text-decoration: underline;
/* 2 */
text-decoration: underline dotted;
/* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@@ -0,0 +1,20 @@
:root {
font-size: 1em;
box-sizing: border-box;
}
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
body {
font-family: Arial, Helvetica, sans-serif;
}
.pointer {
cursor: pointer;
}

View File

@@ -0,0 +1,25 @@
/*
* @Author: Kane
* @Date: 2022-12-14 15:12:46
* @LastEditors: Kane
* @LastEditTime: 2022-12-14 17:47:32
* @FilePath: \admin_system\src\main.js
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// import installElementPlus from './plugins/element'
import("./css/root.css");
import("./css/normalize.css");
import("element-plus/dist/index.css");
import ElementPlus from "element-plus";
const app = createApp(App)
// installElementPlus(app)
app.use(ElementPlus);
app.use(store).use(router).mount('#app')

View File

@@ -0,0 +1,31 @@
/*
* @Author: Kane
* @Date: 2022-12-14 15:12:46
* @LastEditors: Kane
* @LastEditTime: 2022-12-14 16:19:17
* @FilePath: \admin_system\src\router\index.js
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
*/
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [
{
path: "/",
name: "Home",
redirect: "Login",
},
{
path: "/login",
name: "Login",
component: () => import("../views/account/Login.vue"),
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@@ -0,0 +1,14 @@
import { createStore } from 'vuex'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})

View File

@@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@@ -0,0 +1,18 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>

View File

@@ -0,0 +1,143 @@
<!--
* @Author: Kane
* @Date: 2022-12-14 15:23:54
* @LastEditors: Kane
* @LastEditTime: 2022-12-14 22:53:10
* @FilePath: \admin_system\src\views\account\Login.vue
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
-->
<template>
<div id="login">
<div class="form-warp">
<ul class="menu-tab">
<li :class="{ 'current': current_menu === item.type }" @click="onToggleMenu(item.type)" v-for="item in tab_menu"
:key="item.type">{{ item.label
}}
</li>
</ul>
<el-form ref="form" :model="form">
<el-form-item>
<label class="form-label">用户名</label>
<el-input type="text" v-model.lazy.trim="loginForm.username"></el-input>
</el-form-item>
<el-form-item>
<label class="form-label">密码</label>
<el-input type="password" v-model.lazy.trim="loginForm.password"></el-input>
</el-form-item>
<el-form-item v-show="current_menu === tab_menu[1].type">
<label class="form-label">确认密码</label>
<el-input type="password" v-model.lazy.trim="loginForm.confirm_password"></el-input>
</el-form-item>
<el-form-item>
<label class="form-label">验证码</label>
<el-row :gutter="10">
<el-col :span="14">
<el-input type="text"></el-input>
</el-col>
<el-col :span="10">
<el-button type="danger" class="el-button-block">获取验证码</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button type="primary" class="el-button-block">立即创建</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
// import { ref } from "vue";
export default {
name: "loginPage",
data() {
return {
loginForm: {
username: "",
password: "",
confirm_password: "",
},
tab_menu: [
{ type: "login", label: "登录" },
{ type: "regiester", label: "注册" },
],
current_menu: "",
}
},
methods: {
onToggleMenu(type) {
this.current_menu = type;
}
},
created() {
//初始化菜单选项
this.current_menu = this.tab_menu[0].type;
}
};
</script>
<style scoped>
#login {
height: 100vh;
background-color: #344a5f;
padding-top: 50px;
}
.form-warp {
width: 320px;
padding: 30px;
margin: auto;
background-color: #ffffff10;
border-radius: 5px;
}
/*.menu-tab {
text-align: center;
margin-bottom: 15px;
li {
display: inline-block;
padding: 10px 24px;
margin: 0 10px;
color: #fff;
font-size: 16px;
border-radius: 5px;
cursor: pointer;
&.current {
background-color: rgba(0, 0, 0, 0.1);
}
}
}*/
.menu-tab {
text-align: center;
margin-bottom: 15px;
}
.menu-tab li {
display: inline-block;
padding: 10px 24px;
margin: 0 10px;
color: #fff;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
.menu-tab .current {
background-color: rgba(0, 0, 0, 0.1);
}
.form-label {
display: block;
color: #fff;
font-size: 14px;
}
.el-button-block {
width: 100%;
}
</style>

View File

@@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

View File

@@ -0,0 +1,3 @@
VUE_APP_API = '/devApi'
VUE_APP_DEV_TARGET = 'http://v3.web-jshtml.cn/api'
# VUE_API_DEV_TARGET = 'http://v3.com/api'

View File

@@ -0,0 +1 @@
VUE_APP_API = 'http://v3.web-jshtml.cn/api'

View File

@@ -0,0 +1 @@
VUE_APP_API = 'http://v3.web-jshtml.cn/api'

View File

@@ -0,0 +1,14 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint",
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
};

View File

@@ -0,0 +1,22 @@
.DS_Store
node_modules
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,24 @@
# vue3-element-plus-admin
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
{
"name": "vue3-element-plus-admin",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"dev": "vue-cli-service serve --mode test",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"build:test": "vue-cli-service build --mode test"
},
"dependencies": {
"axios": "^0.21.0",
"core-js": "^3.6.5",
"element-plus": "^2.2.26",
"js-cookie": "^2.2.1",
"js-sha1": "^0.6.0",
"vue": "^3.2.27",
"vue-router": "^4.1.6",
"vuex": "^4.0.2",
"wangeditor": "^4.7.12"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"prettier": "^2.2.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"svg-sprite-loader": "^5.2.1",
"unplugin-auto-import": "^0.5.11",
"unplugin-element-plus": "^0.2.0",
"unplugin-vue-components": "^0.17.17"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- vue3最新版本 -->
<script src="https://unpkg.com/vue@next"></script>
<!-- 引入Element Plus UI 组件库 -->
<link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
<script src="//unpkg.com/element-plus"></script>
<!-- 语言包 -->
<script src="https://unpkg.com/browse/element-plus@1.2.0-beta.4/es/locale/lang/zh-cn.mjs"></script>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,21 @@
<template>
<el-config-provider :locale="locale">
<router-view />
</el-config-provider>
</template>
<script>
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
export default {
name: "App",
setup() {
// 切换为中文
let locale = zhCn
return {
locale
}
}
}
</script>
<style lang="scss">
#app{}
</style>

View File

@@ -0,0 +1,37 @@
import instance from "@/utils/request"; // 引入拦截器
/** 注册 */
export function Register(data = {}){
return instance.request({
method: "post",
url: "/register/",
data
})
}
/** 登录 */
export function Login(data = {}){
return instance.request({
method: "post",
url: "/login/",
data
})
}
/** 登出 */
export function Logout(data = {}){
return instance.request({
method: "post",
url: "/logout/",
data
})
}
/** 路由权限 */
export function GetPermission(data = {}){
return instance.request({
method: "post",
url: "/permission",
data
})
}

View File

@@ -0,0 +1,72 @@
import instance from "@/utils/request"; // 引入拦截器
/** 获取验证码 */
export function GetCode(data = {}){
return instance.request({
method: "post",
url: "/getCode/",
data
})
}
/** http 状态码异常演示接口 */
export function ErrorHttp(data = {}){
return instance.request({
method: "post",
url: "/error/",
data
})
}
/**
* BasisTable组件公共接口
* @param { String } method 请求类型
* @param { String } url 请求地址
* @param { Object } data 请求参数
*/
export function TableData(params = {}){
return instance.request({
method: params.method,
url: params.url,
data: params.data
})
}
/**
*
* @param { method } String 请求类型
* @param { url } String 请求地址
* @param { data } Object 请求参数
* @description BasisTable组件公共接口
*/
export function SwitchStatus(params = {}){
return instance.request({
method: params.method,
url: params.url,
data: params.data
})
}
/**
*
* @param { method } String 请求类型
* @param { url } String 请求地址
* @param { data } Object 请求参数
* @description BasisTable组件公共接口
*/
export function CommonApi(params = {}){
return instance.request({
method: params.method,
url: params.url,
data: params.data
})
}
/** 文件上传 */
export function UploadFile(data = {}){
return instance.request({
method: "post",
url: "/upload",
data
})
}

View File

@@ -0,0 +1,102 @@
import instance from "@/utils/request"; // 引入拦截器
/** 一级分类添加 */
export function firstCategoryAdd(data = {}){
return instance.request({
method: "post",
url: "/news/addFirstCategory/",
data
})
}
/** 获取分类 */
export function GetCategory(data = {}){
return instance.request({
method: "post",
url: "/news/getCategoryAll/",
data
})
}
/** 子级分类添加 */
export function ChildCategoryAdd(data = {}){
return instance.request({
method: "post",
url: "/news/addChildrenCategory/",
data
})
}
/** 分类编辑 */
export function CategoryEdit(data = {}){
return instance.request({
method: "post",
url: "/news/editCategory/",
data
})
}
/** 分类删除 */
export function CategoryDel(data = {}){
return instance.request({
method: "post",
url: "/news/deleteCategory/",
data
})
}
/** 添加信息 */
export function InfoCreate(data = {}){
return instance.request({
method: "post",
url: "/news/add/",
data
})
}
/** 获取列表 */
export function GetTableList(data = {}){
return instance.request({
method: "post",
url: "/news/getList/",
data
})
}
/** 发布状态 */
export function Status(data = {}){
return instance.request({
method: "post",
url: "/news/status/",
data
})
}
/** 删除 */
export function Delete(data = {}){
return instance.request({
method: "post",
url: "/news/delete/",
data
})
}
/** 获取详情 */
export function GetDetailed(data = {}){
return instance.request({
method: "get",
url: "/news/detailed/",
params: data
})
}
/** 信息修改 */
export function InfoEdit(data = {}){
return instance.request({
method: "post",
url: "/news/editInfo/",
data
})
}

View File

@@ -0,0 +1,46 @@
import instance from "@/utils/request"; // 引入拦截器
/** 创建菜单 */
export function MenuCreate(data = {}){
return instance.request({
method: "post",
url: "/menu/create",
data
})
}
/** 获取菜单详情 */
export function MenuDetailed(data = {}){
return instance.request({
method: "post",
url: "/menu/detailed",
data
})
}
/** 菜单编辑 */
export function MenuUpdate(data = {}){
return instance.request({
method: "post",
url: "/menu/update",
data
})
}
/** 菜单列表 */
export function MenuList(data = {}){
return instance.request({
method: "post",
url: "/menu/listsTree",
data
})
}
/** 菜单列表树状数据 */
export function MenuListTree(data = {}){
return instance.request({
method: "post",
url: "/menu/listsTree",
data
})
}

View File

@@ -0,0 +1,75 @@
const apiUrl = {
// 信息模块
info: { // 模块键名
list: { // 列表数据接口
url: "/news/getList/",
method: "post"
},
info_status: {
url: "/news/status/",
method: "post"
},
delete: {
method: "post",
url: "/news/delete/"
}
},
// 级联选择器模块
cascader: {
category: {
url: "/news/getCategoryAll/",
method: "post"
}
},
// 菜单模块
menu: {
list: {
url: "/menu/lists",
method: "post"
},
delete: {
method: "post",
url: "/menu/remove"
},
hidden_status: {
url: "/menu/statusHidden",
method: "post"
},
disabled_status: {
url: "/menu/statusDisabled",
method: "post"
}
},
// 角色模块
role: {
status: {
url: "/role/status",
method: "post"
},
delete: {
url: "/role/delete",
method: "post"
},
list: {
url: "/role/lists",
method: "post"
}
},
// 用户模块
user: {
status: {
url: "/user/status",
method: "post"
},
delete: {
method: "post",
url: "/user/remove"
},
list: {
url: "/user/lists",
method: "post"
}
}
}
export default apiUrl;

View File

@@ -0,0 +1,38 @@
import instance from "@/utils/request"; // 引入拦截器
/** 创建角色 */
export function RoleCreate(data = {}){
return instance.request({
method: "post",
url: "/role/create",
data
})
}
/** 角色详情 */
export function RoleDetailed(data = {}){
return instance.request({
method: "post",
url: "/role/detailed",
data
})
}
/** 角色修改 */
export function RoleUpdate(data = {}){
return instance.request({
method: "post",
url: "/role/update",
data
})
}
/** 角色列表 */
export function RoleListAll(data = {}){
return instance.request({
method: "post",
url: "/role/all",
data
})
}

View File

@@ -0,0 +1,36 @@
import instance from "@/utils/request"; // 引入拦截器
/** 创建用户 */
export function UserCreate(data = {}){
return instance.request({
method: "post",
url: "/user/create",
data
})
}
/** 用户详情 */
export function UserDetailed(data = {}){
return instance.request({
method: "post",
url: "/user/detailed",
data
})
}
/** 用户修改 */
export function UserUpdate(data = {}){
return instance.request({
method: "post",
url: "/user/update",
data
})
}
/** 修改密码 */
export function UpdatePass(data = {}){
return instance.request({
method: "post",
url: "/user/password",
data
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,125 @@
var zhCn = {
name: "zh-cn",
el: {
colorpicker: {
confirm: "\u786E\u5B9A",
clear: "\u6E05\u7A7A"
},
datepicker: {
now: "\u6B64\u523B",
today: "\u4ECA\u5929",
cancel: "\u53D6\u6D88",
clear: "\u6E05\u7A7A",
confirm: "\u786E\u5B9A",
selectDate: "\u9009\u62E9\u65E5\u671F",
selectTime: "\u9009\u62E9\u65F6\u95F4",
startDate: "\u5F00\u59CB\u65E5\u671F",
startTime: "\u5F00\u59CB\u65F6\u95F4",
endDate: "\u7ED3\u675F\u65E5\u671F",
endTime: "\u7ED3\u675F\u65F6\u95F4",
prevYear: "\u524D\u4E00\u5E74",
nextYear: "\u540E\u4E00\u5E74",
prevMonth: "\u4E0A\u4E2A\u6708",
nextMonth: "\u4E0B\u4E2A\u6708",
year: "\u5E74",
month1: "1 \u6708",
month2: "2 \u6708",
month3: "3 \u6708",
month4: "4 \u6708",
month5: "5 \u6708",
month6: "6 \u6708",
month7: "7 \u6708",
month8: "8 \u6708",
month9: "9 \u6708",
month10: "10 \u6708",
month11: "11 \u6708",
month12: "12 \u6708",
weeks: {
sun: "\u65E5",
mon: "\u4E00",
tue: "\u4E8C",
wed: "\u4E09",
thu: "\u56DB",
fri: "\u4E94",
sat: "\u516D"
},
months: {
jan: "\u4E00\u6708",
feb: "\u4E8C\u6708",
mar: "\u4E09\u6708",
apr: "\u56DB\u6708",
may: "\u4E94\u6708",
jun: "\u516D\u6708",
jul: "\u4E03\u6708",
aug: "\u516B\u6708",
sep: "\u4E5D\u6708",
oct: "\u5341\u6708",
nov: "\u5341\u4E00\u6708",
dec: "\u5341\u4E8C\u6708"
}
},
select: {
loading: "\u52A0\u8F7D\u4E2D",
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
noData: "\u65E0\u6570\u636E",
placeholder: "\u8BF7\u9009\u62E9"
},
cascader: {
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
loading: "\u52A0\u8F7D\u4E2D",
placeholder: "\u8BF7\u9009\u62E9",
noData: "\u6682\u65E0\u6570\u636E"
},
pagination: {
goto: "\u524D\u5F80",
pagesize: "\u6761/\u9875",
total: "\u5171 {total} \u6761",
pageClassifier: "\u9875",
deprecationWarning: "\u4F60\u4F7F\u7528\u4E86\u4E00\u4E9B\u5DF2\u88AB\u5E9F\u5F03\u7684\u7528\u6CD5\uFF0C\u8BF7\u53C2\u8003 el-pagination \u7684\u5B98\u65B9\u6587\u6863"
},
messagebox: {
title: "\u63D0\u793A",
confirm: "\u786E\u5B9A",
cancel: "\u53D6\u6D88",
error: "\u8F93\u5165\u7684\u6570\u636E\u4E0D\u5408\u6CD5!"
},
upload: {
deleteTip: "\u6309 delete \u952E\u53EF\u5220\u9664",
delete: "\u5220\u9664",
preview: "\u67E5\u770B\u56FE\u7247",
continue: "\u7EE7\u7EED\u4E0A\u4F20"
},
table: {
emptyText: "\u6682\u65E0\u6570\u636E",
confirmFilter: "\u7B5B\u9009",
resetFilter: "\u91CD\u7F6E",
clearFilter: "\u5168\u90E8",
sumText: "\u5408\u8BA1"
},
tree: {
emptyText: "\u6682\u65E0\u6570\u636E"
},
transfer: {
noMatch: "\u65E0\u5339\u914D\u6570\u636E",
noData: "\u65E0\u6570\u636E",
titles: ["\u5217\u8868 1", "\u5217\u8868 2"],
filterPlaceholder: "\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9",
noCheckedFormat: "\u5171 {total} \u9879",
hasCheckedFormat: "\u5DF2\u9009 {checked}/{total} \u9879"
},
image: {
error: "\u52A0\u8F7D\u5931\u8D25"
},
pageHeader: {
title: "\u8FD4\u56DE"
},
popconfirm: {
confirmButtonText: "\u786E\u5B9A",
cancelButtonText: "\u53D6\u6D88"
}
}
};
export { zhCn as default };
//# sourceMappingURL=zh-cn.mjs.map

View File

@@ -0,0 +1,21 @@
<template>
<div class="home">
<h1>这里是测试文字</h1>
<ul>
<li>无序列表LI</li>
<li>无序列表LI</li>
</ul>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,12 @@
import { h } from 'vue';
const Input = (props, context) => {
return () => {
return (
<div>sdfdsf</div>
)
}
}
Input.props = ['level'];
export default Input;

View File

@@ -0,0 +1,34 @@
export function relationHook(){
/**
* @param { String } value 控制方的选项 value
* @param { Array } data 关联关系的数据配置 relation_hidden
* @param { Object } props 显示/隐藏对象集保
*/
const HiddenItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
// 选项为 1 时
// props["title"] = objValue["1"] => true
// props["iamge_url"] = objValue["1"] => true
// 选项为 0 时
// props["title"] = objValue["0"] => true
// props["iamge_url"] = objValue["0"] => undefined
})
}
}
const DisabledItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
})
}
}
return { HiddenItem, DisabledItem };
}

View File

@@ -0,0 +1,75 @@
import { moneyNValid } from "@/utils/amountFormat";
/**
* @returns BasisForm组件创建校验规则
*/
export function rulesHook() {
/**
* @param {*} data 配置额外组件
*/
const InitRules = (data = []) => {
if (data.length === 0) {
return false;
}
// 判断是否有required属性
data.forEach((item) => {
// 初始化规则数组
let rulesArr = [];
if (item.required) {
const json = {
required: true,
message: item.required_msg || messageType(item).msg,
trigger: "blur",
};
rulesArr.push(json);
}
// 生成placeholder
item.placeholder = item.placeholder || messageType(item).placeholder;
// 是否有其他的校验规则
const rule = item.rule;
if (rule && Array.isArray(rule) && rule.length > 0) {
rulesArr = rulesArr.concat(rule);
}
// price
if (item.type === "price" && !item.rule) {
const json = { validator: moneyNValid, trigger: "blur" };
rulesArr.push(json);
}
// 定义rules属性赋值
item.rules = rulesArr;
});
return data;
};
/**
*
* @description 提示文本
*/
const messageType = (data) => {
let msg = "";
switch (data.type) {
case "input":
case "wangeditor":
case "price":
case "range":
case "textarea":
msg = "请输入";
break;
case "upload":
msg = "请上传";
break;
case "radio":
case "checkbox":
case "select":
case "date":
case "cascader":
msg = "请选择";
break;
default:
msg = "未定义";
}
return {
msg: `${msg}${data.label}`,
placeholder: `${msg}${data.label}`,
};
};
return { InitRules };
}

View File

@@ -0,0 +1,180 @@
<template>
<el-form ref="formDom" :model="field" :class="[{'label-gray': labelGray}]" :label-position="labelPosition" element-loading-text="加载中请稍候" :label-width="label_width" >
<el-row :gutter="gutter">
<template v-for="item in form_item" :key="item.prop">
<el-col v-if="!form_hidden[item.prop]" :span="item.col || 24">
<component :is="'htmlComponent'" v-if="item.type === 'html'" :prop="item" />
<el-form-item v-else :label="item.label" :prop="item.prop" :rules="item.rules">
<!--自定义label-->
<template v-if="item.label_html" #label>
<span class="custom-label">
<div>
<span style="position: relative;">
<label class="form-center" v-html="item.label_html.label || ''" />
<em v-if="item.label_html.spot" class="spot"></em>
</span>
</div>
<span :class="item.label_html.suffix_class" @click="item.label_html.suffix_callback && item.label_html.suffix_callback(item)" v-html="item.label_html.suffix_text || ''" />
</span>
</template>
<!--动态组件-->
<div v-show="control" style="width:100%">
<component :is="item.type + 'Component'" v-if="item.type !== 'slot'" v-model:value="field[item.prop]" v-model:second-value="field[item.secondprop]" v-model:select-value="field[item.selProp]" :data="item" :header="item" :disabled="disabled[item.prop]" @callback="componentCallback" />
<!--插槽-->
<template v-else>
<slot :name="item.slot_name" />
<span v-if="item.tip" class="tip">{{ item.tip }}</span>
</template>
</div>
<div v-show="!control">
<slot v-if="item.slot_text" :name="item.slot_text" />
<span v-else>{{ fieldText[item.prop] || '--' }}</span>
</div>
</el-form-item>
</el-col>
</template>
</el-row>
<!-- button -->
<div :class="['form-button', {'dialog-footer': dialog}]">
<template v-if="form_button && form_button.length > 0">
<template v-for="button in form_button" :key="button.key">
<template v-if="!button.hidden">
<CdButton v-if="button.key === 'submit'" :loading="load || loading" margin="0 0 0 16px" :type="button.type" @click="button.callback ? button.callback() : handlerFormValidate()">
{{ button.label }}
</CdButton>
<CdButton v-else-if="button.key === 'cancel'" margin="0 0 0 16px" border @click="button.callback ? button.callback() : handlerFormCancel()">
{{ button.label }}
</CdButton>
<CdButton v-else :type="button.type" margin="0 0 0 16px" @click="button.callback && button.callback()">{{ button.label }}</CdButton>
</template>
</template>
</template>
</div>
</el-form>
</template>
<script>
import { defineAsyncComponent, onBeforeUnmount, reactive, ref, watch } from "vue";
import { checkDataType } from "../common";
// hook
import { rulesHook } from "./hook/rulesHook";
import { relationHook } from "./hook/relationHook";
// props
import { props } from "./props";
// require import components
const files = require.context("@/cd-components/cd-control", true, /\index.vue$/);
const modules = {};
files.keys().forEach((key) => {
const name = key.split("/");
modules[name[1] + "Component"] = files(key).default || files(key);
});
export default {
name: "BasisForm",
components: {
...modules,
CdButton: defineAsyncComponent(() => import("../cd-button")),
},
props,
emits: ["callback", "cancel"],
setup(props, { emit }) {
const { InitRules } = rulesHook();
const { HiddenItem, DisabledItem } = relationHook();
// 校验规则处理
const form_item = reactive(InitRules(props.item));
const label_width = ref(props.labelWidth);
const form_button = reactive(props.button);
const form_disabled = reactive(props.disabled);
const load = ref(false)
let form_hidden = ref({});
watch(() => props.hidden, (value) => {
form_hidden.value = value
})
// 表单提交验证
const formDom = ref(null);
const handlerFormValidate = () => {
formDom.value.validate((valid) => {
if(valid) {
if (checkDataType().isFunction(props.beforeChange)) {
load.value = true
props.beforeChange().then(() => {
load.value = false
}).catch(() => {
load.value = false
})
return false
}
emit("callback")
}
});
};
const handlerFormCancel = () => {
handlerFormReset()
emit("cancel");
};
/**
* 重置表单
*/
const handlerFormReset = () => {
formDom.value.resetFields();
};
// change 事件
const componentCallback = (params) => {};
/**
* 生命周期
*/
onBeforeUnmount(() => {
form_hidden.value = {}
handlerFormReset()
})
return {
form_item,
label_width,
form_button,
form_hidden,
form_disabled,
formDom,
load,
handlerFormValidate,
handlerFormCancel,
handlerFormReset,
componentCallback,
};
},
};
</script>
<style lang="scss" scoped>
.form-button {
width: 100%;
}
.form-left {
text-align: left;
}
.form-center {
text-align: center;
}
.form-right {
text-align: right;
}
.custom-label {
position: relative;
> span {
float: right;
}
}
::v-deep .color-lable-danger {
width: 6px;
height: 6px;
background: #ff6060;
border-radius: 50%;
position: absolute;
right: -7px;
top: 7px;
}
:deep(.el-input__wrapper) {
border-radius: 0;
}
</style>

View File

@@ -0,0 +1,57 @@
export const props = {
item: {
type: Array,
default: () => [],
},
button: {
type: Array,
default: () => [],
},
labelWidth: {
type: [String, Number],
default: "100px",
},
field: {
type: Object,
default: () => ({}),
},
fieldText: {
type: Object,
default: () => ({}),
},
loading: {
type: Boolean,
default: false,
},
hidden: {
type: Object,
default: () => ({}),
},
disabled: {
type: Object,
default: () => ({}),
},
position: {
type: String,
default: "left",
},
labelPosition: {
type: String,
default: "right",
},
gutter: {
type: Number,
default: 0,
},
beforeChange: Function,
dialog: Boolean,
labelGray: Boolean,
control: {
type: Boolean,
default: true,
},
fieldData: {
type: Object,
default: () => ({}),
},
};

View File

@@ -0,0 +1,86 @@
<template>
<el-cascader :style="{width}" :disabled="disabled" v-model="data.value" :options="data.options" :props="data.props" @change="handlerChange"></el-cascader>
</template>
<script>
import { reactive, onBeforeMount, watch } from 'vue';
// API
import { CommonApi } from "@/api/common";
// requestUrl
import ApiUrl from "@/api/requestUrl";
export default {
name: 'BasisCascader',
components: {},
props: {
cascaderProps: {
type: Object,
default: () => ({})
},
url: {
type: String,
default: ""
},
method: {
type: String,
default: "post"
},
data: {
type: Object,
default: () => ({})
},
value: {
type: [String, Number],
default: 0
},
width: {
type: String,
default: "100%"
},
disabled: {
type: Boolean,
default: false
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
const data = reactive({
props: props.data.props,
options: [],
value: 0
})
const handlerChange = (value) => {
// 获取最后一项
const val = value[value.length - 1];
// 返回
emit("update:value", val);
}
/** 获取列表 */
const getData = () => {
const url = ApiUrl?.cascader?.[props.url || props?.data?.url]?.url;
const method = ApiUrl?.cascader?.[props.url]?.method || "post";
if(!url) {
throw new Error("url地址不存在");
}
// 参数
const request_data = {
url,
method,
data: props.data,
}
CommonApi(request_data).then(response => {
data.options = response.data;
})
}
/** 生命周期挂载之前 */
onBeforeMount(() => {
getData();
})
/** 监听 */
watch(() => props.value, (newValue) => {
data.value = newValue
} )
return { data, handlerChange }
}
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<el-checkbox-group v-model="value" :disabled="form_disabled[data.prop]" @change="handlerChange">
<el-checkbox v-for="checkbox in data.options" :label="checkbox.value" :key="checkbox.value">{{ checkbox.label }}</el-checkbox>
</el-checkbox-group>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "CheckboxComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: Array,
default: () => ([])
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'checkbox',
value: val,
data: props.data
})
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<el-date-picker :style="`width: ${data.width}`"
v-model="value"
:disabled="disabled"
:type="data.date_type || 'datetime'"
:format="data.date_format || 'YYYY-MM-DD HH:mm:ss'"
:placeholder="data.placeholder || '选择日期时间'"
:start-placeholder="data.start_placeholder || '请选择开始日期'"
:end-placeholder="data.end_placeholder || '请选择结束日期'"
:range-separator="data.range_placeholder || '-'"
@change="handlerChange"
>
</el-date-picker>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "DateComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Array, String],
default: ''
},
disabled: Boolean
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'date',
value: val,
data: props.data
})
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,49 @@
<template>
<el-input
v-model="value"
:type="data.value_type || 'text'"
v-on:input="handlerEnter"
debounce
:placeholder="placeholder"
:maxlength="prop.maxlength"
:minlength="prop.minlength || 0"
/>
</template>
<script>
import { reactive, toRefs, watchEffect } from 'vue';
export default {
name: "InputComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
prop: props.data,
placeholder: props.data.placeholder
})
const handlerEnter = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerEnter
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,41 @@
<template>
<el-input-number v-model="value" :min="data.min || 0" :max="data.max || 99999999" @change="handleChange" />
</template>
<script>
import { reactive, toRefs, watchEffect } from 'vue';
export default {
name: "InputComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handleChange = (val) => {
console.log(val)
emit("update:value", val)
}
return {
...toRefs(data),
handleChange
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,74 @@
<template>
<el-select v-model="key" placeholder="请选择" class="key-word-select" @change="handlerChange">
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"></el-option>
</el-select>
<el-input v-model="value" class="width-200" :placeholder="placeholder" v-on:input="callback"></el-input>
</template>
<script>
import { reactive, toRefs, watch } from "vue";
export default {
name: "KeyWord",
props: {
data: {
type: Object,
default: () => ({})
},
},
emits: ["callback"],
setup(props, { emit }){
const data = reactive({
key: "",
value: "",
placeholder: "请输入搜索的关键字",
options: props.data.options
})
/** 下拉事件 */
const handlerChange = (value) => {
// 清除input输入框文本
data.value = "";
// 更新input输入框占位文本
updatePlaceholder();
// 回调
callback()
}
/** 事件回调 */
const callback = () => {
emit("callback", {
type: 'keyword',
value: { key: data.key, value: data.value },
formItem: props.data
})
}
/** update Placeholder */
const updatePlaceholder =() => {
const item = data.options.filter(item => item.value === data.key)[0];
data.placeholder = `请输入${item.label}`;
}
/** 清除 */
const handlerClear = () => {
data.key = "";
data.value = "";
}
return {
...toRefs(data),
handlerChange,
handlerClear,
callback
}
}
}
</script>
<style lang="scss" scoped>
.key-word-select {
width: 100px;
margin-right: 10px;
}
.width-200 { width: 200px; }
</style>

View File

@@ -0,0 +1,57 @@
<template>
<template v-if="!data.options && data.callback && data.callback(data)"></template>
<el-radio-group v-else v-model="value" :disabled="disabled" @change="handlerChange">
<el-radio
v-for="radio in data.options"
:key="radio[data.key_value] || radio.value"
:label="radio[data.key_value] || radio.value">
{{ radio[data.key_label] || radio.label }}
</el-radio>
</el-radio-group>
</template>
<script>
import { reactive, toRefs, watch } from 'vue';
// API
import { RoleListAll } from "@/api/role";
export default {
name: "DateComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
},
disabled: Boolean
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const dataa = reactive({
value: '',
data: props.data
})
watch(() => props.data, () => {
RoleListAll().then(response => {
console.log(111)
}, {
deep: true,
immediate: true
})
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'radio',
value: val,
formItem: props.data
})
}
return {
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<el-select v-model="value" :disabled="disabled" :style="`width: ${data.width || '100%'}`" @change="handlerChange">
<el-option v-for="select in data.options" :key="select.value" :label="select.label" :value="select.value"></el-option>
</el-select>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "SelectComponent",
props: {
data: {
type: Object,
default: () => ({})
},
disabled: {
type: Boolean,
default: false
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,80 @@
<template>
<el-switch
v-model="init_data.value"
:loading="init_data.loading"
:before-change="handlerSwitch"
>
</el-switch>
</template>
<script>
import { reactive, getCurrentInstance, watch } from 'vue';
// requestUrl
import ApiUrl from "@/api/requestUrl";
//API
import { SwitchStatus } from "@/api/common";
export default {
name: 'SwitchComponent',
props: {
data: {
type: Object,
default: () => ({})
},
config: {
type: Object,
default: () => ({})
}
},
emits: ['update:value', 'callback'],
setup(props){
// 配置信息
const config = reactive(props.config);
// 数据
const data = reactive(props.data);
// 初始值
const init_data = reactive({
value: data[config.prop],
loading: false
});
// 监听数据
watch(() => props.data, (newValue, oldValue) => {
init_data.value = newValue[config.prop]
})
// 获取实例上下文
const { proxy } = getCurrentInstance();
// 事件
const handlerSwitch = (value) => {
init_data.loading = true;
// 请求参数
const url = config.api_url || ApiUrl?.[config.api_module]?.[config.api_key]?.url;
const method = config.method || ApiUrl?.[config.api_module]?.[config.api_key]?.method || "post";
if(!url) {
console.log("请求接口不存在");
return false
}
// 请求参数
const key_id = config.key_id || 'id'
const request_data = {
url,
method,
data: {
[key_id]: data[key_id], // 等价于["id"]: data["id"]
[config.prop]: !init_data.value // 等价于["status"]: value
}
}
return new Promise((resolve, reject) => {
SwitchStatus(request_data).then(response => {
proxy.$message.success(response.message);
data.status = value;
init_data.loading = false;
resolve(true)
}).catch(error => {
init_data.loading = false;
reject(false)
})
})
}
return { config, init_data, handlerSwitch }
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<el-input
v-model="value"
type="textarea"
debounce
:rows="data.rows || 5"
:disabled="form_disabled[data.prop]"
:style="`width: ${data.width || '100%'}`"
:placeholder="data.placeholder"
@change="handlerChange"
/>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "TextareaComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: String,
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,86 @@
<template>
<el-upload
class="avatar-uploader"
action="#"
:http-request="handlerUpload"
:before-upload="handlerBeforeOnUpload"
:on-error="handlerOnError"
:show-file-list="false"
:disabled="disabled"
>
<img v-if="data.image_url" :src="data.image_url" class="avatar" />
<span v-else>+</span>
</el-upload>
</template>
<script>
import { reactive, getCurrentInstance, watch } from 'vue';
import { UploadFile } from "@/api/common";
export default {
name: "BasisUpload",
components: {},
props: {
value: {
type: String,
default: ""
},
disabled: {
type: Boolean,
default: false
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
// 获取实例上下文
const { proxy } = getCurrentInstance();
const data = reactive({
image_url: ""
})
const handlerOnError = (res, file) => {}
const handlerBeforeOnUpload = async (file) => {
const isLt2M = file.size / 1024 / 1024 < 2; // 限制文件大小不能大于 2M
if (!isLt2M) {
proxy.$message.error("上传图片大小不能超过 2MB!");
return false;
}
return isJPG && isLt2M;
}
const handlerUpload = async (params) => {
const file = params.file;
// 实例化表单对象
const form = new FormData();
// 表单添加 files 字段
form.append("files", file);
// 上传接口
try{
const url = await startUpload(form)
data.image_url = url;
emit("update:imageUrl", url)
}catch{
console.log("上传失败")
}
}
/**
* 开始文件上传
*/
const startUpload = (form) => {
return new Promise((resolve, reject) => {
UploadFile(form).then(response => {
resolve(response.data.files_path);
})
})
}
watch(() => props.value, (newValue, oldValue) => {
data.image_url = newValue
})
return {
data,
handlerUpload,
handlerOnError,
handlerBeforeOnUpload
}
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div ref="editor"></div>
</template>
<script>
import { ref, onMounted, watch, nextTick } from 'vue';
import WangEditor from 'wangeditor';
// cookies
import { getToken } from "@/utils/cookies"; // 这是封装好的方法
export default {
name: 'Wangeditor',
components: {},
props: {
value: {
type: String,
default: ""
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
let html = ref(null)
// 内容
let content = ref('');
// 富文本DOM元素
const editor = ref(null);
// 富文本对象
let editor_instance = null;
// 生命周期
onMounted(() => {
nextTick(() => {
// 创建富文本对象
editor_instance = new WangEditor(editor.value);
// 图片上传配置
editor_instance.config.uploadImgServer = process.env.VUE_APP_DEV_TARGET + '/upload' // 上传图片的接口地址
editor_instance.config.uploadFileName = 'files' // formdata中的name属性
editor_instance.config.uploadImgHeaders = {
Token: getToken() // 设置请求头
}
editor_instance.config.uploadImgHooks = {
// 图片上传并返回结果,但图片插入错误时触发
fail: function (xhr, editor, result) {
console.log(result)
},
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
insertImgFn(result.data.files_path)
}
}
// 配置change事件
Object.assign(editor_instance.config, {
onchange(value) {
content.value = value;
emit("update:value", value);
},
});
// 创建
editor_instance.create();
})
})
watch(() => props.value, (newContent, oldContent) => {
editor_instance.txt.html(newContent);
})
return { editor }
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,34 @@
export function relationHook(){
/**
* @param { String } value 控制方的选项 value
* @param { Array } data 关联关系的数据配置 relation_hidden
* @param { Object } props 显示/隐藏对象集保
*/
const HiddenItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
// 选项为 1 时
// props["title"] = objValue["1"] => true
// props["iamge_url"] = objValue["1"] => true
// 选项为 0 时
// props["title"] = objValue["0"] => true
// props["iamge_url"] = objValue["0"] => undefined
})
}
}
const DisabledItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
})
}
}
return { HiddenItem, DisabledItem };
}

View File

@@ -0,0 +1,88 @@
import { reactive } from "vue";
// 验证
import { validate_email, validate_password } from "@/utils/validate";
/**
* @returns BasisForm组件创建校验规则
*/
export function rulesHook(){
/**
* @param {*} data 配置额外组件
*/
const InitRules = (data = []) => {
if(data.length === 0) { return false; }
// 判断是否有required属性
data.forEach(item => {
//初始化规则数组
let rulesArr = [];
if(item.required) {
let json = { required: true, message: item.message || messageType(item), trigger: 'change' }
rulesArr.push(json)
}
// 是否有其他的校验规则
const rule = item.rule;
if(rule && Array.isArray(rule) && rule.length > 0) {
rulesArr = rulesArr.concat(rule);
}
// 用户名
if(item.value_type === 'username') {
const rule = {
validator(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用户名"));
}else if(!validate_email(value)) {
callback(new Error("邮箱格式不正确"));
} else {
callback();
}
}
}
rulesArr = rulesArr.concat(rule);
}
// 密码
if(item.value_type === 'password') {
const rule = {
validator(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用密码"));
}else if(!validate_password(value)) {
callback(new Error("请输入>=6并且<=20位的密码包含数字、字母"));
} else {
callback();
}
}
}
rulesArr = rulesArr.concat(rule);
}
// 定义rules属性赋值
item.rules = rulesArr;
});
return data;
}
/**
*
* @description 提示文本
*/
const messageType = (data) => {
let msg = "";
switch(data.type){
case "input":
case "wangeditor":
msg = "请输入";
break;
case "upload":
msg = "请上传";
break;
case "radio":
case "checkbox":
case "select":
case "date":
case "cascader":
msg = "请选择";
break;
default:
msg = "未定义";
}
return `${msg}${data.label}`;
}
return { InitRules };
}

View File

@@ -0,0 +1,128 @@
<template>
<el-form ref="formDom" v-loading="loading" element-loading-text="加载中,请稍候" :label-width="label_width" :model="field">
<el-row>
<template v-for="item in form_item" :key="item.prop">
<el-col v-if="!form_hidden[item.prop]" :span="item.col || 24">
<el-form-item v-if="item.type !== 'slot'" :label="item.label" :prop="item.prop" :rules="item.rules">
<component
v-model:value="field[item.prop]"
:is="item.type + 'Component'"
:data="item"
:disabled="disabled[item.prop]"
@callback="componentCallback"
/>
</el-form-item>
<el-form-item v-else :label="item.label" :prop="item.prop" :rules="item.rules">
<slot :name="item.slot_name"></slot>
</el-form-item>
</el-col>
</template>
</el-row>
<!-- button -->
<el-form-item v-if="form_button && form_button.length > 0">
<el-button
v-for="item in form_button"
:key="item.key"
:type="item.type"
@click="item.callback ? item.callback() : handlerFormActive(item)"
>
{{ item.label }}
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { reactive, ref } from 'vue';
// hook
import { rulesHook } from "./hook/rulesHook";
import { relationHook } from "./hook/relationHook";
// require import components
const files = require.context('@c/control', true, /\index.vue$/);
const modules = {}
files.keys().forEach(key => {
const name = key.split("/")
modules[name[1]+'Component'] = files(key).default || files(key)
})
export default {
name: 'BasisForm',
components: modules,
props: {
item: {
type: Array,
default: () => ([])
},
button: {
type: Array,
default: () => ([])
},
labelWidth: {
type: [String, Number],
default: "100px"
},
field: {
type: Object,
default: () => ({})
},
hidden: {
type: Object,
default: () => ({})
},
disabled: {
type: Object,
default: () => ({})
},
loading: {
type: Boolean,
default: false
},
hidden: {
type: Object,
default: () => ({})
},
disabled: {
type: Object,
default: () => ({})
}
},
emits: ["callback"],
setup(props, { emit }){
const { InitRules } = rulesHook();
const { HiddenItem, DisabledItem } = relationHook();
// 校验规则处理
const form_item = reactive(InitRules(props.item));
const label_width = ref(props.labelWidth);
const form_button = reactive(props.button);
const form_hidden = reactive(props.hidden);
const form_disabled = reactive(props.disabled);
// 表单提交验证
const formDom = ref(null);
// form表单动作
const handlerFormActive = (data) => {
// 提交表单事件
if(data.key === 'submit') {
formDom.value.validate((valid) => valid && emit("callback"));
}
// 重置表单事件
if(data.key === 'reset') { handlerFormReset(); }
}
/**
* 重置表单
*/
const handlerFormReset = () => {
formDom.value.resetFields();
}
// change 事件
const componentCallback = (params) => {
if(params.formItem.relation_hidden) {
HiddenItem(params.value, params.formItem.relation_hidden, form_hidden)
}
if(params.formItem.relation_disabled) {
DisabledItem(params.value, params.formItem.relation_hidden, form_disabled)
}
}
return { formDom, form_item, label_width, form_button, form_hidden, form_disabled, formDom, handlerFormActive, componentCallback, handlerFormReset }
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,69 @@
<template>
<el-pagination
class="pull-right"
sizs="small"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
v-model:current-page="current_page"
:page-size="10"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</template>
<script>
import { ref, watchEffect, watch } from 'vue';
export default {
name: 'Pagination',
props: {
total: {
type: [Number, String],
default: 0
},
current: {
type: [Number, String],
default: 0
},
flag: {
type: Boolean,
default: false
},
},
emits: ["sizeChange", "currentChange", "update:flag"],
setup(props, { emit }){
// 当前页码
const current_page = ref(1);
// 总条数统计
const total = ref(props.total);
const page_status = ref(false);
// 监听props.total发生变化时更新total
watchEffect(() => {
total.value = props.total;
current_page.value = props.current;
})
watch(() => props.flag, (newValue) => {
page_status.value = newValue
})
// 页码事件
const handleSizeChange = (val) => {
page_status.value = true
emit("update:flag", true)
const params = {
pageNumber: 1,
pageSize: val
}
emit("sizeChange", params, 'page')
}
const handleCurrentChange = (val) => {
if(page_status.value) { return false }
const params = {
pageNumber: val
}
emit("currentChange", params, 'page')
}
return { total, current_page, handleSizeChange, handleCurrentChange }
}
}
</script>

View File

@@ -0,0 +1,130 @@
<template>
<div v-if="button_group.length > 0" class="form-search-button-group">
<el-button v-for="item in button_group" :key="item.id" :type="item.type" @click="item.callback && item.callback()">{{ item.label }}</el-button>
</div>
<el-form ref="searchForm" inline :label-width="label_width" :model="field">
<template v-if="form_item && form_item.length > 0">
<el-form-item v-for="item in form_item" :key="item.prop" :label="item.label" :prop="item.prop" :label-width="item.label_width || label_width">
<component :ref="componentDom" :key="item.type" v-model:value="field[item.prop]" :is="item.type + 'Component'" :data="item" @callback="componentCallback" />
</el-form-item>
<!-- button -->
<el-form-item>
<div class="pull-right">
<el-button type="danger" @click="handlerSearch">搜索</el-button>
<el-button v-if="button.reset_button" @click="handlerReset">重置</el-button>
</div>
</el-form-item>
</template>
</el-form>
</template>
<script>
import { reactive, ref, inject, toRefs, nextTick } from 'vue';
// require import components
const files = require.context('@c/control', true, /\index.vue$/);
const modules = {}
files.keys().forEach(key => {
const name = key.split("/")
modules[name[1]+'Component'] = files(key).default || files(key)
})
export default {
name: 'BasisForm',
components: modules,
props: {
item: {
type: Array,
default: () => ([])
},
labelWidth: {
type: [String, Number],
default: "100px"
},
field: {
type: Object,
default: () => ({})
},
button: {
type: Object,
default: () => ({})
},
button_group: {
type: Array,
default: () => ([])
},
button_col: {
type: Number,
default: 24
}
},
emits: ["callbackSearch"],
setup(props, { emit }){
const data = reactive({ key: "", value: "" });
// 获取父组件provide数据
const search_config = inject("search_config");
// label占位宽度
const label_width = ref(search_config?.label_width || props.labelWidth);
// 组件元素配置
const form_item = reactive(search_config?.form_item || props.item);
// 字段配置
const field = reactive(search_config?.form_data || props.field);
// 按钮配置
const button = reactive(search_config?.form_button || props.button);
// 按钮组配置
const button_group = reactive(search_config?.form_button_group || props.button_group);
// 按钮占位 col
const button_col = reactive(search_config?.button_col || props.button_col);
/**
* 获取动态组件的dom
*/
const keyword = ref(null)
const componentDom = el => {
console.log(el)
if(el && el.data.type === 'keyword') {
keyword.value = el
}
}
// 搜索
const handlerSearch = () => {
const data = formatRequest();
// 回调父组件方法
emit("callbackSearch", data, "search");
}
// 重置
const searchForm = ref(null);
const handlerReset = () => {
searchForm.value.resetFields();
// 调用关键字组件的clear方法
keyword.value && keyword.value.handlerClear()
emit("callbackSearch", {}, "search");
}
/** 参数格式化 */
const formatRequest = () => {
// 声明空的JSON对象临时存储数据
const request_data = {};
// 过滤在值存在的数据
for(let key in field) {
if(field[key] !== "") { request_data[key] = field[key]; }
}
// 判断关键字下拉选择是否有值存在
if(data.key && data.value) { request_data[data.key] = data.value; }
// 返回参数
return request_data;
}
// 回调
const componentCallback = (params) => {
if(params.type === 'keyword') {
data.key = params.value.key
data.value = params.value.value
}
}
return {
...toRefs(data),
form_item, field, label_width, button, componentCallback, handlerSearch, handlerReset, componentDom, searchForm, button_group, button_col
}
}
}
</script>
<style lang="scss" scoped>
.form-search-button-group { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,38 @@
<template>
<svg class="svg-class" :class="svgClassName">
<use :href="svgIcon"></use>
</svg>
</template>
<script>
import { ref, computed } from "vue";
export default {
name: "SvgIcon",
props: {
iconName: {
type: String,
default: ""
},
className: {
type: String,
default: ""
}
},
setup(props){
// className
const svgClassName = ref(props.className);
// svg 图标
const svgIcon = computed(() => `#icon-${props.iconName}`);
return {
svgClassName,
svgIcon
}
}
}
</script>
<style lang="scss">
.svg-class {
width: 1em;
height: 1em;
fill: currentColor;
}
</style>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608734152787" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1160" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M959.390493 527.761985c0 23.127743-18.419512 41.87369-41.138956 41.87369-12.335956 0-23.276123-5.640464-30.817889-14.393825L541.089525 202.729238c-16.065907-16.353456-42.113143-16.353456-58.179051 0L237.802034 452.204126c2.877537 5.636371 4.653997 11.926634 4.653997 18.690688l0 165.475775c0 0.328481-0.089028 0.635473-0.097214 0.961908l0.297782 0 0 163.003468c0 23.125697 18.506493 41.871643 41.337477 41.871643l82.568531 0L366.562607 677.738765c0-22.850427 18.520819-41.3692 41.3692-41.3692L614.775758 636.369565c22.848381 0 41.3692 18.518772 41.3692 41.3692 0 22.846334-18.520819 41.3692-41.3692 41.3692L449.299983 719.107965l0 165.473729c0 22.427802-17.875113 40.583301-40.142256 41.246403l0 0.121773L263.148305 925.94987c-57.115835 0-103.418394-46.864354-103.418394-104.678084L159.729911 637.331473l0.084934 0c-0.00614-0.326435-0.097214-0.633427-0.097214-0.961908L159.717631 531.680225l-23.162536 23.575952c-7.541767 8.745175-18.475793 14.379499-30.807656 14.379499-22.719444 0-41.138956-18.745946-41.138956-41.87369 0-11.862165 4.895497-22.520923 12.687974-30.141484l-0.237407-0.2415 362.215602-368.668571c40.164768-40.881083 105.285928-40.881083 145.450696 0l361.569896 368.010585-0.299829 0.305969C954.175725 504.680291 959.390493 515.563152 959.390493 527.761985zM614.733802 842.206584c0.443092 0 0.854461 0.119727 1.294482 0.132006l0-0.132006 122.683157 0c22.828938 0 41.337477-18.745946 41.337477-41.871643L780.048919 637.331473l0.534166 0c-0.016373-0.496304-0.145309-0.961908-0.145309-1.464351 0-23.125697 18.417465-41.87369 41.138956-41.87369 22.719444 0 41.136909 18.747993 41.136909 41.87369 0 0.502443-0.125867 0.968048-0.145309 1.464351l0.405229 0 0 183.940312c0 57.812707-46.299489 104.678084-103.418394 104.678084l-143.528927 0 0-0.132006c-0.441045 0.014326-0.852414 0.132006-1.294482 0.132006-22.719444 0-41.138956-18.7439-41.138956-41.869596C573.594847 860.9556 592.014358 842.206584 614.733802 842.206584z" p-id="1161"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608736466130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="791" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M782.27531852 936.77037037H241.72468148c-60.19602963 0-109.22666667-49.03063703-109.22666666-109.22666667V196.4562963c0-60.19602963 49.03063703-109.22666667 109.22666666-109.22666667h540.55063704c60.19602963 0 109.22666667 49.03063703 109.22666666 109.22666667v631.0874074c0 60.19602963-49.03063703 109.22666667-109.22666666 109.22666667zM241.72468148 160.04740741c-20.02488889 0-36.40888889 16.384-36.40888889 36.40888889v631.0874074c0 20.02488889 16.384 36.40888889 36.40888889 36.40888889h540.55063704c20.02488889 0 36.40888889-16.384 36.40888889-36.40888889V196.4562963c0-20.02488889-16.384-36.40888889-36.40888889-36.40888889H241.72468148z" p-id="792"></path><path d="M747.44414815 343.30548148H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.40888889s16.26263703-36.40888889 36.40888888-36.40888889h475.74281482c20.14625185 0 36.40888889 16.26263703 36.40888888 36.40888889s-16.26263703 36.40888889-36.40888888 36.40888889zM747.44414815 497.43644445H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.4088889s16.26263703-36.40888889 36.40888888-36.40888888h475.74281482c20.14625185 0 36.40888889 16.26263703 36.40888888 36.40888888s-16.26263703 36.40888889-36.40888888 36.4088889zM554.47703703 653.99466667H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.40888889s16.26263703-36.40888889 36.40888888-36.40888889h282.7757037c20.14625185 0 36.40888889 16.26263703 36.40888889 36.40888889s-16.26263703 36.40888889-36.40888889 36.40888889z" p-id="793"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621589152962" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2857" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M706.432 195.136c-14.656-9.536-34.56-5.44-44.224 9.408-9.536 14.848-5.376 34.688 9.472 44.288C772.032 314.048 832 424.448 832 544.192c0 193.92-157.504 351.68-351.04 351.68s-351.04-157.76-351.04-351.68c0-119.68 60.032-230.144 160.448-295.296 14.848-9.6 19.072-29.504 9.472-44.352C290.112 189.632 270.336 185.408 255.552 195.072 136.768 272.192 65.856 402.688 65.856 544.192 65.856 773.504 252.032 960 480.96 960S896 773.504 896 544.192C896 402.624 825.152 272.128 706.432 195.136zM480.96 572.16c17.664 0 32-14.656 32-32.832l0-380.8c0-18.112-14.336-32.768-32-32.768s-32 14.656-32 32.768l0 380.8C448.96 557.504 463.232 572.16 480.96 572.16z" p-id="2858"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621586114074" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2473" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><defs><style type="text/css"></style></defs><path d="M980.8 468.8H43.2C17.6 468.8 0 486.4 0 512s17.6 43.2 43.2 43.2h939.2c25.6 0 43.2-17.6 43.2-43.2-1.6-25.6-19.2-43.2-44.8-43.2zM980.8 768H43.2C17.6 768 0 785.6 0 811.2s17.6 43.2 43.2 43.2h939.2c25.6 0 43.2-17.6 43.2-43.2-1.6-25.6-19.2-43.2-44.8-43.2zM43.2 256h939.2c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2H43.2c-25.6 0-43.2 17.6-43.2 43.2S17.6 256 43.2 256z" p-id="2474"></path></svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627108347577" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4935" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
</style></defs><path d="M660.750044 997.266537c-14.811783 0-29.804198-5.418945-40.461457-15.173046-15.714941-14.45052-69.181866-55.995766-107.114482-55.995767-38.293879 0-91.580173 41.545246-107.114482 55.995767-16.256835 15.173046-42.809667 19.688834-63.221027 10.657259l-3.070736-1.445052-153.898042-89.231964c-17.701887-13.1861-27.094726-38.47451-22.217675-59.969659 4.696419-20.772623 13.908626-87.786911-5.057682-120.661845-18.966308-32.874934-81.64544-58.343976-102.0568-64.846711-20.41136-6.322103-37.932616-26.191568-41.545246-47.325454-0.361263-2.167578-9.212207-54.370083-9.212207-92.663962 0-38.293879 8.850944-90.315752 9.212207-92.663962 3.61263-21.133886 21.133886-41.003352 41.545246-47.325454s83.090492-31.791145 102.0568-64.84671c19.146939-33.055565 9.934733-99.889222 5.057682-120.661845-4.877051-21.675781 4.335156-46.78356 22.217675-59.96966l2.890104-1.986946 153.898042-88.8707c20.41136-9.031575 46.964191-4.515788 63.221027 10.657258 15.714941 14.45052 69.181866 55.995766 107.114482 55.995767 38.293879 0 91.580173-41.545246 107.114483-55.995767 16.256835-15.173046 42.809667-19.688834 63.221026-10.657258l3.070736 1.445052 153.898042 89.231963c17.701887 13.1861 27.094726 38.47451 22.217675 59.969659-4.696419 20.772623-13.908626 87.786911 5.057682 120.661846 18.966308 32.874934 81.64544 58.343976 102.0568 64.84671 20.41136 6.322103 37.932616 26.191568 41.545246 47.325454 0.361263 2.167578 9.212207 54.370083 9.212207 92.663962 0 38.293879-8.850944 90.315752-9.212207 92.663962-3.61263 21.133886-21.133886 41.003352-41.545246 47.325454s-83.090492 31.791145-102.0568 64.84671c-19.146939 33.055565-9.934733 99.889222-5.057682 120.661845 4.877051 21.495149-4.335156 46.78356-22.217675 59.96966l-2.890104 1.986946-153.898042 88.870701c-7.22526 2.890104-14.992415 4.515788-22.75957 4.515787zM225.247486 852.580702l140.531311 81.284177c15.353678-13.727994 82.548598-70.626918 147.214676-70.626918s131.860998 56.718292 147.214677 70.626918l140.53131-81.284177c-4.154525-19.869466-19.869466-106.572588 12.644206-162.748986 33.416828-57.982713 120.661845-87.786911 136.376786-92.844593 1.445052-8.489681 8.128418-50.938084 8.128417-80.561651 0-29.623567-6.683366-72.07197-8.128417-80.561651-15.714941-5.057682-102.959958-34.86188-136.376786-92.844594-32.333039-56.176398-16.79873-142.87952-12.644206-162.748985l-140.53131-81.284177c-15.353678 13.727994-82.548598 70.626918-147.214677 70.626918-64.84671 0-131.860998-56.718292-147.214676-70.626918l-140.531311 81.284177c4.154525 19.869466 19.869466 106.572588-12.644205 162.748985-33.416828 57.982713-120.661845 87.786911-136.376786 92.844594-1.445052 8.489681-8.128418 50.938084-8.128418 80.561651 0 29.623567 6.683366 72.07197 8.128418 80.561651 15.714941 5.057682 102.959958 34.86188 136.376786 92.844593 32.333039 56.176398 16.79873 142.87952 12.644205 162.748986z" p-id="4936"></path><path d="M512.993473 729.751279c-117.59111 0-213.145176-95.734697-213.145175-213.325807s95.734697-213.325807 213.325807-213.325807 213.145176 95.734697 213.145175 213.325807S630.584583 729.751279 512.993473 729.751279z m0-363.972482c-83.090492 0-150.646675 67.556183-150.646675 150.646675s67.556183 150.646675 150.646675 150.646675c83.090492 0 150.646675-67.556183 150.646675-150.646675S596.083965 365.778797 512.993473 365.778797z" p-id="4937"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608814595533" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="809" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 962C263.4875 962 62 760.5125 62 512S263.4875 62 512 62s450 201.4875 450 450-201.4875 450-450 450z m0-825.00000029C304.8875 136.99999971 136.99999971 304.8875 136.99999971 512s167.88750029 375.00000029 375.00000029 375.00000029 375.00000029-167.88750029 375.00000029-375.00000029S719.1125 136.99999971 512 136.99999971z m0 675a299.40000029 299.40000029 0 0 1-234-112.5A299.40000029 299.40000029 0 0 1 512 586.99999971a299.25 299.25 0 0 1 233.96249971 112.5A299.25 299.25 0 0 1 512 811.99999971z m0-262.49999942a150.00000029 150.00000029 0 1 1 0-300.00000058 150.00000029 150.00000029 0 0 1 0 300.00000058z" p-id="810"></path></svg>

After

Width:  |  Height:  |  Size: 1011 B

View File

@@ -0,0 +1,7 @@
// 获取当前svg目录所有为.svg结尾的文件
const context = require.context('./icon', false, /\.svg$/);
// 解析获取的.svg文件的文件名称并返回
const requireAll = (requireContext) => {
return requireContext.keys().map(requireContext);  
};
requireAll(context);

View File

@@ -0,0 +1,67 @@
<template>
<el-switch
v-model="init_data.value"
:loading="init_data.loading"
@change="handlerSwitch($event)"
>
</el-switch>
</template>
<script>
import { reactive, getCurrentInstance } from 'vue';
// requestUrl
import ApiUrl from "@/api/requestUrl";
//API
import { SwitchStatus } from "@/api/common";
export default {
name: 'SwitchComponent',
props: {
data: {
type: Object,
default: () => ({})
},
config: {
type: Object,
default: () => ({})
}
},
setup(props){
// 配置信息
const config = reactive(props.config);
// 数据
const data = reactive(props.data);
// 初始值
const init_data = reactive({
value: data[config.prop],
loading: false
});
// 获取实例上下文
const { proxy } = getCurrentInstance();
// 事件
const handlerSwitch = (value) => {
// 请求参数
const key_id = config.key_id;
if(!key_id) { return false; }
init_data.loading = true;
// 参数
const request_data = {
url: config.api_url || ApiUrl[config.api_module][config.api_module_key],
method: config.method || "post",
data: {
[key_id]: data[key_id], // 等价于["id"]: data["id"]
[config.prop]: value // 等价于["status"]: value
}
}
// 接口请求
SwitchStatus(request_data).then(response => {
proxy.$message.success(response.message);
init_data.value = value;
init_data.loading = false;
}).catch(error => {
init_data.loading = false;
})
}
return { config, init_data, handlerSwitch }
}
}
</script>

View File

@@ -0,0 +1,39 @@
import { reactive } from "vue";
/**
* BasisTable组件默认配置
* @param { Boolean } selection 多选项配置
* @param { Boolean } batch_delete 指量删除按钮
* @param { Boolean } pagination 分页
*/
export function configHook(){
const config = reactive({
selection: true,
batch_delete: true,
pagination: true,
request: true,
search: true,
action_request: false,
row_key: "id",
expand_all: true,
has_button_delete: "",
has_button_batch_delete: ""
})
/**
*
* @param {*} data 配置额外组件true显示false不显示
*/
const configInit = (data = {}) => {
// 无数据时不执行
if(JSON.stringify(data) === "{}") { return false; }
// 获取默认配置项的所有key
const keys = Object.keys(config);
// 循环传入的配置项
for(let key in data) {
// 过滤原型链的对象
if (!data.hasOwnProperty(key)) { continue; }
// ES6语法判断传入的key和默认配置的key相同时则直接更新默认配置项的值
if(keys.includes(key)) { config[key] = data[key]; }
}
}
return { config, configInit };
}

View File

@@ -0,0 +1,121 @@
<template>
<SearchForm v-if="config.search" @callbackSearch="getList" />
<el-table :row-key="config.row_key" :default-expand-all="config.expand_all" :data="table_data.data" v-loading="table_data.loading" element-loading-text="加载中,请稍候" border style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column v-if="config.selection" type="selection" width="40"></el-table-column>
<template v-for="header in data.render_header" :key="header.prop">
<!-- switch -->
<el-table-column v-if="header.type === 'switch'" :label="header.label" :width="header.width">
<template #default="scope">
<Switch :data="scope.row" :config="header" />
</template>
</el-table-column>
<!-- function -->
<el-table-column v-else-if="header.type === 'function'" :label="header.label" :width="header.width">
<template #default="scope">
<div v-html="header.callback && header.callback(scope.row)"></div>
</template>
</el-table-column>
<!-- slot -->
<el-table-column v-else-if="header.type === 'slot'" :label="header.label" :width="header.width">
<template #default="scope">
<slot :name="header.slot_name" :data="scope.row"></slot>
<el-button v-if="header.delete_elem" @click="handlerDelete('delete', scope.row)" v-has-button="config.has_button_delete">删除</el-button>
</template>
</el-table-column>
<!-- text -->
<el-table-column v-else :prop="header.prop" :label="header.label" :width="header.width"></el-table-column>
</template>
</el-table>
<el-row class="margin-top-20">
<el-col :span="6">
<el-button v-if="config.batch_delete" :disabled="!table_data.data_id" @click="handlerDelete('batch')" v-has-button="config.has_button_batch_delete">批量删除</el-button>
</el-col>
<el-col :span="18">
<Pagination
v-if="config.pagination"
@sizeChange="getList"
@currentChange="getList"
:total="table_data.total"
:current="table_data.current_page" />
</el-col>
</el-row>
</template>
<script>
import { reactive, getCurrentInstance, onBeforeMount, watch } from 'vue';
// store
import { useStore } from "vuex";
// components
import SearchForm from "@c/search";
import Pagination from "@c/pagination";
import Switch from "@c/control/switch";
// hook
import { configHook } from "./configHook";
import { requestHook } from "./requestHook";
export default {
name: 'TableComponents',
components: { Pagination, Switch, SearchForm },
props: {
columns: {
type: Array,
default: () => ([])
},
config: {
type: Object,
default: () => ({})
},
request: {
type: Object,
default: () => ({})
},
search: {
type: Boolean,
default: true
}
},
emits: ["onload"],
setup(props, { emit }){
// store
const store = useStore();
// 获取实例上下文
const { proxy } = getCurrentInstance();
const { config, configInit } = configHook();
const { requestData, table_data, handlerDeleteComfirm, saveDataId } = requestHook();
const data = reactive({
table_data: [],
render_header: props.columns,
row_data_id: [],
currentPage: 0,
total: 0
})
onBeforeMount(() => {
// 默认配置项处理
configInit(props.config);
// 请求数据
getList(props.request)
})
const getList = (params, type) => {
requestData(params, type).then(response => {
emit("onload", table_data);
});
}
/** 删除动作 */
const handlerDelete = (type, val) => {
if(type === "delete") { saveDataId(val); }
handlerDeleteComfirm();
}
/** 复选框事件 */
const handleSelectionChange = (val) => {
saveDataId(val);
}
watch(() => store.state.app.table_action_request, () => {
config.action_request && getList();
})
return { data, config, table_data, requestData, getList, handlerDelete, handleSelectionChange }
}
}
</script>
<style lang='scss' scoped>
.margin-top-20 { margin-top: 20px; }
</style>

View File

@@ -0,0 +1,154 @@
import { reactive, getCurrentInstance } from "vue";
// requestUrl
import ApiUrl from "@/api/requestUrl";
// API
import { TableData, CommonApi } from "@/api/common";
import { configHook } from "./configHook";
import { ElMessage } from "element-plus"
export function requestHook(){
const { config, configInit } = configHook();
// 获取实例上下文
const { proxy } = getCurrentInstance();
let request_config = {
has: true, // 是否请求
url: "", // 请求模块
method: "post", // 请求类型
data: {}, // 请求参数
delete_key: '', // 删除接口的KEY
search_params: {}
}
const table_data = reactive({
data: [],
data_id: "",
current_page: 0,
total: 0,
loading: false
})
/**
* @returns 列表数据请求
*/
const loadData = () => {
// 判断条件是否请求接口
if(!request_config.has) { return false; }
if(!request_config.url) { return false; }
// 参数
const url = ApiUrl[request_config.url]?.list?.url;
const method = ApiUrl[request_config.url]?.list?.method || 'post';
const data = { ...request_config.data, ...request_config.search_params };
if(!url) {
console.log("请求地址不存在");
return Promise.reject();
}
const request_data = {
url,
method,
data
}
// 加载状态
table_data.loading = true;
return new Promise((resolve, reject) => {
TableData(request_data).then(response => {
let response_data = response.data.data;
// 是否格式化数据
if(request_config.format_data && Object.prototype.toString.call(request_config.format_data) === "[object Function]") {
response_data = request_config.format_data(response.data.data);
}
console.log(response_data)
table_data.data = response_data;
table_data.total = response.data.total;
table_data.current_page = response.data.current_page;
// 取消加载状态
table_data.loading = false;
resolve(response.data);
}).catch(error => {
reject()
// 取消加载状态
table_data.loading = false;
})
})
}
const requestData = (data = {}, type = 'init') => {
// 初始化逻辑
if(type === "init") {
request_config = {...request_config, ...data};
}
// 分页逻辑
if(type === 'page') {
request_config.data = {...request_config.data, ...data};
}
// 搜索
if(type === "search"){
request_config.data.pageNumber = 1;
request_config.data.pageSize = 10;
request_config.search_params = data;
}
// 请求数据
return loadData();
}
/**
*
* @param {Object} data
* @description 手动请求
*/
const handlerRequestData = (data = {}) => {
table_data.params_data = data;
loadData();
}
/**
*
* @param { String、Array } value
* @description 存储数据ID作用于删除接口使用
* 删除接口
*/
const saveDataId = (value) => {
const isArray = Array.isArray(value);
if(!isArray) {
table_data.data_id = value[request_config.delete_key];
}else{
table_data.data_id = value.length > 0 ? value.map(item => item[request_config.delete_key]).join() : "";
}
console.log(table_data.data_id)
}
/**
*
* @description 删除动作的确认弹窗
*/
const handlerDeleteComfirm = () => {
proxy.deleteConfirm({
title: "删除",
message: "确认删除当前数据吗?",
thenFun: () => { handlerDelete(); }
});
}
const handlerDelete = () => {
const url = ApiUrl?.[request_config.url]?.delete?.url
const method = ApiUrl?.[request_config.url]?.delete?.method || "post"
if(!url) {
console.log("未配置删除接口参数");
return false;
}
// 参数
const request_data = {
url,
method: method,
data: { [request_config.delete_key]: table_data.data_id },
}
CommonApi(request_data).then(response => {
// 成功提示
proxy.$message({
message: response.message,
type: "success"
})
loadData();
table_data.data_id = "";
proxy.deleteConfirmClose();
}).catch(error => {
proxy.confirmButtonLoading(false);
})
}
return { table_data, requestData, handlerDelete, saveDataId, handlerDeleteComfirm };
}

View File

@@ -0,0 +1,67 @@
<template>
<div ref="editor"></div>
</template>
<script>
import { ref, onMounted, watch } from 'vue';
import WangEditor from 'wangeditor';
// cookies
import { getToken } from "@/utils/cookies"; // 这是封装好的方法
export default {
name: 'Wangeditor',
components: {},
props: {
content: {
type: String,
default: ""
}
},
emits: ["update:content"],
setup(props, { emit }){
let html = ref(null)
// 内容
let content = ref();
// 富文本DOM元素
const editor = ref();
// 富文本对象
let editor_instance = null;
// 生命周期
onMounted(() => {
// 创建富文本对象
editor_instance = new WangEditor(editor.value);
// 图片上传配置
editor_instance.config.uploadImgServer = process.env.VUE_APP_DEV_TARGET + '/upload' // 上传图片的接口地址
editor_instance.config.uploadFileName = 'files' // formdata中的name属性
editor_instance.config.uploadImgHeaders = {
Token: getToken() // 设置请求头
}
editor_instance.config.uploadImgHooks = {
// 图片上传并返回结果,但图片插入错误时触发
fail: function (xhr, editor, result) {
console.log(result)
},
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
insertImgFn(result.data.files_path)
}
}
// 配置change事件
Object.assign(editor_instance.config, {
onchange(value) {
content.value = value;
emit("update:content", value);
},
});
// 创建
editor_instance.create();
})
watch(() => props.content, (newContent, oldContent) => {
editor_instance.txt.html(newContent);
})
return { editor }
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,27 @@
/** props */
export const propsType = {
flag: {
type: Boolean,
default: false
},
width: {
type: String,
default: "30%"
},
title: {
type: String,
default: "消息"
}
}
/** 自定义hook */
export function dialogHook(emit){
/** dialog关闭 */
const close = (form) => {
emit("update:flag", false)
// 重置表单
form && form.value.handlerFormReset();
};
return {
close
};
}

View File

@@ -0,0 +1,19 @@
import { reactive } from "vue";
import { GetCategory } from "@/api/info";
/** 自定义hook */
export function categoryHook(){
const infoData = reactive({
category_options: []
})
/** 获取分类 */
const handlerGetCategory = () => {
return GetCategory().then(response => {
// return response.data;
infoData.category_options = response.data;
});
};
return {
infoData,
handlerGetCategory
};
}

View File

@@ -0,0 +1,17 @@
// api
import { GetPermission } from "@a/account";
// store
import { useStore } from "vuex";
/** 自定义hook */
export function permissionHook(){
console.log(store)
/** 获取分类 */
const getPermission = () => {
GetPermission().then(response => {
store.dispatch("permission/getPermissionAction")
})
};
return { getPermission }
}

View File

@@ -0,0 +1,7 @@
const globalData = {
whether: [
{ value: "1", label: "是" },
{ value: "0", label: "否" },
]
}
export default globalData;

View File

@@ -0,0 +1,15 @@
const elemCode = {
// 信息模块
"init:add": 'M001F001E001', // 信息 - 添加按钮
"init:edit": 'M001F001E002', // 信息 -编辑按钮
"init:del": 'M001F001E003', // 信息 -删除按钮
// 菜单模块
"menu:add": 'M002F001E001', // 菜单 - 添加按钮
"menu:edit": 'M002F001E002', // 菜单 -编辑按钮
"menu:del": 'M002F001E003', // 菜单 -删除按钮
// 角色模块
"role:add": 'M003F001E001', // 角色 - 添加按钮
"role:edit": 'M003F001E002', // 角色 -编辑按钮
"role:del": 'M003F001E003', // 角色 -删除按钮
}
export default elemCode;

View File

@@ -0,0 +1,43 @@
<template>
<el-container id="layout-container">
<el-aside id="layout-aside" :width="collapse === true ? '60px' : '250px'"><layout-aside /></el-aside>
<el-container>
<el-header id="layout-header" height="75px"><layout-header /></el-header>
<el-main id="layout-main"><layout-main /></el-main>
</el-container>
</el-container>
</template>
<script>
import LayoutAside from "./components/Aside";
import LayoutHeader from "./components/Header";
import LayoutMain from "./components/Main";
import { useStore } from "vuex";
import { computed } from "vue";
export default {
name: "Layout",
components: { LayoutAside, LayoutHeader, LayoutMain },
props: {},
setup(props){
const store = useStore();
const collapse = computed(() => store.state.app.collapse);
return {
collapse
}
}
}
</script>
<style lang="scss" scpoed>
#layout-container { height: 100vh; }
#layout-aside {
background-color: #344a5f;
@include webkit(transition, all .3s ease 0s);
}
#layout-header {
position: relative;
z-index: 10;
background-color: $color_main;
@include webkit(box-shadow, 0 0 10px 0 rgba(0, 0, 0, .1));
}
#layout-main { background-color: #f7f7f7; }
</style>

View File

@@ -0,0 +1,97 @@
<template>
<h1 class="logo"><img :src="logo" alt=""></h1>
<el-menu :collapse="collapse" :default-active="currentPath" background-color="#344a5f" text-color="#fff" active-text-color="#ffffff" router>
<template v-for="item in routers" :key="item.path">
<template v-if="!item.hidden">
<!-- 一级菜单 -->
<template v-if="hasOnlyChild(item.children)">
<el-menu-item :index="item.children[0].path">
<img class="menu-icon" :src="item.meta.icon" alt="">
<template #title>{{ item.children[0].meta && item.children[0].meta.title }}</template>
</el-menu-item>
</template>
<!-- 子级菜单 -->
<template v-else>
<el-sub-menu v-if="item.children && item.children.length > 0" :index="item.path" >
<template #title>
<img class="menu-icon" :src="item.meta && item.meta.icon" alt="">
<span>{{ item.meta && item.meta.title }}</span>
</template>
<template v-for="child in item.children" :key="child.path">
<el-menu-item v-if="!child.hidden" :index="child.path">{{ child.meta && child.meta.title }}</el-menu-item>
</template>
</el-sub-menu>
</template>
</template>
</template>
</el-menu>
</template>
<script>
import { useRouter, useRoute } from "vue-router";
import { reactive, computed, toRefs } from "vue";
import { useStore } from "vuex";
export default {
name: "Aside",
components: {},
props: {},
setup(){
const { options } = useRouter();
const { path } = useRoute();
const store = useStore();
const routers = options.routes;
/**
* 数据
*/
const data = reactive({
logo: computed(() => {
return store.state.app.collapse ? require("@/assets/images/logo-min.png") : require("@/assets/images/logo.png")
}),
collapse: computed(() => store.state.app.collapse)
})
/**
* 判断是否只有一个子级菜单
*/
const hasOnlyChild = (children) => {
if(!children) { return false; }
// 存储路由
if(!children) { return false; }
const childRouter = children.filter(item => {
return item.hidden ? false : true;
})
// 只有一个子级路由
if(childRouter.length === 1) {
return true;
}
// 否则
return false;
}
/**
* 获取当前路由路径
*/
const currentPath = computed(() => path);
return {
...toRefs(data),
routers,
hasOnlyChild,
currentPath
}
}
};
</script>
<style lang="scss" scoped>
.logo {
padding: 20px 0;
border-bottom: 1px solid #2d4153;
img { margin: auto; }
}
.menu-icon {
width: 18px;
height: 18px;
display: inline-block;
margin:0 5px 0 0;
}
</style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="header-wrap">
<div class="wrap">
<span class="menu-btn" @click="switchAside">
<svg-icon iconName="menuBtn" className="icon-menu-svg"></svg-icon>
</span>
</div>
<div class="wrap">
<div class="user-info">
<div class="face-info">
<img src="../../assets/images/logo-min.png" alt="409019683@qq.com">
<span class="name">{{ username }}</span>
</div>
<span class="logout" @click="handlerLogout">
<svg-icon iconName="logout" className="icon-logout"></svg-icon>
</span>
</div>
</div>
</div>
</template>
<script>
import { ref, getCurrentInstance } from "vue";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
export default {
name: "Header",
components: {},
props: {},
setup(){
// 获取实例上下文
const { proxy } = getCurrentInstance();
// Vuex
const store = useStore();
// 引入router
const { replace } = useRouter();
// 菜单按钮
const switchAside = (() => { store.commit('app/SET_COLLAPSE'); })
// 用户名
const username = ref(store.state.app.username);
// 登出
const handlerLogout = (() => {
proxy.$confirm('确认退出管理后台', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('app/logoutAction').then(response => {
proxy.$message({
message: response.message,
type: "success"
})
// 刷新页面清除数据
location.reload();
replace({
name: "Login"
})
})
}).catch(error => {});
})
return {
switchAside,
handlerLogout,
username
}
}
};
</script>
<style lang="scss" scoped>
.header-wrap {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.user-info {
float: right;
display: flex;
align-items: center;
}
.face-info {
span, img {
display: inline-block;
vertical-align: middle;
}
span { margin-left: 15px;}
}
.logout {
display: flex;
align-items: center;
justify-content: center;
width: 75px;
height: 75px;
cursor: pointer;
}
.menu-btn { cursor: pointer; } // 手势
.icon-menu-svg { font-size: 24px; }
.icon-logout { font-size: 24px; }
</style>

View File

@@ -0,0 +1,25 @@
<template>
<div id="main-content">
<router-view />
</div>
</template>
<script>
export default {
name: "Main",
components: {},
props: {},
setup(){
return {}
}
};
</script>
<style lang="scss" scoped>
#main-content {
background-color: #fff; // 背景色
padding: 20px; // 内边距4个边都为20px
min-height: 100%; // 高度
@include webkit(box-sizing, border-box); // css3阴影
}
</style>

View File

@@ -0,0 +1,25 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
// 守卫路由
import "./router/permit";
// elementui
import ElementUI from "./plugins/elementui";
// 批量导入svg文件
import "@/components/svgIcon/svg";
// svgicon
import SvgIcon from "@/components/svgIcon/Index.vue";
// 全局方法
import Global from "@/utils/global";
// 自定义指令
import directive from "./plugins/directive";
const app = createApp(App);
app
.use(store)
.use(router)
.use(ElementUI)
.use(directive)
.use(Global)
.component("svg-icon", SvgIcon)
.mount("#app");

View File

@@ -0,0 +1,33 @@
import store from "../store";
// 元素编码
import elemCode from "../js/elemCode";
// 导出
export default (app) => {
app.directive('has-button', {
// 创建完成:在绑定元素的 attribute 或事件监听器被应用之前调用
created() {},
// 挂载之前:在绑定元素的父组件挂载之前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 挂载完成:绑定元素的父组件被挂载时调用
mounted(el, binding, vnode, prevVnode) {
// 获取元素权限
const elem_data = store.getters["permission/elem"];
const user_type = store.getters["permission/user_type"];
// 否则是非超管
if(!elem_data) { return false }
const elems = elem_data.split(",")
const code = elemCode[binding.value] // 等价于elemCode['init:edit']
if(!user_type && !elems.includes(code)) {
el.parentNode.removeChild(el);
}
},
// 更新之前:在包含组件的 VNode 更新之前调用
beforeUpdate() {},
// 更新完成:在包含组件的 VNode 及其子组件的 VNode 更新之后调用
updated() {},
// 销毁之前:在绑定元素的父组件卸载之前调用
beforeUnmount() {},
// 销毁完成:卸载绑定元素的父组件时调用
unmounted() {}
})
}

View File

@@ -0,0 +1,35 @@
import { ElConfigProvider, ElSlider, ElButton, ElSwitch, ElTree, ElForm, ElFormItem, ElInput, ElSelect, ElCol , ElRow, ElMessage, ElMessageBox, ElContainer, ElAside, ElHeader,ElMain, ElMenu, ElMenuItem, ElSubMenu, ElTable, ElPagination, ElUpload, ElDatePicker, ElCascader, ElRadio, ElLoading, ElTimePicker, ElCheckbox, ElDialog, ElInputNumber } from 'element-plus';
// 导出
export default (app) => {
app.use(ElConfigProvider);
app.use(ElButton);
app.use(ElSwitch);
app.use(ElForm);
app.use(ElFormItem);
app.use(ElInput);
app.use(ElCol);
app.use(ElRow);
app.use(ElMessage);
app.use(ElMessageBox);
app.use(ElContainer);
app.use(ElAside);
app.use(ElHeader);
app.use(ElMain);
app.use(ElMenu);
app.use(ElMenuItem);
app.use(ElSubMenu);
app.use(ElSlider);
app.use(ElSelect);
app.use(ElTable);
app.use(ElPagination);
app.use(ElTree);
app.use(ElUpload);
app.use(ElDatePicker);
app.use(ElRadio);
app.use(ElLoading);
app.use(ElTimePicker);
app.use(ElCascader);
app.use(ElCheckbox);
app.use(ElDialog);
app.use(ElInputNumber);
}

View File

@@ -0,0 +1,29 @@
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
export const routes = [
// 根路由
{
path: "/",
redirect: "Login",
hidden: true
},
// 登录
{
path: "/login",
name: "Login",
hidden: true,
component: () => import("../views/account/Login.vue")
},
// 路由中转
{
path: "/admin",
name: "Admin",
hidden: true
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;

View File

@@ -0,0 +1,118 @@
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
export const routes = [
{
path: "/",
redirect: "Login",
hidden: true,
aaa: "",
bbb:""
},
// 登录
{
path: "/login",
name: "Login",
hidden: true,
component: () => import("../views/account/Login.vue")
},
// 后台首页
{
path: "/home",
name: "Home",
meta: {
title: "控制台",
icon: "home"
},
component: () => import("../layout/Index.vue"),
children: [
{
path: "/console",
name: "Console",
meta: {
title: "首页"
},
component: () => import("../views/console/Index.vue"),
}
]
},
{
path: "/system",
name: "System",
meta: {
title: "系统配置",
icon: "system"
},
component: () => import("../layout/Index.vue"),
children: [
{
path: "/user",
name: "User",
meta: {
title: "用户列表"
},
component: () => import("../views/system/User.vue"),
},
{
path: "/role",
name: "Role",
meta: {
title: "角色列表"
},
component: () => import("../views/system/Role.vue"),
},
{
path: "/menu",
name: "Menu",
meta: {
title: "菜单列表"
},
component: () => import("../views/system/Menu.vue"),
}
]
},
{
path: "/news",
name: "News",
meta: {
title: "信息管理",
icon: "information"
},
component: () => import("../layout/Index.vue"),
children: [
{
path: "/newsIndex",
name: "NewsIndex",
meta: {
title: "信息列表"
},
component: () => import("../views/info/Index.vue"),
},
{
path: "/newsCategory",
name: "NewsCategory",
meta: {
title: "信息分类"
},
component: () => import("../views/info/Category.vue"),
},
{
path: "/newsDetailed",
name: "NewsDetailed",
hidden: true,
meta: {
title: "信息详情"
},
component: () => import("../views/info/Detailed.vue"),
}
]
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;

View File

@@ -0,0 +1,41 @@
import router from "./index";
import store from "../store";
// cookie
import { getToken } from "@u/cookies";
// 全局前置守卫
router.beforeEach((to, from, next) => {
if(!getToken()) {
if(to.name !== "Login") {
next("Login");
}else{
next();
}
}else{
if(store.state.permission.async_router.length === 0){
store.dispatch("permission/getRouterAction").then(response => {
// 获取动态路由
const async_router_data = store.getters["permission/async_router"];
// 获取静态路由
const default_router_data = router.options.routes;
// 更新静态路由
router.options.routes = default_router_data.concat(async_router_data);
// 更新动态路由
async_router_data.forEach(item => {
router.addRoute(item);
});
if(to.name === "Admin") {
const first_router = async_router_data[0]?.children[0] || async_router_data[0];
next({ ...first_router, replace: true});
}else{
// 确认进入下一个路由
next({ ...to, replace: true});
}
})
}else{
next();
}
}
})
// 全局后置守卫
router.afterEach((to, from) => {
})

Some files were not shown because too many files have changed in this diff Show More