保存进度!

This commit is contained in:
Kane Wang 2022-12-14 18:38:51 +08:00
parent 4cb90a5925
commit 4e189eb2ab
104 changed files with 36714 additions and 0 deletions

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' // formdataname
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.totaltotal
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 { StringArray } 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' // formdataname
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; // 420px
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) => {
})

View File

@ -0,0 +1,13 @@
import { createStore } from "vuex";
// 引用模块文件
import app from "./modules/app";
import info from "./modules/info";
import permission from "./modules/permission";
// 创建Vuex
export default createStore({
modules: { // 模块化
app, // 载入 app 模块
info,
permission
}
});

View File

@ -0,0 +1,63 @@
//cookies
import { setToken, setUsername, getUsername, getToken, removeToken, removeUsername } from "@u/cookies";
// api
import { Login, Logout } from "@a/account";
const state = {
collapse: JSON.parse(sessionStorage.getItem('collapse')) || false,
token: "" || getToken(),
username: "" || getUsername(),
table_action_request: false
};
const getters = {}
const mutations = {
SET_COLLAPSE(state){
state.collapse = !state.collapse;
sessionStorage.setItem('collapse', JSON.stringify(state.collapse));
},
SET_TOKEN(state, value){ // 设置 token
state.token = value;
value && setToken(value);
},
SET_USERNAME(state, value){ // 设置用户名
state.username = value;
value && setUsername(value);
},
SET_TABLE_REQUEST(state){
state.table_action_request = !state.table_action_request
}
}
const actions = {
loginAction(context, repuestData) {
return new Promise((resolve, reject) => {
Login(repuestData).then((response) => {
let data = response.data;
context.commit('SET_TOKEN', data.token);
context.commit('SET_USERNAME', data.username);
resolve(response);
}).catch(error => {
reject(error);
})
})
},
// 登出
logoutAction({ commit }){
return new Promise((resolve, reject) => {
Logout().then(response => {
removeToken();
removeUsername();
commit('SET_TOKEN', '');
commit('SET_USERNAME', '');
resolve(response);
})
})
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
};

View File

@ -0,0 +1,23 @@
// api
import { GetCategory } from "@/api/info";;
const state = {};
const getters = {}
const mutations = {}
const actions = {
categoryAction() {
return new Promise((resolve, reject) => {
GetCategory().then(response => {
resolve(response.data)
})
})
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
};

View File

@ -0,0 +1,94 @@
// API
import { GetPermission } from "@a/account";
// utils
import { formatTree } from "@/utils/format";
/** 格式化菜单数据生成路由 */
const formatRouterMenu = (data) => {
// 检测数据是否存在
if(data && (!Array.isArray(data) || data.length === 0)) {
return false
}
// 监听存储数据
const router = [];
// 遍历菜单
data.forEach(item => {
const router_obj = {};
// id
router_obj.menu_id = item.menu_id;
router_obj.parent_id = item.parent_id;
// path
router_obj.path = item.menu_path;
// name
router_obj.name = item.menu_router;
// hidden
if(item.menu_hidden === "1") { router_obj.hidden = true; }
// redirect
if(item.menu_redirect) { router_obj.redirect = item.menu_redirect; }
// meta
router_obj.meta = {
// 菜单名称
title: item.menu_name,
// 菜单图标
icon: item.menu_icon,
// 是否缓存页面
keepAlive: item.menu_keep === "1" ? true : false
}
// component
router_obj.component = require(`@/${item.menu_component}`).default;
// 添加至router数组
router.push(router_obj);
})
// 树状格式化
const tree_data = formatTree(router, "menu_id", "parent_id", "children", 0);
// 返回数据
return tree_data;
}
const state = {
async_router: [],
elem: "",
user_type: ""
};
const getters = {
async_router: state => state.async_router,
elem: state => state.elem,
user_type: state => state.user_type,
}
const mutations = {
SET_ROUTER(state, value){
state.async_router = value;
},
SET_ELEM(state, value){
state.elem = value;
},
SET_USERTYPE(state, value){
state.user_type = value;
}
}
const actions = {
getRouterAction(context) {
return new Promise((resolve, reject) => {
GetPermission().then((response) => {
const data = response.data.router_menu;
// 格式化菜单数据生成路由
const menu = formatRouterMenu(data);
// 存储路由
context.commit("SET_ROUTER", menu);
// 存储元素权限
context.commit("SET_ELEM", response.data.elem_data);
// 更新用户类型
context.commit("SET_USERTYPE", response.data.user);
resolve(response);
}).catch(error => {
reject(error);
})
})
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
};

View File

@ -0,0 +1,34 @@
.el-button-block {
display: block;
width: 100%;
}
#layout-aside .el-menu {
border-right: none;
}
/* 导航菜单高亮 */
.is-active { background-color: rgba(254, 108, 108, .2) !important; }
.is-opened {
.el-sub-menu__title { background-color: #f56c6c !important; }
}
.el-form-item__content { display: block; }
/*上传组件*/
.avatar-uploader {
overflow: hidden;
img { width: 100%; }
.el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
}
}
/* 日期组件 z-index */
.el-popper { z-index: 10002 !important; }
.va-top { vertical-align: top; }

View File

@ -0,0 +1,9 @@
.aside-menu-svg {
margin-right: 5px;
margin-top: -2px;
font-size: 18px;
}
.icon-logo {
background: url("~@/assets/images/logo.png") no-repeat;
}

View File

@ -0,0 +1,14 @@
@import "./sassConfig.scss";
@import "./normalize.scss";
@import "./elementui.scss";
@import "./icon.scss";
/* float */
.pull-right { float: right; }
/* margin */
.margin-top-20 { margin-top: 20px; }
.margin-top-10 { margin-top: 10px; }
/* text-color */
.text-white { color: white; }

View File

@ -0,0 +1,375 @@
/*! 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.
*/
/* div的默认样式不存在padding和margin为0的情况*/
html, body, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul,
fieldset, form, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
font-family: 'Microsoft YaHei';
font-size: 14px;
}
/**
* 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.
*/
/* 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;
text-decoration: none;
}
/**
* 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 {
display: block;
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 */
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;
}
ul, li { list-style: none; }

View File

@ -0,0 +1,12 @@
$color_main: white; // 定义
$danger: #f56c6c;
@mixin webkit($attr, $value){
-webkit-#{$attr}: $value;
-moz-#{$attr}: $value;
-o-#{$attr}: $value;
-ms-#{$attr}: $value;
#{$attr}: $value;
}

View File

@ -0,0 +1,49 @@
/**
* @param { String } value 时间的值
* @param { String } type
* @param { Boolean } br true换行false默认
* @param { String } conn 任意字符
* @returns 日期格式化
*/
export function getDate(params){
const new_date = params.value ? new Date(params.value) : new Date();
let year = new_date.getFullYear(); //年
let month = new_date.getMonth() + 1; //月
let day = new_date.getDate(); //日
let hh = new_date.getHours(); //时
let mm = new_date.getMinutes(); //分
let ss = new_date.getSeconds(); //分
if(month < 10) { month = `0${month}`; }
if(day < 10) { day = `0${day}`; }
if(hh < 10) { hh = `0${hh}`; }
if(mm < 10) { mm = `0${mm}`; }
if(ss < 10) { ss = `0${ss}`; }
// 连接符
const conn = params.connDate || "-";
// 格式化
const br = params.br ? "<br />" : " ";
const date = `${year}${conn}${month}${conn}${day}`;
const time = `${hh}:${mm}:${ss}`;
// 仅日期
if(params.type === "date") { return date; }
// 仅时间
if(params.type === "time") { return time; }
// 整体
return `${date}${br}${time}`;
}
/**
* @param { Function } fun 执行的函数
* @param { Number } time 延时执行时间默认500毫秒
* @returns 防抖函数
*/
export function debounce(fun, time = 500) {
let timer;
return function() {
let args = arguments;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fun.apply(this, args)
}, time)
}
}

View File

@ -0,0 +1,18 @@
import Cookies from "js-cookie";
// 变量
const tokenKey = "tokenAdmin";
const userNameKey = "username";
// 获取token
export function getToken(){ return Cookies.get(tokenKey); }
// 写入token
export function setToken(value){ Cookies.set(tokenKey, value); }
// 删除token
export function removeToken(){ Cookies.remove(tokenKey); }
// 获取userName
export function getUsername(){ return Cookies.get(userNameKey); }
// 写入userName
export function setUsername(value){ Cookies.set(userNameKey, value); }
// 删除userName
export function removeUsername(){ Cookies.remove(userNameKey); }

View File

@ -0,0 +1,42 @@
/**
* @param { responseData } Object 请求参数数据
* @param { formData } Object Form表单字段
* @returns 枚举匹配key
*/
export function formatRequestData(responseData, formData){
// 判断是否有值存在
if(JSON.stringify(responseData) === "{}") { return false; }
// 获取form表单字段的所有key
const keys = Object.keys(responseData);
// 空JSON对象存储过滤出来的数据
const data_json = {};
// 执行字段匹配
for(let key in formData) {
if(keys.includes(key) && formData.hasOwnProperty(key)) {
data_json[key]= responseData[key];
}
}
// 返回处理后的数据
return data_json;
}
/**
*
* @returns 递归函数格式化树状菜单
*/
export function formatTree(data, id = "id", pid = "parent_id", child = "children", root){
const tree = [];
if(data && data.length > 0) {
data.forEach(item => {
// 获取顶层菜单parent_id === 0
if(item[pid] === root) {
const children = formatTree(data, id, pid, child, item[id]);
if(children) {
item[child] = children;
}
tree.push(item);
}
})
}
return tree;
}

View File

@ -0,0 +1,59 @@
// Element Plus
import { ElMessageBox } from 'element-plus';
// 命名空间
const globalFunction = {}
/**
* @param { message } String 内容可选
* @param { title } String 标题可选
* @param { thenFun } Function 回调函数可选
* @description 确认弹窗
*/
globalFunction.deleteConfirm = (params) => {
ElMessageBox.confirm(params.message || '确认删除当前数据吗?删除后将无法恢复', params.title || '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose: false, // 取消右上角关闭按钮
closeOnClickModal: false, // 取消点击遮罩关闭 MessageBox
closeOnPressEscape: false, // 取消按下ESC键关闭MessageBox
type: 'warning',
beforeClose: (action, instance, done) => {
globalFunction.deleteConfirmObj = { instance, done };
if(action === "confirm") {
// 按钮加载状态
globalFunction.confirmButtonLoading(true);
// 判断thenFun存在并且是Function类型是则自动执行函数
params.thenFun && Object.prototype.toString.call(params.thenFun) === "[object Function]" && params.thenFun();
}else{
globalFunction.deleteConfirmClose();
}
}
}).then(()=>{}).catch(()=>{})
}
/**
* @param { bool } Boolean 加载状态可选
* @description 弹窗确认按钮加载状态
*/
globalFunction.confirmButtonLoading = (bool) => {
globalFunction.deleteConfirmObj.instance.confirmButtonLoading = bool;
}
/**
* @param {*} params
*/
globalFunction.deleteConfirmClose = () => {
globalFunction.deleteConfirmObj.done();
globalFunction.deleteConfirmObj = null;
}
/** 函数2 */
globalFunction.message = (params) => {
console.log(params)
}
export default {
install(app){
app.config.globalProperties["deleteConfirm"] = globalFunction.deleteConfirm;
app.config.globalProperties["deleteConfirmClose"] = globalFunction.deleteConfirmClose;
app.config.globalProperties["confirmButtonLoading"] = globalFunction.confirmButtonLoading;
app.config.globalProperties["message"] = globalFunction.message;
}
}

View File

@ -0,0 +1,61 @@
import axios from "axios";
// cookies
import { getToken, getUsername, removeToken, removeUsername } from "@/utils/cookies"; // 这是封装好的方法
// ElementUI 单独引入
import { ElMessage } from 'element-plus'
// vue-router
import router from "@/router";
// 创建实例
const instance = axios.create({
baseURL: process.env.VUE_APP_API, // 请求地址
timeout: 5000, // 超时
});
// 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if(getToken()) {
config.headers['Token'] = getToken(); // 携带token
}
if(getUsername()) {
config.headers['Username'] = getUsername(); // 携带用户名
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
const data = response.data;
if(data.resCode === 0) {
return Promise.resolve(data);
}else{
ElMessage({
message: data.message,
type: "error"
})
return Promise.reject(data);
}
}, function (error) {
const errorData = JSON.parse(error.request.response);
if(errorData.message) {
ElMessage({
message: errorData.message,
type: "error"
})
}
// token失效自动退出
if(errorData.resCode === 1010) {
router.replace({
name: "Login"
})
removeToken();
removeUsername();
}
// 对响应错误做点什么
return Promise.reject(errorData);
});
// 暴露instance
export default instance;

View File

@ -0,0 +1,40 @@
// 校验邮箱
export function validate_email(value){
let regEmail = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
return regEmail.test(value);
}
// 校验密码
export function validate_password(value){
let regPassword = /^(?!\D+$)(?![^a-zA-Z]+$)\S{6,20}$/;
return regPassword.test(value);
}
// 校验验证码
export function validate_code(value){
let regCode = /^[a-z0-9]{6}$/;
return regCode.test(value);
}
// 密码校验
export function checkPassword(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用密码"));
}else if(!validate_password(value)) {
callback(new Error("请输入>=6并且<=20位的密码包含数字、字母"));
} else {
callback();
}
}
// 用户名校验
export function checkUser(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用户名"));
}else if(!validate_email(value)) {
callback(new Error("邮箱格式不正确"));
} else {
callback();
}
}

View File

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

View File

@ -0,0 +1,17 @@
<template>
<div class="home">
<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: "Home",
components: {
HelloWorld
}
};
</script>

View File

@ -0,0 +1,346 @@
<!-- eslint-disable prettier/prettier -->
<template>
<div id="login">
<div class="form-wrap">
<ul class="menu-tab">
<li @click="data.current_menu = item.type" :class="{'current': data.current_menu === item.type}" v-for="item in data.tab_menu" :key="item.type">{{ item.label }}</li>
</ul>
<el-form ref="account_form" :model="data.form" :rules="data.form_rules">
<el-form-item prop="username">
<label class="form-label">用户名</label>
<el-input v-model="data.form.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<label class="form-label">密码</label>
<el-input type="password" v-model="data.form.password"></el-input>
</el-form-item>
<el-form-item prop="passwords" v-if="data.current_menu === 'register'">
<label class="form-label">确认密码</label>
<el-input type="password" v-model="data.form.passwords"></el-input>
</el-form-item>
<el-form-item prop="code">
<label class="form-label">验证码</label>
<el-row :gutter="10">
<el-col :span="14">
<el-input v-model="data.form.code"></el-input>
</el-col>
<el-col :span="10">
<el-button type="success" class="el-button-block" :loading="data.code_button_loading" :disabled="data.code_button_disabled" @click="handlerGetCode">{{ data.code_button_text }}</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button type="danger" @click="submitForm" :disabled="data.submit_button_disabled" :loading="data.submit_button_loading" class="el-button-block">
{{ data.current_menu === "login" ? "登录": "注册"}}
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { reactive, ref, onBeforeUnmount, getCurrentInstance } from 'vue';
import { useStore } from "vuex";
import { useRouter } from "vue-router";
//
import { validate_email, validate_password, validate_code } from "../../utils/validate";
// sha1
import sha1 from "js-sha1";
// API
import { GetCode } from "../../api/common";
import { Register, Login } from "../../api/account";
export default {
name: "Login",
components: {},
props: {},
setup(props){
const instance = getCurrentInstance();
//
const { proxy } = getCurrentInstance();
// store
const store = useStore();
// router
const rotuer = useRouter();
//
const validate_name_rules = (rule, value, callback) => {
let regEmail = validate_email(value);
if (value === '') {
callback(new Error("请输入邮箱"));
} else if(!regEmail) {
callback(new Error("邮箱格式不正确"));
} else {
callback();
}
}
const validate_password_rules = (rule, value, callback) => {
let regPassword = validate_password(value);
//
const passwordsValue = data.form.passwords;
if (value === '') {
callback(new Error("请输入密码"));
} else if(!regPassword) {
callback(new Error("请输入>=6并且<=20位的密码包含数字、字母"));
} else {
callback();
}
}
//
const validate_passwords_rules = (rule, value, callback) => {
//
if(data.current_menu === "login") { callback(); }
let regPassword = validate_password(value);
//
const passwordValue = data.form.password;
if (value === '') {
callback(new Error("请输入密码"));
} else if(!regPassword) {
callback(new Error("请输入>=6并且<=20位的密码包含数字、字母"));
} else if(passwordValue && passwordValue !== value){
callback(new Error("两次密码不一致"));
}else {
callback();
}
}
const validate_code_rules = (rule, value, callback) => {
let regCode = validate_code(value);
if (value === '') {
callback(new Error("请输入验证码"));
} else if(!regCode) {
callback(new Error("请输入6位的验证码"));
} else {
callback();
}
}
const data = reactive({
form: {
username: "", //
password: "", //
passwords: "", //
code: "", //
},
form_rules: {
username: [
{ validator: validate_name_rules, trigger: 'change' }
],
password: [
{ validator: validate_password_rules, trigger: 'change' }
],
passwords: [
{ validator: validate_passwords_rules, trigger: 'change' }
],
code: [
{ validator: validate_code_rules, trigger: 'change' }
]
},
tab_menu: [
{ type: "login", label: "登录" },
{ type: "register", label: "注册" }
],
current_menu: "login",
/**
* 获取验证码按钮交互
*/
code_button_disabled: false,
code_button_loading: false,
code_button_text: "获取验证码",
code_button_timer: null,
//
submit_button_disabled: true,
loading: false
})
//
const handlerGetCode = () => {
const username = data.form.username;
const password = data.form.password;
const passwords = data.form.passwords;
//
if(!validate_email(username)) {
proxy.$message.error({
message: "用户名不能为空 或 格式不正确",
type: "error"
})
return false;
}
//
if(!validate_password(password)) {
proxy.$message({
message: "密码不能为空 或 格式不正确",
type: "error"
})
return false;
}
//
if(data.current_menu === 'register' && (password !== passwords)) {
proxy.$message({
message: "两次密码不一致",
type: "error"
})
return false;
}
//
const requestData = {
username: data.form.username,
module: data.current_menu
}
data.code_button_loading = true;
data.code_button_text = "发送中";
GetCode(requestData).then(response => {
const resData = response;
//
data.submit_button_disabled = false;
//
if(resData.resCode === 1024) {
proxy.$message.error(resData.message);
return false;
}
// Elementui
proxy.$message({
message: resData.message,
type: "success"
})
//
countdown();
}).catch(error => {
data.code_button_loading = false;
data.code_button_text = "获取验证码";
})
}
/** 倒计时 */
const countdown = (time) => {
if(time && typeof time !== 'number') { return false; }
let second = time || 60; //
data.code_button_loading = false; //
data.code_button_disabled = true; //
data.code_button_text = `倒计进${second}`; //
//
if(data.code_button_timer) { clearInterval(data.code_button_timer) };
//
data.code_button_timer = setInterval(() => {
second--;
data.code_button_text = `倒计进${second}`; //
if(second <= 0) {
data.code_button_text = `重新获取`; //
data.code_button_disabled = false; //
clearInterval(data.code_button_timer); //
}
}, 1000)
}
/** 表单提交 */
const account_form = ref(null);
const submitForm = (formName) => {
account_form.value.validate((valid) => {
if (valid) {
data.current_menu === "login" ? login() : register();
} else {
alert('表单验证不通过');
return false;
}
});
}
/** 注册 */
const register = () => {
const requestData = {
username: data.form.username,
password: data.form.password,
code: data.form.code
}
data.submit_button_loading = true;
Register(requestData).then(response => {
proxy.$message({
message: response.message,
type: "success"
})
reset();
}).catch(error => {
data.submit_button_loading = false;
})
}
/** 登录 */
const login = () => {
const requestData = {
username: data.form.username,
password: sha1(data.form.password),
code: data.form.code
}
data.submit_button_loading = true;
store.dispatch("app/loginAction", requestData).then(response => {
data.submit_button_loading = false;
proxy.$message({
message: response.message,
type: "success"
})
//
rotuer.push({path: "/admin"});
reset();
}).catch(error => {
data.submit_button_loading = false;
console.log("失败");
})
}
/** 重置 */
const reset = () => {
//
proxy.$refs.form.resetFields();
//
data.current_menu = "login";
//
data.code_button_timer && clearInterval(data.code_button_timer);
//
data.code_button_text = "获取验证码";
//
data.code_button_disabled = false;
//
data.submit_button_disabled = true;
//
data.submit_button_loading = false;
}
// -
onBeforeUnmount(() => {
clearInterval(data.code_button_timer); //
})
return {
data,
handlerGetCode,
submitForm,
account_form
}
}
}
</script>
<style lang='scss' scoped>
#login {
height: 100vh; //
background-color: #344a5f; //
}
.form-wrap {
width: 320px;
padding-top: 100px; //
margin: auto; //
}
.menu-tab {
text-align: center;
li {
display: inline-block;
padding: 10px 24px;
margin: 0 10px;
color: #fff;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
&.current { background-color: rgba(0, 0, 0, .1);}
}
}
.form-label {
display: block; //
color: #fff; //
font-size: 14px; //
}
</style>

View File

@ -0,0 +1,18 @@
<template>
<div class="">控制台</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
name: 'NewsIndex',
components: {},
props: {},
setup(props){
const store = useStore();
store.dispatch("app/update_count");
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,304 @@
<template>
<el-button type="danger" @click="handlerCategory('first_category_add')">添加一级分类</el-button>
<hr class="spacing-hr" />
<el-row :gutter="20">
<el-col :span="7">
<el-tree ref="categoryTree" :data="data.tree_data" :props="data.defaultProps" default-expand-all :expand-on-click-node="false">
<template #default="{ node, data }">
<div class="custom-tree-node">
<span>{{ node.label }}</span>
<span>
<el-button type="danger" round @click="handlerCategory('child_category_add', node)">添加子级</el-button>
<el-button type="success" round @click="handlerCategory(node.level === 1 ? 'parent_category_edit' : 'child_category_edit', node)">编辑</el-button>
<el-button round @click="handlerCategory('delete_category', node)">删除</el-button>
</span>
</div>
</template>
</el-tree>
</el-col>
<el-col :span="17">
<h4 class="column">{{ config[config.type].title }}</h4>
<el-form label-width="100px">
<el-form-item label="父级分类:">
<el-input v-model.trim="data.parent_category" :disabled="config[config.type].parent_disabled" style="width: 20%;"></el-input>
</el-form-item>
<el-form-item label="子级分类:" v-if="config[config.type].sub_show">
<el-input v-model.trim="data.sub_category" :disabled="config[config.type].sub_disabled" style="width: 20%;"></el-input>
</el-form-item>
<el-form-item label="">
<el-button type="danger" :loading="data.button_loading" @click="handlerSubmit">确定</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script>
import { ref, reactive, getCurrentInstance, onBeforeMount } from 'vue';
import { firstCategoryAdd, GetCategory, ChildCategoryAdd, CategoryEdit, CategoryDel } from "@/api/info";
export default {
name: 'InfoCategory',
components: {},
props: {},
setup(props){
//
const { proxy } = getCurrentInstance();
const data = reactive({
tree_data: [],
defaultProps: {
children: 'children',
label: 'category_name'
},
parent_category: "父级分类文本演示", //
sub_category: "子级分类文本演示", //
button_loading: false, //
parent_category_data: {},
sub_category_data: {}
})
const config = reactive({
type: "default",
default: {
title: "添加分类", //
parent_disabled: true, // /
sub_disabled: true, // /
sub_show: true, // /
clear: ["parent_category", "sub_category"]
},
first_category_add: {
title: "一级分类添加", //
parent_disabled: false, // /
sub_disabled: true, // /
sub_show: false, // /
clear: ["parent_category", "sub_category"]
},
child_category_add: {
title: "子级分类添加", //
parent_disabled: true, // /
sub_disabled: false, // /
sub_show: true,
clear: ["sub_category"],
create: ["parent_category"]
},
parent_category_edit: {
title: "父级分类编辑", //
parent_disabled: false, // /
sub_disabled: true, // /
sub_show: false,
create: ["parent_category"]
},
child_category_edit: {
title: "子级分类编辑", //
parent_disabled: true, // /
sub_disabled: false, // /
sub_show: true,
create: ["parent_category", "sub_category"]
}
});
// const handleNodeClick = (data) => {
// console.log(data)
// }
const categoryTree = ref(null);
const handlerCategory = (type, category_data) => {
console.log(category_data);
if(type === "child_category_edit") {
data.parent_category_data = category_data.parent || null;
data.sub_category_data = category_data || null;
}else{
data.parent_category_data = category_data || null;
}
config.type = type === "delete_category" ? "default": type;
//
handlerInputValue();
//
(type === "delete_category") && handlerDeleteComfirm();
}
const handlerInputValue = () => {
//
const clearObject = config[config.type].clear;
//
const createObject = config[config.type].create;
//
if(clearObject && clearObject.length > 0) {
clearObject.forEach(item => {
data[item] = "";
})
}
//
if(createObject && createObject.length > 0) {
createObject.forEach(item => {
data[item] = data[`${item}_data`].data.category_name;
})
}
}
//
const handlerSubmit = () => {
if(config.type === 'first_category_add') { handlerFirstCategoryAdd(); }
if(config.type === 'child_category_add') { handlerChildCategoryAdd(); }
if(config.type === 'child_category_edit' || config.type === 'parent_category_edit') { handlerCategoryEdit(); }
}
//
const handlerFirstCategoryAdd = () => {
//
if(!data.parent_category) {
proxy.$message.error("父级分类不能为空");
return false
}
data.button_loading = true;
firstCategoryAdd({categoryName: data.parent_category}).then(response => {
data.button_loading = false;
proxy.$message({
message: response.message,
type: "success"
})
data.parent_category = "";
}).catch(error => {
data.button_loading = false;
})
}
const handlerGetCategory = () => {
GetCategory().then(response => {
const response_data = response.data
data.tree_data = response_data || [];
})
}
//
const handlerChildCategoryAdd = () => {
//
if(!data.sub_category) {
proxy.$message.error("子级分类不能为空");
return false
}
//
data.button_loading = true;
//
ChildCategoryAdd({
categoryName: data.sub_category, //
parentId: data.parent_category_data.data.id // ID
}).then(response => {
//
data.button_loading = false;
//
proxy.$message({
message: response.message,
type: "success"
})
//
data.sub_category = "";
//
categoryTree.value.append(response.data, data.parent_category_data);
}).catch(error => {
//
data.button_loading = false;
})
}
//
const handlerCategoryEdit = () => {
//
console.log(!data.parent_category);
console.log(data.sub_category);
if(data.parent_category == "" || data.sub_category == "") {
const message = config.type === "parent_category_edit" ? "父级" : "子级";
proxy.$message.error(`${message}分类不能为空`);
return false
}
//
data.button_loading = true;
const node_parent = data.parent_category_data.data
const node_sub = data.sub_category_data.data
//
CategoryEdit({
categoryName: config.type === "parent_category_edit" ? data.parent_category : data.sub_category, //
id: config.type === "parent_category_edit" ? node_parent.id : node_sub.id // ID
}).then(response => {
//
data.button_loading = false;
//
proxy.$message({
message: response.message,
type: "success"
})
//
const node_date = config.type === "parent_category_edit" ? node_parent : node_sub;
node_date.category_name = response.data.category_name;
}).catch(error => {
//
data.button_loading = false;
})
}
const handlerDeleteComfirm = () => {
proxy.$confirm('确认删除该分类吗?删除后将无法恢复', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showClose: false, //
closeOnClickModal: false, // MessageBox
closeOnPressEscape: false, // ESCMessageBox
type: 'warning',
beforeClose: (action, instance, done) => {
if(action === "confirm") {
instance.confirmButtonLoading = true;
CategoryDel({categoryId: data.parent_category_data.data.id}).then(response => {
//
proxy.$message({
message: response.message,
type: "success"
})
instance.confirmButtonLoading = false;
done();
//
categoryTree.value.remove(data.parent_category_data);
}).catch(error => {
instance.confirmButtonLoading = false;
done();
})
}
else{
done();
}
}
})
}
onBeforeMount(() => {
handlerGetCategory();
})
return {
data,
// handleNodeClick,
handlerCategory,
handlerSubmit,
config,
categoryTree
}
}
}
</script>
<style lang="scss" scoped>
.spacing-hr {
border: none; //
border-top: 1px solid #e9e9e9; // 1线 e9e9e9
margin:30px 0; // 300
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 8px;
}
:deep(.el-tree-node__content) {
height: auto;
button {
padding: 8px 12px;
margin: 8px 3px;
font-size: 12px;
height: auto;
}
}
.column {
height: 44px;
padding: 0 20px;
margin-bottom: 30px;
line-height: 44px;
border-radius: 6px;
background-color: #f3f3f3;
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<BasisForm @callback="handlerSubmitForm" :item="form_config.form_item" :button="form_config.form_button" label-width="120px" :field="form_config.form_data"/>
</template>
<script>
import { reactive, onBeforeMount, getCurrentInstance } from "vue";
import { useRouter, useRoute } from "vue-router";
import BasisForm from "@c/form";
import { InfoCreate, GetDetailed, InfoEdit } from "@/api/info";
//
import globalData from "@/js/data";
import dayjs from "dayjs";
export default {
name: 'InfoDetailed',
components: { BasisForm },
props: {},
setup(props){
//
const { proxy } = getCurrentInstance();
// router
const { go } = useRouter();
const { query } = useRoute();
const data = reactive({
row_id: query.id,
})
const form_config = reactive({
form_item: [
{
type: "cascader",
label: "信息分类",
prop: "category_id",
props: {
label: "category_name",
value: "id"
},
url: "category",
required: true
},
{
type: "input",
label: "信息标题",
prop: "title",
width: "300px",
placeholder: "请输入标题",
max_length: 50, //
min_length: 1, //
required: true,
message: "请务必填写标题"
},
{ type: "upload", label: "缩略图", prop: "image_url" },
{ type: "date", label: "发布日期", prop: "create_date" },
{
type: "radio",
label: "是否发布",
prop: "status",
options: globalData.whether,
required: true
},
{ type: "wangeditor", label: "内容描述", prop: "content" }
],
form_data: {
image_url: "",
category_id: "",
title: "",
create_date: "",
content: "",
status: "1",
},
form_button: [
{ label: "提交", type: "danger", key: "submit" },
// { label: "", type: "primary", key: "reset" },
// { label: "", type: "primary", key: "close", callback: () => handlerClose() },
]
})
onBeforeMount(() => {
//
data.row_id && handlerGetDetailed();
})
const handlerSubmitForm = (formName) => {
data.row_id ? handlerEditInfo() : handlerAddInfo()
}
//
const handlerAddInfo = () => {
//
const request_data = JSON.parse(JSON.stringify(form_config.form_data));
//
request_data.create_date = dayjs(request_data.create_date).format("YYYY-MM-DD HH:mm:ss");
InfoCreate(request_data).then(response => {
//
proxy.$message.success(response.message);
//
go(-1);
})
}
//
const handlerEditInfo = () => {
console.log(22222)
//
const request_data = JSON.parse(JSON.stringify(form_config.form_data));
InfoEdit(request_data).then(response => {
//
proxy.$message.success(response.message);
//
go(-1);
})
}
//
const handlerGetDetailed = () => {
GetDetailed({id:data.row_id}).then(response => {
form_config.form_data = response.data
})
}
return {
handlerSubmitForm,
form_config,
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,169 @@
<template>
<BasisTable :search="false" :columns="table_config.table_header" :config="table_config.config" :request="table_config.request">
<template v-slot:operation="slotData">
<el-button type="danger" @click="handlerDetailed(slotData.data.id)" v-has-button="'init:edit'">编辑</el-button>
</template>
</BasisTable>
</template>
<script>
import { useRouter } from "vue-router";
import{ reactive, provide } from "vue";
import { getDate } from "@/utils/common";
import { categoryHook } from "@/hook/infoHook";
// components
import BasisTable from "@c/table";
// `
import globalData from "@/js/data";
export default {
name: 'InfoIndex',
components: { BasisTable },
props: {},
setup(props){
const search_config = reactive({
label_width: "100px",
form_button_group: [
{ label: "新增", type: "danger", callback: () => addInfo() },
{ label: "其他按钮", callback: () => {} }
],
form_button: {
reset_button: true
},
form_item: [
{
type: "cascader",
label: "类别",
prop: "category_id",
props: {
label: "category_name",
value: "id"
},
url: "category",
label_width: 80,
col: 6
},
{
type: "select",
label: "发布状态",
prop: "status",
options: globalData.whether,
col: 4
},
{
type: "keyword",
label: "关键字",
prop: "keyword",
options: [
{ label: "ID", value: "id" },
{ label: "标题", value: "title" }
],
col: 8
},
],
button_col: 6,
form_data: {
category_id: "",
status: ""
}
})
provide("search_config", search_config);
// router
const { push } = useRouter();
// hook
const { infoData: category_data, handlerGetCategory: getList } = categoryHook();
const data = reactive({
category: 0,
keyword_options: [
{ label: "ID", value: "id" },
{ label: "标题", value: "title" }
],
});
const request_data = reactive({
pageNumber: 1,
pageSize: 10,
category_id: [],
key: "",
keyword: ""
})
const table_config = reactive({
table_header: [
{ label: "标题", prop: "title", width: "500" },
{
label: "类别",
prop: "category_name",
width: "200",
type: "function",
callback: (row) => {
return `<a href="http://www.web-jshtml.cn" title="手把手撸码前端">${row.category_name}</a>`;
}
},
{
label: "日期",
prop: "createDate",
type: "function",
callback: (row) => {
return getDate({ value: row.createDate * 1000 });
}
},
{
label: "发布状态",
prop: "status",
type: "switch",
key_id: "id",
api_module: "info",
api_key: "info_status",
options: globalData.whether
},
{
label: "操作",
type: "slot",
slot_name: "operation",
width: "200",
delete_elem: true
}
],
config: {},
request: {
url: "info",
data: {
pageNumber: 1,
pageSize: 10,
},
delete_key: "id"
}
})
//
const handlerDetailed = (id) => {
push({
path: "/newsDetailed",
query: { id }
})
}
const addInfo = () => {
push({
path: "/newsDetailed"
})
}
const handlerOnload = (data) => {
console.log(data)
}
return {
data,
category_data,
request_data,
table_config,
handlerDetailed,
handlerOnload,
addInfo
}
}
}
</script>
<style lang="scss" scoped>
.width-160 { width: 160px; }
.width-100 { width: 100px; }
.width-180 { width: 180px; }
</style>

View File

@ -0,0 +1,324 @@
<template>
<BasisTable
:columns="table_config.table_header"
:config="table_config.config"
:request="table_config.request"
>
<template v-slot:operation="slotData">
<el-button type="danger" @click="handlerMenu('edit', slotData.data.menu_id)" v-has-button="'menu:edit'">编辑</el-button>
<el-button type="danger" @click="handlerMenu('add_sub', slotData.data.menu_id)" v-has-button="'menu:add_sub'">添加子菜单</el-button>
</template>
</BasisTable>
<el-dialog :title="data.title" @closed="dislogClose" @open="dialogOpen" v-model="dialogVisible" width="30%" :close-on-click-modal="false" :close-on-press-escape="false">
<BasisForm
ref="basisFormRef"
label-width="100px"
:item="form_config.form_item"
:button="form_config.form_button"
:field="form_config.form_data"
:loading="form_config.form_loading"
@callback="handlerSubmit"
>
<template v-slot:menu_function>
<el-row :gutter="10">
<el-col :span="9">页面元素</el-col>
<el-col :span="9">标识符</el-col>
<el-col :span="4">操作</el-col>
</el-row>
<el-row :gutter="10" v-for="(item, index) in form_config.page_item" :key="item.id">
<el-col :span="9"><el-input v-model.trim="item.label" size="small" /></el-col>
<el-col :span="9"><el-input v-model.trim="item.value" size="small" /></el-col>
<el-col :span="4"><el-button size="small" @click="handlerDel(index)">删除</el-button></el-col>
</el-row>
<el-button type="primary" @click="handlerPush">添加功能</el-button>
</template>
</BasisForm>
</el-dialog>
</template>
<script>
import { useRouter } from "vue-router";
import{ reactive, provide, ref } from "vue";
// store
import { useStore } from "vuex";
// components
import BasisTable from "@c/table";
import BasisForm from "@c/form";
//
import globalData from "@/js/data";
// API
import { MenuCreate, MenuDetailed, MenuUpdate } from "@/api/menu";
// utils
import { formatRequestData, formatTree } from "@/utils/format";
export default {
name: 'MenuIndex',
components: { BasisTable, BasisForm },
props: {},
setup(props){
const dialogVisible = ref(false);
// router
const { push } = useRouter();
// store
const store = useStore();
//
const page_fun_json = { value: "", label: "" };
const data = reactive({
menu_handler_flag: "",
row_id: 0,
title: "",
title_item: {
add: "添加一级菜单",
edit: "编辑菜单",
add_sub: "添加子级菜单"
}
})
//
const table_config = reactive({
table_header: [
{ label: "菜单名称", prop: "menu_name" },
{ label: "菜单路径", prop: "menu_path" },
{ label: "映射组件", prop: "menu_component" },
{ label: "重定向", prop: "menu_redirect" },
{
label: "是否隐藏",
prop: "menu_hidden",
type: "switch",
key_id: "menu_id",
api_module: "menu",
api_key: "hidden_status",
},
{
label: "是否禁用",
prop: "menu_disabled",
type: "switch",
key_id: "menu_id",
api_module: "menu",
api_key: "disabled_status",
},
{ label: "操作", type: "slot", slot_name: "operation", width: "280", delete_elem: true }
],
config: {
selection: false,
batch_delete: false,
pagination: false,
action_request: true,
row_key: "menu_id"
},
request: {
url: "menu",
data: {},
delete_key: "menu_id",
format_data: (data) => formatTree(data, "menu_id", "parent_id", "children", 0)
}
})
const search_config = reactive({
label_width: "70px",
form_button_group: [
{ label: "新增一级菜单", type: "danger", callback: () => handlerMenu('add') },
],
form_button: {
reset_button: true
},
form_item: [
{
type: "select",
label: "禁启用",
prop: "menu_disabled",
width: "100px",
options: globalData.whether
},
{
type: "keyword",
label: "关键字",
prop: "keyword",
options: [
{ label: "菜单名称", value: "menu_name" },
{ label: "菜单路径", value: "menu_path" },
{ label: "组件名称", value: "menu_component" }
]
},
],
form_data: {
menu_disabled: ""
}
})
provide("search_config", search_config);
const form_config = reactive({
form_item: [
{
type: "input",
label: "菜单名称",
prop: "menu_name",
width: "300px",
required: true
},
{
type: "input",
label: "菜单路径",
prop: "menu_path",
width: "300px",
required: true
},
{
type: "input",
label: "路由名称",
prop: "menu_router",
width: "300px",
required: true
},
{
type: "input",
label: "映射组件",
prop: "menu_component",
width: "300px",
required: true
},
{ type: "upload", label: "图标", prop: "menu_icon" },
{ type: "inputNumber", label: "排序", prop: "menu_sort", required: true },
{
type: "radio",
label: "是否禁用",
prop: "menu_disabled",
options: globalData.whether
},
{
type: "radio",
label: "是否隐藏",
prop: "menu_hidden",
options: globalData.whether
},
{
type: "radio",
label: "是否缓存",
prop: "menu_keep",
options: globalData.whether
},
{
type: "input",
label: "重定向",
prop: "menu_redirect",
width: "300px"
},
{
type: "slot",
label: "页面功能",
slot_name: "menu_function"
}
],
form_button: [
{ label: "确认提交", type: "danger", key: "submit" }
],
form_data: {
menu_name: "",
menu_path: "",
menu_router: "",
menu_component: "",
menu_sort: 0,
menu_disabled: "2",
menu_hidden: "2",
menu_keep: "2",
menu_redirect: "",
menu_icon: ""
},
form_loading: false,
page_item: [JSON.parse(JSON.stringify(page_fun_json))]
})
const handlerSubmit = (value) => {
if(data.menu_handler_flag === "edit" && data.row_id) {
handlerMenuEdit();
}
if(data.menu_handler_flag === "add" || data.menu_handler_flag === "add_sub") {
handlerMenuCreate();
}
}
const basisFormRef = ref(null);
const dislogClose = () => {
form_config.page_item = [JSON.parse(JSON.stringify(page_fun_json))];
data.row_id = "";
data.menu_handler_flag = "";
basisFormRef.value && basisFormRef.value.handlerFormReset();
dialogVisible.value = false;
}
const dialogOpen = () => {
if(data.menu_handler_flag === "edit" && data.row_id) {
handlerMenuDetailed();
}
}
const handlerPush = () => {
form_config.page_item.push(JSON.parse(JSON.stringify(page_fun_json)));
}
const handlerDel = (index) => {
form_config.page_item.splice(index, 1);
}
const formatPageItem = () => {
const data = Object.assign([], form_config.page_item);
const dataItem = data.filter(item => item.label && item.value);
return JSON.stringify(dataItem);
}
const handlerMenu = (type, id = "") => {
data.menu_handler_flag = type;
data.row_id = id;
dialogVisible.value = true;
//
data.title = data.title_item[type];
}
const handlerMenuDetailed = () => {
form_config.form_loading = true;
MenuDetailed({menu_id: data.row_id}).then(response => {
form_config.form_loading = false;
// form
form_config.form_data = formatRequestData(response.data, form_config.form_data);
//
const pageItemInit = response.data.menu_fun;
pageItemInit && (form_config.page_item = JSON.parse(pageItemInit));
}).catch(error => {
form_config.form_loading = false;
})
}
const handlerMenuEdit = () => {
form_config.form_loading = true;
MenuUpdate({
...form_config.form_data,
menu_fun: formatPageItem(),
menu_id: data.row_id
}).then(response => {
form_config.form_loading = false;
dislogClose();
store.commit("app/SET_TABLE_REQUEST");
}).catch(error => {
form_config.form_loading = false;
})
}
const handlerMenuCreate = () => {
form_config.form_loading = true;
const request_data = {
...form_config.form_data,
menu_fun: formatPageItem()
}
if(data.menu_handler_flag === "add_sub") { request_data.parent_id = data.row_id; }
MenuCreate(request_data).then(response => {
form_config.form_loading = false;
dislogClose();
store.commit("app/SET_TABLE_REQUEST");
}).catch(error => {
form_config.form_loading = false;
})
}
return {
table_config,
dialogVisible,
form_config,
basisFormRef,
handlerSubmit,
dislogClose,
dialogOpen,
handlerDel,
handlerPush,
handlerMenu,
data
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,100 @@
<template>
<BasisTable :columns="table_config.table_header" :config="table_config.config" :request="table_config.request">
<template v-slot:operation="slotData">
<el-button type="danger" @click="handlerRole(slotData.data.role_id)">编辑</el-button>
</template>
</BasisTable>
<DialogRole v-model:flag="data.dialogFlag" v-model:row-id="data.row_id" />
</template>
<script>
import{ reactive, provide } from "vue";
// components
import BasisTable from "@c/table";
import DialogRole from "./components/dialogRole";
//
import globalData from "@/js/data";
export default {
name: 'InfoIndex',
components: { BasisTable, DialogRole },
props: {},
setup(props){
const data = reactive({
dialogFlag: false,
row_id: ""
})
const table_config = reactive({
table_header: [
{ label: "角色名称", prop: "role_name" },
{
label: "是否禁用",
prop: "role_disabled",
type: "switch",
key_id: "role_id",
api_module: "role",
api_key: "status"
},
{
label: "操作",
type: "slot",
slot_name: "operation",
width: "200",
delete_elem: true
}
],
config: {
action_request: true,
has_button_delete: "role:delete",
has_button_batch_delete: "role:batch_delete"
},
request: {
url: "role",
data: {
pageNumber: 1,
pageSize: 10,
},
delete_key: "role_id"
}
})
const search_config = reactive({
label_width: "80px",
form_button_group: [
{ label: "添加角色", type: "danger", callback: () => handlerRole() },
],
form_button: {
reset_button: true
},
form_item: [
{
type: "input",
label: "角色名称",
prop: "role_name",
placeholder: "请输入角色名称"
},
{
type: "select",
label: "禁用状态",
prop: "role_disabled",
width: "100px",
options: globalData.whether
},
],
form_data: {
role_name: "",
role_disabled: ""
}
})
provide("search_config", search_config);
const handlerRole = (id = "") => {
data.row_id = id;
data.dialogFlag = true;
}
return {
table_config,
data,
handlerRole
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,115 @@
<template>
<BasisTable
:columns="table_config.table_header"
:config="table_config.config"
:request="table_config.request"
>
<template v-slot:operation="slotData">
<el-button type="danger" @click="handlerDialog(slotData.data.id)" v-has-button="'user:edit'">编辑</el-button>
<el-button type="danger" @click="handlerPassword(slotData.data.id)" v-has-button="'user:password'">修改密码</el-button>
</template>
</BasisTable>
<DialogUser v-model:flag="dialogFlag" v-model:row-id="row_id" />
<DialogPass v-model:flag="dialogPassFlag" v-model:row-id="row_id" title="修改密码" />
</template>
<script>
import { reactive, provide, toRefs } from "vue";
// components
import BasisTable from "@c/table";
import DialogUser from "./components/dialogUser";
import DialogPass from "./components/dialogPassword";
//
import globalData from "@/js/data";
export default {
name: "SystemUser",
components: { BasisTable, DialogUser, DialogPass },
props: {},
setup(props){
const data = reactive({
dialogFlag: false,
dialogPassFlag: false,
row_id: ""
})
//
const table_config = reactive({
table_header: [
{ label: "用户名", prop: "username" },
{ label: "真实姓名", prop: "truename" },
{ label: "角色类型", prop: "role_type" },
{
label: "帐号状态",
prop: "user_disabled",
type: "switch",
key_id: "id",
api_module: "user",
api_key: "status"
},
{ label: "创建时间", prop: "user_createtime" },
{ label: "操作", type: "slot", slot_name: "operation", width: "280", delete_elem: true }
],
config: {
action_request: true,
has_button_delete: "user:delete",
has_button_batch_delete: "user:batch_delete"
},
request: {
url: "user",
data: {
pageSize: 10,
pageNumber: 1
},
delete_key: "id"
}
})
//
const search_config = reactive({
label_width: "70px",
form_button_group: [
{ label: "添加用户", type: "danger", callback: () => handlerDialog() },
],
form_button: {
reset_button: true
},
form_item: [
{
type: "select",
label: "禁启用",
prop: "user_disabled",
width: "100px",
options: globalData.whether
},
{
type: "keyword",
label: "关键字",
prop: "keyword",
options: [
{ label: "用户名", value: "username" },
{ label: "真实姓名", value: "truename" },
{ label: "角色类型", value: "role_type" }
]
},
],
form_data: {
user_disabled: ""
}
})
provide("search_config", search_config);
const handlerDialog = (id = '') => {
data.row_id = id;
data.dialogFlag = true;
}
const handlerPassword = (id) => {
data.row_id = id;
data.dialogPassFlag = true;
}
return {
table_config,
handlerDialog,
handlerPassword,
...toRefs(data)
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,95 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
:width="width"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="dialogClose"
@open="dialogOpen"
>
<BasisForm @callback="handlerSubmit" ref="basisFormRef" label-width="100px" :item="form_config.form_item" :button="form_config.form_button" :field="form_config.form_data" :loading="form_config.form_loading">
</BasisForm>
</el-dialog>
</template>
<script setup>
import { ref, watch, reactive, getCurrentInstance, defineProps, defineEmits } from "vue";
// components
import BasisForm from "@c/form";
// validate
import { checkPassword } from "@/utils/validate";
// hook
import { propsType, dialogHook } from "@/hook/dialogHook.js";
// sha1
import sha1 from "js-sha1";
// API
import { UpdatePass } from "@/api/user";
// store
import { useStore } from "vuex";
// Props
const props = defineProps({
...propsType,
rowId: {
type: [String, Number],
default: ""
}
})
const emit = defineEmits(["update:flag"])
//
const dialogVisible = ref(props.flag);
const width = ref(props.width);
const title = ref(props.title);
const basisFormRef = ref(null);
//
const { proxy } = getCurrentInstance();
// dialog
const { close } = dialogHook(emit);
// store
const store = useStore();
// form
const form_config = reactive({
form_item: [
{
type: "input",
label: "密码",
prop: "password",
width: "300px",
value_type: "password",
required: true
}
],
form_button: [
{ label: "确认修改", type: "danger", key: "submit" }
],
form_data: {
password: "",
},
form_loading: false
})
const dialogClose = () => {
dialogVisible.value = false;
close(basisFormRef);
}
const dialogOpen = () => {}
watch(() => props.flag, (newValue, oldValue) => {
dialogVisible.value = newValue;
})
/** 表单提交 */
const handlerSubmit = () => {
if(!props.rowId) { return false; } // ID
form_config.form_loading = true; //
UpdatePass({
password: sha1(form_config.form_data.password),
id: props.rowId
}).then(response => {
form_config.form_loading = false; //
proxy.$message.success(response.message); //
dialogClose(); //
}).catch(error => {
form_config.form_loading = false; //
})
}
</script>
<style lang='scss' scoped></style>

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