Merge remote-tracking branch 'origin/master'
This commit is contained in:
182
README.en.md
182
README.en.md
@@ -1,36 +1,168 @@
|
||||
# django-vue3-admin
|
||||
# Django-Vue3-Admin
|
||||
|
||||
#### Description
|
||||
django-vue3-admin
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
[preview](https://demo.django-vue-admin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
|
||||
|
||||
#### Installation
|
||||
💡 **「About」**
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
We are a group of young people who love Code. In this hot era, we hope to calm down and bring some of our colors and colors through code.
|
||||
|
||||
#### Instructions
|
||||
Because of love, so embrace the future
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
## framework introduction
|
||||
|
||||
#### Contribution
|
||||
💡 [django-vue-admin](https://gitee.com/dvadmin/django-vue-admin) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
* 🧑🤝🧑Front-end adoption[D2Admin](https://github.com/d2-projects/d2-admin) 、[Vue](https://cn.vuejs.org/)、[ElementUI](https://element.eleme.cn/)。
|
||||
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
|
||||
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),Supports the multi-terminal authentication system.
|
||||
* 👬Support loading dynamic permission menu, multi - way easy permission control.
|
||||
* 💏 Special thanks:[D2Admin](https://github.com/d2-projects/d2-admin) 、[Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin)。
|
||||
* 💡 💏 Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
|
||||
|
||||
## Online experience
|
||||
|
||||
#### Gitee Feature
|
||||
👩👧👦👩👧👦 demo address:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
* demo account:superadmin
|
||||
|
||||
* demo password:admin123456
|
||||
|
||||
👩👦👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
|
||||
|
||||
## communication
|
||||
|
||||
* Communication community:[click here](https://bbs.django-vue-admin.com)👩👦👦
|
||||
|
||||
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩👦👦
|
||||
|
||||
## source code url:
|
||||
|
||||
gitee(Main push):[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩👦👦
|
||||
|
||||
github:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩👦👦
|
||||
|
||||
## core function
|
||||
|
||||
1. 👨⚕️ Menu management: Configure the system menu, operation permissions, button permissions, back-end interface permissions, etc.
|
||||
2. 🧑⚕️ Department management: Configure the system organization (company, department, role).
|
||||
3. 👩⚕️ Role management: role menu permission allocation, data permission allocation, set roles according to the department for data range permission division.
|
||||
4. 🧑🎓 Rights Specifies the rights of the authorization role.
|
||||
5. 👨🎓 User management: The user is the system operator, this function mainly completes the system user configuration.
|
||||
6. 👬 Interface whitelist: specifies the interface that does not need permission verification.
|
||||
7. 🧑🔧 Dictionary management: Maintenance of some fixed data frequently used in the system.
|
||||
8. 🧑🔧 Regional management: to manage provinces, cities, counties and regions.
|
||||
9. 📁 Attachment management: Unified management of all files and pictures on the platform.
|
||||
10. 🗓 ️operation logs: log and query the system normal operation; Log and query system exception information.
|
||||
11.🔌 [plugins market] (<https://bbs.django-vue-admin.com/plugMarket.html>) : based on the Django framework - Vue - Admin application and plug-in development.
|
||||
|
||||
## plugins market 🔌
|
||||
|
||||
* Celery Asynchronous task:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
|
||||
* Upgrade center backend:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
|
||||
* Upgrade center front:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
|
||||
|
||||
## before start project you need:
|
||||
|
||||
~~~
|
||||
Python >= 3.8.0
|
||||
nodejs >= 14.0
|
||||
Mysql >= 5.7.0 (Optional. The default database is sqlite3. 8.0 is recommended)
|
||||
Redis(Optional, the latest edition)
|
||||
~~~
|
||||
|
||||
## frontend♝
|
||||
|
||||
```bash
|
||||
# clone code
|
||||
git clone https://gitee.com/liqianglog/django-vue-admin.git
|
||||
|
||||
# enter code dir
|
||||
cd web
|
||||
|
||||
# install dependence
|
||||
npm install --registry=https://registry.npm.taobao.org
|
||||
|
||||
# Start service
|
||||
npm run dev
|
||||
# Visit http://localhost:8080 in your browser
|
||||
# Parameters such as boot port can be configured in the #.env.development file
|
||||
# Build the production environment
|
||||
# npm run build
|
||||
```
|
||||
|
||||
## backend💈
|
||||
|
||||
~~~bash
|
||||
1. enter code dir cd backend
|
||||
2. copy ./conf/env.example.py to ./conf dir,rename as env.py
|
||||
3. in env.py configure database information
|
||||
mysql database recommended version: 8.0
|
||||
mysql database character set: utf8mb4
|
||||
4. install pip dependence
|
||||
pip3 install -r requirements.txt
|
||||
5. Execute the migration command:
|
||||
python3 manage.py makemigrations
|
||||
python3 manage.py migrate
|
||||
6. Initialization data
|
||||
python3 manage.py init
|
||||
7. Initialize provincial, municipal and county data:
|
||||
python3 manage.py init_area
|
||||
8. start backend
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
or daphne :
|
||||
daphne -b 0.0.0.0 -p 8000 application.asgi:application
|
||||
~~~
|
||||
|
||||
### visit backend swagger
|
||||
|
||||
* visit url:[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
|
||||
* account:`superadmin` password:`admin123456`
|
||||
|
||||
### docker-compose
|
||||
|
||||
~~~shell
|
||||
docker-compose up -d
|
||||
# Initialize backend data (first execution only)
|
||||
docker exec -ti dvadmin-django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init_area
|
||||
python manage.py init
|
||||
exit
|
||||
|
||||
frontend url:http://127.0.0.1:8080
|
||||
backend url:http://127.0.0.1:8080/api
|
||||
# Change 127.0.0.1 to your own public ip address on the server
|
||||
account:`superadmin` password:`admin123456`
|
||||
|
||||
# docker-compose stop
|
||||
docker-compose down
|
||||
# docker-compose restart
|
||||
docker-compose restart
|
||||
# docker-compose on start build
|
||||
docker-compose up -d --build
|
||||
~~~
|
||||
|
||||
## Demo screenshot✅
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Django-Vue-Admin
|
||||
# Django-Vue3-Admin
|
||||
|
||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://python.org/) [](https://docs.djangoproject.com/zh-hans/3.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"vue-grid-layout": "^3.0.0-beta1",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vxe-table": "^4.3.10",
|
||||
"xe-utils": "^3.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
17
web/src/utils/message.ts
Normal file
17
web/src/utils/message.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ElMessage, MessageOptions } from 'element-plus';
|
||||
|
||||
export function message(message: string, option?: MessageOptions) {
|
||||
ElMessage({ message, ...option });
|
||||
}
|
||||
export function successMessage(message: string, option?: MessageOptions) {
|
||||
ElMessage({ message, ...option, type: 'success' });
|
||||
}
|
||||
export function warningMessage(message: string, option?: MessageOptions) {
|
||||
ElMessage({ message, ...option, type: 'warning' });
|
||||
}
|
||||
export function errorMessage(message: string, option?: MessageOptions) {
|
||||
ElMessage({ message, ...option, type: 'error' });
|
||||
}
|
||||
export function infoMessage(message: string, option?: MessageOptions) {
|
||||
ElMessage({ message, ...option, type: 'info' });
|
||||
}
|
||||
53
web/src/views/system/config/api.ts
Normal file
53
web/src/views/system/config/api.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { request } from '/@/utils/service';
|
||||
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
|
||||
import XEUtils from 'xe-utils';
|
||||
|
||||
export const apiPrefix = '/api/system/system_config/';
|
||||
export function GetList(query: PageQuery) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export function GetObj(id: InfoReq) {
|
||||
return request({
|
||||
url: apiPrefix + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function AddObj(obj: AddReq) {
|
||||
return request({
|
||||
url: apiPrefix,
|
||||
method: 'post',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateObj(obj: EditReq) {
|
||||
return request({
|
||||
url: apiPrefix + obj.id + '/',
|
||||
method: 'put',
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
export function DelObj(id: DelReq) {
|
||||
return request({
|
||||
url: apiPrefix + id + '/',
|
||||
method: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
获取所有的model及字段信息
|
||||
*/
|
||||
export function GetAssociationTable() {
|
||||
return request({
|
||||
url: apiPrefix + 'get_association_table/',
|
||||
method: 'get',
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
156
web/src/views/system/config/components/addContent.vue
Normal file
156
web/src/views/system/config/components/addContent.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div style="padding: 20px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="所属分组" prop="parent">
|
||||
<el-select v-model="form.parent" placeholder="请选择分组" clearable>
|
||||
<el-option :label="item.title" :value="item.id" :key="index" v-for="(item, index) in parentOptions"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="key值" prop="key">
|
||||
<el-input v-model="form.key" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="表单类型" prop="form_item_type">
|
||||
<el-select v-model="form.form_item_type" placeholder="请选择" clearable>
|
||||
<el-option :label="item.label" :value="item.value" :key="index" v-for="(item, index) in dictionary('config_form_type')"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="[4, 5, 6].indexOf(form.form_item_type) > -1"
|
||||
label="字典key"
|
||||
prop="setting"
|
||||
:rules="[{ required: true, message: '不能为空' }]"
|
||||
>
|
||||
<el-input v-model="form.setting" placeholder="请输入dictionary中key值" clearable></el-input>
|
||||
</el-form-item>
|
||||
<div v-if="[13, 14].indexOf(form.form_item_type) > -1">
|
||||
<associationTable ref="associationTableRef" v-model="form.setting" @updateVal="associationTableUpdate"></associationTable>
|
||||
</div>
|
||||
<el-form-item label="校验规则">
|
||||
<el-select v-model="form.rule" multiple placeholder="请选择(可多选)" clearable>
|
||||
<el-option :label="item.label" :value="item.value" :key="index" v-for="(item, index) in ruleOptions"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="提示信息" prop="placeholder">
|
||||
<el-input v-model="form.placeholder" placeholder="请输入" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="form.sort" :min="0" :max="99"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">立即创建</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import associationTable from './components/associationTable.vue';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
let form: any = reactive({
|
||||
parent: null,
|
||||
title: null,
|
||||
key: null,
|
||||
form_item_type: '',
|
||||
rule: null,
|
||||
placeholder: null,
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const associationTableRef: any = ref<FormInstance>();
|
||||
const rules = reactive<FormRules>({
|
||||
parent: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
},
|
||||
],
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Za-z0-9_]+$/,
|
||||
message: '请输入数字、字母或下划线',
|
||||
},
|
||||
],
|
||||
form_item_type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
});
|
||||
let parentOptions: any = ref([]);
|
||||
let ruleOptions = ref([
|
||||
{
|
||||
label: '必填项',
|
||||
value: '{"required": true, "message": "必填项不能为空"}',
|
||||
},
|
||||
{
|
||||
label: '邮箱',
|
||||
value: '{ "type": "email", "message": "请输入正确的邮箱地址"}',
|
||||
},
|
||||
{
|
||||
label: 'URL地址',
|
||||
value: '{ "type": "url", "message": "请输入正确的URL地址"}',
|
||||
},
|
||||
]);
|
||||
const getParent = () => {
|
||||
api
|
||||
.GetList({
|
||||
parent__isnull: true,
|
||||
limit: 999,
|
||||
})
|
||||
.then((res: any) => {
|
||||
parentOptions.value = res.data;
|
||||
});
|
||||
};
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.AddObj(form).then((res: any) => {
|
||||
if (res.code == 2000) successMessage('新增成功');
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 关联表数据更新
|
||||
const associationTableUpdate = () => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (associationTableRef) {
|
||||
if (!associationTableRef.onSubmit()) {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return reject(false);
|
||||
}
|
||||
const { formObj } = associationTableRef;
|
||||
form.setting = formObj;
|
||||
return resolve(true);
|
||||
} else {
|
||||
return resolve(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getParent();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
60
web/src/views/system/config/components/addTabs.vue
Normal file
60
web/src/views/system/config/components/addTabs.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div style="padding: 20px">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="form.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="key值" prop="key">
|
||||
<el-input v-model="form.key"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">立即创建</el-button>
|
||||
<el-button>取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import { ref, reactive } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
let form = reactive({
|
||||
title: null,
|
||||
key: null,
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const rules = reactive<FormRules>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入',
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Za-z0-9]+$/,
|
||||
message: '只能是英文和数字',
|
||||
},
|
||||
],
|
||||
});
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
api.AddObj(form).then((res: any) => {
|
||||
if (res.code == 2000) successMessage('新增成功');
|
||||
});
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="formObj" ref="associationRef">
|
||||
<el-form-item label="关联表" prop="table" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.table" filterable clearable placeholder="请选择" @change="handleChange">
|
||||
<el-option v-for="item in tableOptions" :key="item.table" :label="item.tableName" :value="item.table">
|
||||
<span style="float: left">{{ item.tableName }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.table }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="显示字段" prop="field" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.field" filterable clearable placeholder="请选择">
|
||||
<el-option v-for="item in labelOptions" :key="item.table" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="储存字段" prop="primarykey" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.primarykey" filterable clearable placeholder="请选择">
|
||||
<el-option v-for="(item, index) in labelOptions" :key="index" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="过滤条件" prop="oldSearchField" :rules="[{ required: true, message: '必填项', trigger: 'blur' }]">
|
||||
<el-select v-model="formObj.oldSearchField" multiple filterable clearable placeholder="请选择" @change="handleSearch">
|
||||
<el-option v-for="(item, index) in labelOptions" :key="index" :label="item.title" :value="item.field">
|
||||
<span style="float: left">{{ item.field }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../../api';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { successMessage } from '/@/utils/message';
|
||||
let formObj: any = reactive({
|
||||
table: null,
|
||||
primarykey: null,
|
||||
field: null,
|
||||
searchField: null,
|
||||
oldSearchField: null,
|
||||
});
|
||||
let searchField = ref('');
|
||||
let tableOptions: any = ref([]);
|
||||
let labelOptions: any = ref([]);
|
||||
const associationRef: any = ref<FormInstance>();
|
||||
|
||||
const emits = defineEmits(['updateVal']);
|
||||
const props = defineProps(['value']);
|
||||
// 初始化数据
|
||||
const init = () => {
|
||||
api.GetAssociationTable().then((res: any) => {
|
||||
const { data } = res.data;
|
||||
tableOptions = data;
|
||||
// 设置默认选中
|
||||
formObj.table = data[0].table;
|
||||
labelOptions = data[0].tableFields;
|
||||
formObj.primarykey = 'id';
|
||||
formObj.field = 'id';
|
||||
});
|
||||
};
|
||||
// 选中事件
|
||||
const handleChange = (val: any) => {
|
||||
const { tableFields } = tableOptions.find((item: any) => {
|
||||
return item.table === val;
|
||||
});
|
||||
labelOptions = tableFields;
|
||||
};
|
||||
|
||||
// 过滤条件选中
|
||||
const handleSearch = (val: any) => {
|
||||
const fields = labelOptions.filter((item: any) => {
|
||||
return val.indexOf(item.field) > -1;
|
||||
});
|
||||
formObj.searchField = fields;
|
||||
};
|
||||
// 更新数据
|
||||
const handleUpdate = () => {
|
||||
emits('updateVal', formObj);
|
||||
};
|
||||
// 数据验证
|
||||
const onSubmit = () => {
|
||||
let res = false;
|
||||
associationRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
res = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
505
web/src/views/system/config/components/formContent.vue
Normal file
505
web/src/views/system/config/components/formContent.vue
Normal file
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">变量标题</el-col>
|
||||
<el-col :span="10">变量值</el-col>
|
||||
<el-col :span="4">变量名</el-col>
|
||||
<el-col :span="2">是否前端配置</el-col>
|
||||
<el-col :span="3" :offset="1">操作</el-col>
|
||||
</el-row>
|
||||
<el-form ref="formRef" :model="form" label-width="240px" label-position="left" style="margin-top: 20px">
|
||||
<el-form-item
|
||||
:label="item.title"
|
||||
:prop="['array'].indexOf(item.form_item_type_label) > -1 ? '' : item.key"
|
||||
:key="index"
|
||||
:rules="item.rule || []"
|
||||
v-for="(item, index) in formList"
|
||||
>
|
||||
<template slot="label">
|
||||
<el-input v-if="item.edit" v-model="item.title" style="display: inline-block; width: 200px" placeholder="请输入标题"></el-input>
|
||||
<span v-else>{{ item.title }}</span>
|
||||
</template>
|
||||
<el-col :span="11">
|
||||
<!-- 文本 -->
|
||||
<el-input
|
||||
:key="index"
|
||||
v-if="['text', 'textarea'].indexOf(item.form_item_type_label) > -1"
|
||||
:type="item.form_item_type_label"
|
||||
v-model="form[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
></el-input>
|
||||
|
||||
<el-input-number :key="index + 1" v-else-if="item.form_item_type_label === 'number'" v-model="form[item.key]" :min="0"></el-input-number>
|
||||
<!-- datetime、date、time -->
|
||||
<el-date-picker
|
||||
v-else-if="['datetime', 'date', 'time'].indexOf(item.form_item_type_label) > -1"
|
||||
v-model="form[item.key]"
|
||||
:key="index + 2"
|
||||
:type="item.form_item_type_label"
|
||||
:placeholder="item.placeholder"
|
||||
>
|
||||
</el-date-picker>
|
||||
<!-- select -->
|
||||
<el-select
|
||||
:key="index + 3"
|
||||
v-else-if="item.form_item_type_label === 'select'"
|
||||
v-model="form[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
>
|
||||
<el-option v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
<!-- checkbox -->
|
||||
<el-checkbox-group
|
||||
:key="index + 4"
|
||||
v-else-if="item.form_item_type_label === 'checkbox'"
|
||||
v-model="form[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
>
|
||||
<el-checkbox v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<!-- radio -->
|
||||
<el-radio-group
|
||||
:key="index + 5"
|
||||
v-else-if="item.form_item_type_label === 'radio'"
|
||||
v-model="form[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
clearable
|
||||
>
|
||||
<el-radio v-for="item in dictionary(item.setting) || []" :key="item.value" :label="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- switch -->
|
||||
<el-switch
|
||||
:key="index + 6"
|
||||
v-else-if="item.form_item_type_label === 'switch'"
|
||||
v-model="form[item.key]"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
>
|
||||
</el-switch>
|
||||
<!-- 图片 -->
|
||||
<div v-else-if="['img', 'imgs'].indexOf(item.form_item_type_label) > -1" :key="index + 7">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:accept="'image/*'"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-success="
|
||||
(response:any, file:any, fileList:any) => {
|
||||
handleUploadSuccess(response, file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:on-error="handleError"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="
|
||||
(file:any, fileList:any) => {
|
||||
beforeRemove(file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:multiple="item.form_item_type_label !== 'img'"
|
||||
:limit="item.form_item_type_label === 'img' ? 1 : 5"
|
||||
:ref="'imgUpload_' + item.key"
|
||||
:data-keyname="item.key"
|
||||
:file-list="item.value ? item.value : []"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<!-- 文件 -->
|
||||
<div v-else-if="['file'].indexOf(item.form_item_type_label) > -1" :key="index + 8">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="uploadHeaders"
|
||||
name="file"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:on-success="
|
||||
(response:any, file:any, fileList:any) => {
|
||||
handleUploadSuccess(response, file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:on-error="handleError"
|
||||
:on-exceed="handleExceed"
|
||||
:before-remove="
|
||||
(file:any, fileList:any) => {
|
||||
beforeRemove(file, fileList, item.key);
|
||||
}
|
||||
"
|
||||
:limit="5"
|
||||
:ref="'fileUpload_' + item.key"
|
||||
:data-keyname="item.key"
|
||||
:file-list="item.value"
|
||||
list-type="picture-card"
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
<div slot="tip" class="el-upload__tip">选取图片后,需手动上传到服务器,并且只能上传jpg/png文件</div>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialogImgVisible">
|
||||
<img width="100%" :src="dialogImageUrl" alt="" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<!-- 关联表 -->
|
||||
<div v-else-if="['foreignkey', 'manytomany'].indexOf(item.form_item_type_label) > -1" :key="index + 9">
|
||||
<table-selector
|
||||
v-model="form[item.key]"
|
||||
:el-props="{
|
||||
pagination: true,
|
||||
columns: item.setting.searchField,
|
||||
}"
|
||||
:dict="{
|
||||
url: '/api/system/system_config/get_table_data/' + item.id + '/',
|
||||
value: item.setting.primarykey,
|
||||
label: item.setting.field,
|
||||
}"
|
||||
:pagination="true"
|
||||
:multiple="item.form_item_type_label === 'manytomany'"
|
||||
></table-selector>
|
||||
</div>
|
||||
<!-- 数组 -->
|
||||
<div v-else-if="item.form_item_type_label === 'array'" :key="index + 10">
|
||||
<vxe-table
|
||||
border
|
||||
resizable
|
||||
auto-resize
|
||||
show-overflow
|
||||
keep-source
|
||||
:ref="'xTable_' + item.key"
|
||||
height="200"
|
||||
:edit-rules="validRules"
|
||||
:edit-config="{ trigger: 'click', mode: 'row', showStatus: true }"
|
||||
>
|
||||
<vxe-column field="title" title="标题" :edit-render="{ autofocus: '.vxe-input--inner' }">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.title" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="key" title="键名" :edit-render="{ autofocus: '.vxe-input--inner' }">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.key" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="value" title="键值" :edit-render="{}">
|
||||
<template #edit="{ row }">
|
||||
<vxe-input v-model="row.value" type="text"></vxe-input>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="操作" width="100" show-overflow>
|
||||
<template #default="{ row, index }">
|
||||
<el-popover placement="top" width="160" v-model="childRemoveVisible">
|
||||
<p>删除后无法恢复,确定删除吗?</p>
|
||||
<div style="text-align: right; margin: 0">
|
||||
<el-button size="mini" type="text" @click="childRemoveVisible = false">取消</el-button>
|
||||
<el-button type="primary" size="mini" @click="onRemoveChild(row, index, item.key)">确定</el-button>
|
||||
</div>
|
||||
<el-button type="text" slot="reference">删除</el-button>
|
||||
</el-popover>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div>
|
||||
<el-button size="mini" @click="onAppend('xTable_' + item.key)">追加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4" :offset="1">
|
||||
<el-input v-if="item.edit" v-model="item.new_key" style="width: 200px" placeholder="请输入变量key">
|
||||
<template slot="prepend">
|
||||
<span style="padding: 0px 5px">{{ editableTabsItem.key }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<span v-else>{{ editableTabsItem.key }}.{{ item.key }}</span>
|
||||
</el-col>
|
||||
<el-col :span="3" :offset="1">
|
||||
<el-switch v-model="item.status" active-color="#13ce66" inactive-color="#ff4949"> </el-switch>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button v-if="item.edit" size="mini" type="primary" icon="el-icon-success" @click="onEditSave(item)"></el-button>
|
||||
<el-button v-else size="mini" type="primary" icon="el-icon-edit" @click="onEdit(index)"></el-button>
|
||||
<el-popconfirm title="确定删除该条数据吗?" @confirm="onDelRow(item)">
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" slot="reference"></el-button>
|
||||
</el-popconfirm>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as api from '../api';
|
||||
import { dictionary } from '/@/utils/dictionary';
|
||||
import { getBaseURL } from '/@/utils/baseUrl';
|
||||
import { ref, reactive, watch, nextTick } from 'vue';
|
||||
import type { FormInstance, FormRules, TableInstance } from 'element-plus';
|
||||
import { successMessage, errorMessage } from '/@/utils/message';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
const props = defineProps(['options', 'editableTabsItem']);
|
||||
|
||||
let form: any = reactive({});
|
||||
let formList: any = ref([]);
|
||||
let childTableData = ref([]);
|
||||
let childRemoveVisible = ref(false);
|
||||
const validRules = reactive<FormRules>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '必须填写',
|
||||
},
|
||||
],
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
let uploadUrl = ref(getBaseURL + 'api/system/file/');
|
||||
let uploadHeaders = ref({
|
||||
Authorization: 'JWT ' + Session.get('token'),
|
||||
});
|
||||
let dialogImageUrl = ref('');
|
||||
let dialogImgVisible = ref(false);
|
||||
let uploadImgKey = ref(null);
|
||||
|
||||
// 获取数据
|
||||
const getInit = () => {
|
||||
api.GetList({ parent: props.options.id, limit: 999 }).then((res: any) => {
|
||||
let data = res.data;
|
||||
formList = data;
|
||||
const formData: any = {};
|
||||
for (const item of data) {
|
||||
const key = item.key;
|
||||
if (item.value) {
|
||||
formData[key] = item.value;
|
||||
} else {
|
||||
if ([5, 12, 14].indexOf(item.form_item_type) !== -1) {
|
||||
formData[key] = [];
|
||||
} else {
|
||||
formData[key] = undefined;
|
||||
}
|
||||
}
|
||||
if (item.form_item_type_label === 'array') {
|
||||
console.log('test');
|
||||
nextTick(() => {
|
||||
const tableName = 'xTable_' + key;
|
||||
const tabelRef = ref<TableInstance>();
|
||||
console.log(tabelRef);
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// $table.loadData(item.chinldern);
|
||||
});
|
||||
}
|
||||
}
|
||||
form = JSON.parse(JSON.stringify(formData));
|
||||
});
|
||||
};
|
||||
|
||||
// 提交数据
|
||||
const onSubmit = () => {
|
||||
// const form = JSON.parse(JSON.stringify(form));
|
||||
const keys = Object.keys(form);
|
||||
const values = Object.values(form);
|
||||
for (const index in formList) {
|
||||
const item = formList[index];
|
||||
// eslint-disable-next-line camelcase
|
||||
const form_item_type_label = item.form_item_type_label;
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
if (form_item_type_label === 'array') {
|
||||
const parentId = item.id;
|
||||
const tableName = 'xTable_' + item.key;
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// const { tableData } = $table.getTableData();
|
||||
// for (const child of tableData) {
|
||||
// if (!child.id && child.key && child.value) {
|
||||
// child.parent = parentId;
|
||||
// child.id = null;
|
||||
// formList.push(child);
|
||||
// }
|
||||
// }
|
||||
// // 必填项的判断
|
||||
// for (const arr of item.rule) {
|
||||
// if (arr.required && tableData.length === 0) {
|
||||
// errorMessage(item.title + '不能为空');
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// item.value = tableData;
|
||||
}
|
||||
// 赋值操作
|
||||
keys.map((mapKey, mapIndex) => {
|
||||
if (mapKey === item.key) {
|
||||
if (item.form_item_type_label !== 'array') {
|
||||
item.value = values[mapIndex];
|
||||
}
|
||||
// 必填项的验证
|
||||
if (['img', 'imgs'].indexOf(item.form_item_type_label) > -1) {
|
||||
for (const arr of item.rule) {
|
||||
if (arr.required && item.value === null) {
|
||||
errorMessage(item.title + '不能为空');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// formRef.clearValidate();
|
||||
// formRef.validate((valid) => {
|
||||
// if (valid) {
|
||||
// api.saveContent(this.options.id, this.formList).then((res) => {
|
||||
// this.$message.success('保存成功');
|
||||
// this.refreshView();
|
||||
// });
|
||||
// } else {
|
||||
// console.log('error submit!!');
|
||||
// return false;
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
// 追加
|
||||
const onAppend = (tableName: any) => {
|
||||
// const $table = this.$refs[tableName][0];
|
||||
// const { tableData } = $table.getTableData();
|
||||
// const tableLength = tableData.length;
|
||||
// if (tableLength === 0) {
|
||||
// const { row: newRow } = $table.insert();
|
||||
// console.log(newRow);
|
||||
// } else {
|
||||
// const errMap = $table.validate().catch((errMap: any) => errMap);
|
||||
// if (errMap) {
|
||||
// errorMessage('校验不通过!');
|
||||
// } else {
|
||||
// const { row: newRow } = $table.insert();
|
||||
// console.log(newRow);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
// 子表删除
|
||||
const onRemoveChild = (row: any, index: any, refName: any) => {
|
||||
console.log(row, index);
|
||||
if (row.id) {
|
||||
api.DelObj(row.id).then((res: any) => {
|
||||
// this.refreshView();
|
||||
});
|
||||
} else {
|
||||
// this.childTableData.splice(index, 1);
|
||||
// const tableName = 'xTable_' + refName;
|
||||
// const tableData = this.$refs[tableName][0].remove(row);
|
||||
// console.log(tableData);
|
||||
}
|
||||
};
|
||||
|
||||
// 图片预览
|
||||
const handlePictureCardPreview = (file: any) => {
|
||||
dialogImageUrl = file.url;
|
||||
dialogImgVisible.value = true;
|
||||
};
|
||||
|
||||
// 判断是否为图片
|
||||
// 封装一个判断图片文件后缀名的方法
|
||||
const isImage = (fileName: any) => {
|
||||
if (typeof fileName !== 'string') return;
|
||||
const name = fileName.toLowerCase();
|
||||
return name.endsWith('.png') || name.endsWith('.jpeg') || name.endsWith('.jpg') || name.endsWith('.png') || name.endsWith('.bmp');
|
||||
};
|
||||
|
||||
// 上传成功
|
||||
const handleUploadSuccess = (response: any, file: any, fileList: any, imgKey: any) => {
|
||||
const that = this;
|
||||
const { code, msg } = response;
|
||||
if (code === 2000) {
|
||||
const { url } = response.data;
|
||||
const { name } = file;
|
||||
const type = isImage(name);
|
||||
if (!type) {
|
||||
errorMessage('只允许上传图片');
|
||||
} else {
|
||||
const uploadImgKey = form[imgKey];
|
||||
if (!uploadImgKey || uploadImgKey === '') {
|
||||
form[imgKey] = [];
|
||||
}
|
||||
// console.log(len)
|
||||
const dict = {
|
||||
name: name,
|
||||
url: getBaseURL() + url,
|
||||
};
|
||||
form[imgKey].push(dict);
|
||||
}
|
||||
} else {
|
||||
errorMessage('上传失败,' + JSON.stringify(msg));
|
||||
}
|
||||
};
|
||||
|
||||
// 上传失败
|
||||
const handleError = () => {
|
||||
errorMessage('上传失败');
|
||||
};
|
||||
|
||||
// 上传超出限制
|
||||
const handleExceed = () => {
|
||||
errorMessage('超过文件上传数量');
|
||||
};
|
||||
|
||||
// 删除时的钩子
|
||||
const beforeRemove = (file: any, fileList: any, key: any) => {
|
||||
var index = 0;
|
||||
form[key].map((value: any, inx: any) => {
|
||||
if (value.uid === file.uid) index = inx;
|
||||
});
|
||||
form[key].splice(index, 1);
|
||||
};
|
||||
|
||||
// 配置的行删除
|
||||
const onDelRow = (obj: any) => {
|
||||
api.DelObj(obj.id).then((res: any) => {
|
||||
// this.refreshView();
|
||||
});
|
||||
};
|
||||
|
||||
// 行编辑
|
||||
const onEdit = (index: any) => {
|
||||
// that.$set(that.formList[index], 'new_key', that.formList[index].key)
|
||||
// that.$set(that.formList[index], 'edit', true)
|
||||
};
|
||||
// 行编辑保存
|
||||
const onEditSave = (obj: any) => {
|
||||
obj.key = JSON.parse(JSON.stringify(obj.new_key));
|
||||
api.UpdateObj(obj).then((res: any) => {
|
||||
// this.refreshView();
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
props.options,
|
||||
(nv) => {
|
||||
if (nv && nv.id) {
|
||||
getInit();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
82
web/src/views/system/config/index.vue
Normal file
82
web/src/views/system/config/index.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<div>
|
||||
<el-header>
|
||||
<div class="yxt-flex-between">
|
||||
<div>
|
||||
<el-tag>系统配置:您可以对您的网站进行自定义配置</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<el-button-group>
|
||||
<el-button type="primary" size="small" :icon="FolderAdd" @click="tabsDrawer = true"> 添加分组 </el-button>
|
||||
<el-button size="small" type="warning" :icon="Edit" @click="contentDrawer = true"> 添加内容 </el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
</div>
|
||||
<div>
|
||||
<el-drawer v-if="tabsDrawer" title="添加分组" v-model="tabsDrawer" direction="rtl" size="30%">
|
||||
<addTabs></addTabs>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<div>
|
||||
<el-drawer v-if="contentDrawer" title="添加内容" v-model="contentDrawer" direction="rtl" size="30%">
|
||||
<addContent></addContent>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<el-tabs type="border-card" v-model="editableTabsValue">
|
||||
<el-tab-pane :key="index" v-for="(item, index) in editableTabs" :label="item.title" :name="item.key">
|
||||
<span slot="label" v-if="item.icon"><i :class="item.icon" style="font-weight: 1000; font-size: 16px"></i></span>
|
||||
<el-row v-if="item.icon">
|
||||
<el-col :offset="4" :span="8">
|
||||
<addContent></addContent>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<formContent v-else :options="item" :editableTabsItem="item"></formContent>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Edit, FolderAdd } from '@element-plus/icons-vue';
|
||||
import * as api from './api';
|
||||
import addTabs from './components/addTabs.vue';
|
||||
import addContent from './components/addContent.vue';
|
||||
import formContent from './components/formContent.vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
let tabsDrawer = ref(false);
|
||||
let contentDrawer = ref(false);
|
||||
let editableTabsValue = ref('base');
|
||||
let editableTabs: any = ref([]);
|
||||
|
||||
const getTabs = () => {
|
||||
api
|
||||
.GetList({
|
||||
limit: 999,
|
||||
parent__isnull: true,
|
||||
})
|
||||
.then((res: any) => {
|
||||
let data = res.data;
|
||||
data.push({
|
||||
title: '无',
|
||||
icon: 'el-icon-plus',
|
||||
key: 'null',
|
||||
});
|
||||
editableTabs.value = data;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTabs();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*用 flex 两边对齐*/
|
||||
.yxt-flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -3956,6 +3956,11 @@ vuedraggable-es@^4.1.1:
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
vxe-table@^4.3.10:
|
||||
version "4.3.10"
|
||||
resolved "https://registry.npmjs.org/vxe-table/-/vxe-table-4.3.10.tgz#4156d9542d61997d07c29a06e89208a3adc4825a"
|
||||
integrity sha512-qxLhA3hiAfxsm8+dbN1n7+FrRwMEzUB/676x67gEb3H63WFWulRvTc88LCe0itMcuYcpy7uZHn5ruRsz0KnorQ==
|
||||
|
||||
wangeditor@^4.7.5:
|
||||
version "4.7.15"
|
||||
resolved "https://registry.npmjs.org/wangeditor/-/wangeditor-4.7.15.tgz#38c5e279a79d0428e4fd77ae5be46367e9c819e5"
|
||||
|
||||
Reference in New Issue
Block a user