feat:优化重命名软件
This commit is contained in:
parent
4e17b86554
commit
23d9c83593
4
.gitignore
vendored
4
.gitignore
vendored
@ -101,6 +101,4 @@ dist
|
|||||||
.dynamodb/
|
.dynamodb/
|
||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
openRenamerBackend/database.db
|
|
||||||
openRenamerBackend/database.db
|
|
31
openRenamerBackend/.gitignore
vendored
31
openRenamerBackend/.gitignore
vendored
@ -1,15 +1,16 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/.vscode/
|
/.vscode/
|
||||||
.vscode
|
.vscode
|
||||||
node_modules/
|
node_modules/
|
||||||
/dist/
|
/dist/
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
yarn.lock
|
yarn.lock
|
||||||
log
|
log
|
||||||
sqls/history.json
|
sqls/history.json
|
||||||
static/*
|
static/*
|
||||||
!static/.gitkeep
|
!static/.gitkeep
|
||||||
|
database.db
|
@ -6,53 +6,53 @@ import ProcessHelper from '../util/ProcesHelper';
|
|||||||
import FileObj from '../vo/FileObj';
|
import FileObj from '../vo/FileObj';
|
||||||
|
|
||||||
class FileService {
|
class FileService {
|
||||||
static async readPath(pathStr: string, showHidden: boolean): Promise<Array<FileObj>> {
|
static async readPath(pathStr: string, showHidden: boolean): Promise<Array<FileObj>> {
|
||||||
pathStr = decodeURIComponent(pathStr);
|
pathStr = decodeURIComponent(pathStr);
|
||||||
let fileList = new Array();
|
let fileList = new Array();
|
||||||
if (pathStr.trim().length == 0) {
|
if (pathStr.trim().length == 0) {
|
||||||
//获取根目录路径
|
//获取根目录路径
|
||||||
if (config.isWindows) {
|
if (config.isWindows) {
|
||||||
//windows下
|
//windows下
|
||||||
let std: string = ((await ProcessHelper.exec('wmic logicaldisk get caption')) as Object)['stdout'].replace('Caption', '');
|
let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', '');
|
||||||
fileList = std
|
fileList = std
|
||||||
.split('\r\n')
|
.split('\r\n')
|
||||||
.filter((item) => item.trim().length > 0)
|
.filter((item) => item.trim().length > 0)
|
||||||
.map((item) => item.trim());
|
.map((item) => item.trim());
|
||||||
} else {
|
} else {
|
||||||
//linux下
|
//linux下
|
||||||
pathStr = '/';
|
pathStr = '/';
|
||||||
fileList = await fs.readdir(pathStr);
|
fileList = await fs.readdir(pathStr);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileList = await fs.readdir(pathStr);
|
fileList = await fs.readdir(pathStr);
|
||||||
}
|
}
|
||||||
let folderList: Array<FileObj> = new Array();
|
let folderList: Array<FileObj> = new Array();
|
||||||
let files: Array<FileObj> = new Array();
|
let files: Array<FileObj> = new Array();
|
||||||
for (let index in fileList) {
|
for (let index in fileList) {
|
||||||
try {
|
try {
|
||||||
let stat = await fs.stat(path.join(pathStr, fileList[index]));
|
let stat = await fs.stat(path.join(pathStr, fileList[index]));
|
||||||
if (fileList[index].startsWith('.')) {
|
if (fileList[index].startsWith('.')) {
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
(stat.isDirectory() ? folderList : files).push(
|
(stat.isDirectory() ? folderList : files).push(
|
||||||
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
|
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(stat.isDirectory() ? folderList : files).push(
|
(stat.isDirectory() ? folderList : files).push(
|
||||||
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
|
new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
folderList.sort((a, b) => a.name.localeCompare(b.name)).push(...files.sort((a, b) => a.name.localeCompare(b.name)));
|
folderList.sort((a, b) => a.name.localeCompare(b.name)).push(...files.sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
return folderList;
|
return folderList;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async checkExist(pathStr: string) {
|
static async checkExist(pathStr: string) {
|
||||||
return await fs.pathExists(pathStr);
|
return await fs.pathExists(pathStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileService;
|
export default FileService;
|
||||||
|
@ -11,13 +11,13 @@
|
|||||||
"@element-plus/icons": "^0.0.11",
|
"@element-plus/icons": "^0.0.11",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"element-plus": "^1.0.2-beta.48",
|
"dayjs": "^1.10.7",
|
||||||
|
"element-plus": "^1.2.0-beta.5",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0-0"
|
"vue-router": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
|
||||||
"@vue/cli-plugin-router": "~4.5.0",
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.0",
|
||||||
"@vue/compiler-sfc": "^3.0.0",
|
"@vue/compiler-sfc": "^3.0.0",
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-menu
|
<el-menu
|
||||||
:default-active="activeIndex"
|
:default-active="activeIndex"
|
||||||
class="el-menu-demo"
|
class="el-menu-demo"
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
@select="handleSelect"
|
@select="handleSelect"
|
||||||
>
|
>
|
||||||
<el-menu-item index="dealCenter">处理中心</el-menu-item>
|
<el-menu-item index="dealCenter">处理中心</el-menu-item>
|
||||||
<el-menu-item index="history">历史记录</el-menu-item>
|
<!-- <el-menu-item index="history">历史记录</el-menu-item> -->
|
||||||
</el-menu>
|
</el-menu>
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "Home",
|
name: "Home",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeIndex: "dealCenter",
|
activeIndex: "dealCenter",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav {
|
#nav {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
|
|
||||||
&.router-link-exact-active {
|
&.router-link-exact-active {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,145 +1,124 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-loading="loading" class="main">
|
<div v-loading="loading" class="main">
|
||||||
<el-breadcrumb separator="/">
|
<el-breadcrumb separator="/">
|
||||||
<el-breadcrumb-item
|
<el-breadcrumb-item><a @click.prevent="breadcrumbClick(-1)">根</a></el-breadcrumb-item>
|
||||||
><a @click.prevent="breadcrumbClick(-1)">根</a></el-breadcrumb-item
|
<el-breadcrumb-item v-for="(item, index) in pathList" :key="index">
|
||||||
>
|
<a v-if="index < pathList.length - 1" @click.prevent="breadcrumbClick(index)">{{ item }}</a>
|
||||||
<el-breadcrumb-item v-for="(item, index) in pathList" :key="index">
|
<span v-else>{{ item }}</span>
|
||||||
<a
|
</el-breadcrumb-item>
|
||||||
v-if="index < pathList.length - 1"
|
</el-breadcrumb>
|
||||||
@click.prevent="breadcrumbClick(index)"
|
|
||||||
>{{ item }}</a
|
<div class="fileList">
|
||||||
>
|
<div>
|
||||||
<span v-else>{{ item }}</span>
|
<el-input style="display: inline-block; width: 150px" type="text" size="small" placeholder="关键词过滤" v-model="filterText" />
|
||||||
</el-breadcrumb-item>
|
<el-button type="primary" @click="selectAll(true)" size="mini">全选</el-button>
|
||||||
</el-breadcrumb>
|
<el-button type="primary" @click="selectAll(false)" size="mini">全不选</el-button>
|
||||||
|
</div>
|
||||||
<div class="fileList">
|
<div v-for="(item, index) in filterFileList" :key="index">
|
||||||
<div>
|
<span class="folder" v-if="item.isFolder" @click="fileClick(item)">{{ item.name }}</span>
|
||||||
<el-input
|
<el-checkbox style="height: 1.4em" v-model="item.checked" v-else>{{ item.name }}</el-checkbox>
|
||||||
style="display: inline-block; width: 150px"
|
</div>
|
||||||
type="text"
|
</div>
|
||||||
size="small"
|
|
||||||
placeholder="关键词过滤"
|
<div>
|
||||||
v-model="filterText"
|
<el-button type="primary" @click="submit">确定</el-button>
|
||||||
/>
|
</div>
|
||||||
<el-button type="primary" @click="selectAll(true)" size="mini"
|
</div>
|
||||||
>全选</el-button
|
</template>
|
||||||
>
|
|
||||||
<el-button type="primary" @click="selectAll(false)" size="mini"
|
<script>
|
||||||
>全不选</el-button
|
import HttpUtil from "../utils/HttpUtil";
|
||||||
>
|
export default {
|
||||||
</div>
|
name: "FileChose",
|
||||||
<div v-for="(item, index) in filterFileList" :key="index">
|
data() {
|
||||||
<span class="folder" v-if="item.isFolder" @click="fileClick(item)">{{
|
return {
|
||||||
item.name
|
isWindows: false,
|
||||||
}}</span>
|
fileList: [], //路径下的文件节点
|
||||||
<el-checkbox v-model="item.checked" v-else>{{ item.name }}</el-checkbox>
|
chosedFileList: [], //选中的文件节点
|
||||||
</div>
|
pathList: [], //选择的路径
|
||||||
</div>
|
loading: false, //加载
|
||||||
|
filterText: "", //关键字过滤
|
||||||
<div>
|
};
|
||||||
<el-button type="primary" @click="submit">确定</el-button>
|
},
|
||||||
</div>
|
computed: {
|
||||||
</div>
|
filterFileList() {
|
||||||
</template>
|
let text = this.filterText.trim();
|
||||||
|
return text === "" ? this.fileList : this.fileList.filter((item) => item.name.indexOf(text) > -1);
|
||||||
<script>
|
},
|
||||||
import HttpUtil from "../utils/HttpUtil";
|
},
|
||||||
export default {
|
async mounted() {
|
||||||
name: "FileChose",
|
this.isWindows = await HttpUtil.get("/file/isWindows");
|
||||||
data() {
|
await this.breadcrumbClick(-1);
|
||||||
return {
|
},
|
||||||
isWindows: false,
|
methods: {
|
||||||
fileList: [], //路径下的文件节点
|
//点击面包蟹
|
||||||
chosedFileList: [], //选中的文件节点
|
async breadcrumbClick(index) {
|
||||||
pathList: [], //选择的路径
|
this.loading = true;
|
||||||
loading: false, //加载
|
let path = this.createPath(index);
|
||||||
filterText: "", //关键字过滤
|
let fileList = await HttpUtil.get("/file/query", {
|
||||||
};
|
path: encodeURIComponent(path),
|
||||||
},
|
showHidden: false,
|
||||||
computed: {
|
});
|
||||||
filterFileList() {
|
fileList.forEach((item) => (item.checked = false));
|
||||||
let text = this.filterText.trim();
|
this.fileList = fileList;
|
||||||
return text === ""
|
this.loading = false;
|
||||||
? this.fileList
|
return false;
|
||||||
: this.fileList.filter((item) => item.name.indexOf(text) > -1);
|
},
|
||||||
},
|
//文件列表点击
|
||||||
},
|
fileClick(item) {
|
||||||
async mounted() {
|
if (item.isFolder) {
|
||||||
this.isWindows = await HttpUtil.get("/file/isWindows");
|
this.pathList.push(item.name);
|
||||||
await this.breadcrumbClick(-1);
|
this.breadcrumbClick(this.pathList.length);
|
||||||
},
|
} else {
|
||||||
methods: {
|
item.checked = !item.checked;
|
||||||
//点击面包蟹
|
}
|
||||||
async breadcrumbClick(index) {
|
},
|
||||||
this.loading = true;
|
//全选
|
||||||
let path = this.createPath(index);
|
selectAll(status) {
|
||||||
let fileList = await HttpUtil.get("/file/query", {
|
this.filterFileList.filter((item) => !item.isFolder).forEach((item) => (item.checked = status));
|
||||||
path: encodeURIComponent(path),
|
},
|
||||||
showHidden: false,
|
//根据index构建路径
|
||||||
});
|
createPath(index) {
|
||||||
fileList.forEach((item) => (item.checked = false));
|
let path;
|
||||||
this.fileList = fileList;
|
if (index == -1) {
|
||||||
this.loading = false;
|
path = "";
|
||||||
return false;
|
this.pathList = [];
|
||||||
},
|
} else {
|
||||||
//文件列表点击
|
this.pathList = this.pathList.slice(0, index + 1);
|
||||||
fileClick(item) {
|
let str = this.pathList.join(this.isWindows ? "\\" : "/") + (this.isWindows ? "\\" : "/");
|
||||||
if (item.isFolder) {
|
path = this.isWindows ? str : "/" + str;
|
||||||
this.pathList.push(item.name);
|
}
|
||||||
this.breadcrumbClick(this.pathList.length);
|
return path;
|
||||||
} else {
|
},
|
||||||
item.checked = !item.checked;
|
//点击确定
|
||||||
}
|
submit() {
|
||||||
},
|
let chosedFiles = this.fileList.filter((item) => item.checked);
|
||||||
//全选
|
if (chosedFiles.length == 0) {
|
||||||
selectAll(status) {
|
this.$message({ message: "未选择文件", type: "warning" });
|
||||||
this.filterFileList
|
return;
|
||||||
.filter((item) => !item.isFolder)
|
}
|
||||||
.forEach((item) => (item.checked = status));
|
this.$emit("addData", chosedFiles);
|
||||||
},
|
},
|
||||||
//根据index构建路径
|
},
|
||||||
createPath(index) {
|
};
|
||||||
let path;
|
</script>
|
||||||
if (index == -1) {
|
|
||||||
path = "/";
|
<style lang="less" scoped>
|
||||||
this.pathList = [];
|
.main {
|
||||||
} else {
|
height: 65vh;
|
||||||
this.pathList = this.pathList.slice(0, index + 1);
|
}
|
||||||
let str = this.pathList.join(this.isWindows ? "\\" : "/");
|
.fileList {
|
||||||
path = this.isWindows ? str : "/" + str;
|
padding: 1em;
|
||||||
}
|
text-align: left;
|
||||||
return path;
|
height: 80%;
|
||||||
},
|
overflow: hidden auto;
|
||||||
//点击确定
|
|
||||||
submit() {
|
.folder {
|
||||||
let chosedFiles = this.fileList.filter((item) => item.checked);
|
cursor: pointer;
|
||||||
if (chosedFiles.length == 0) {
|
color: blue;
|
||||||
this.$message({ message: "未选择文件", type: "warning" });
|
display: inline-block;
|
||||||
return;
|
min-width: 3em;
|
||||||
}
|
line-height: 1.4em;
|
||||||
this.$emit("addData", chosedFiles);
|
}
|
||||||
},
|
}
|
||||||
},
|
</style>
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.main {
|
|
||||||
height: 65vh;
|
|
||||||
}
|
|
||||||
.fileList {
|
|
||||||
padding: 1em;
|
|
||||||
text-align: left;
|
|
||||||
height: 80%;
|
|
||||||
overflow: hidden auto;
|
|
||||||
|
|
||||||
.folder {
|
|
||||||
cursor: pointer;
|
|
||||||
color: blue;
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,82 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<el-menu
|
<el-menu style="width: 8em" mode="vertical" :default-active="currentIndex" @select="menuChange">
|
||||||
style="width: 8em"
|
<el-menu-item :disabled="editRule" index="insert">插入</el-menu-item>
|
||||||
mode="vertical"
|
<el-menu-item :disabled="editRule" index="delete">删除</el-menu-item>
|
||||||
:default-active="currentIndex"
|
<!-- <el-menu-item index="replace">替换</el-menu-item> -->
|
||||||
@select="menuChange"
|
<el-menu-item :disabled="editRule" index="serialization">序列化</el-menu-item>
|
||||||
>
|
</el-menu>
|
||||||
<el-menu-item :disabled="editRule" index="insert">插入</el-menu-item>
|
<div class="rule">
|
||||||
<el-menu-item :disabled="editRule" index="delete">删除</el-menu-item>
|
<insert-rule ref="rule" :editRule="editRule" v-if="currentIndex == 'insert'" />
|
||||||
<!-- <el-menu-item index="replace">替换</el-menu-item> -->
|
<delete-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'delete'" />
|
||||||
<el-menu-item :disabled="editRule" index="serialization"
|
<serialization-rule ref="rule" :editRule="editRule" v-else-if="currentIndex == 'serialization'" />
|
||||||
>序列化</el-menu-item
|
</div>
|
||||||
>
|
</div>
|
||||||
</el-menu>
|
<div style="text-align: center">
|
||||||
<div class="rule">
|
<el-button type="primary" @click="submit">确定</el-button>
|
||||||
<insert-rule
|
</div>
|
||||||
ref="rule"
|
</template>
|
||||||
:editRule="editRule"
|
|
||||||
v-if="currentIndex == 'insert'"
|
<script>
|
||||||
/>
|
import InsertRule from "./rules/InsertRule.vue";
|
||||||
<delete-rule
|
import DeleteRule from "./rules/DeleteRule.vue";
|
||||||
ref="rule"
|
import SerializationRule from "./rules/SerializationRule.vue";
|
||||||
:editRule="editRule"
|
export default {
|
||||||
v-else-if="currentIndex == 'delete'"
|
components: { InsertRule, DeleteRule, SerializationRule },
|
||||||
/>
|
props: ["editRule"],
|
||||||
<serialization-rule
|
name: "Rule",
|
||||||
ref="rule"
|
data() {
|
||||||
:editRule="editRule"
|
return {
|
||||||
v-else-if="currentIndex == 'serialization'"
|
currentIndex: "insert",
|
||||||
/>
|
options: [{ label: "插入", value: "insert" }],
|
||||||
</div>
|
};
|
||||||
</div>
|
},
|
||||||
|
created() {
|
||||||
<el-button type="primary" @click="submit">确定</el-button>
|
if (this.editRule) {
|
||||||
</template>
|
this.currentIndex = this.editRule.type;
|
||||||
|
}
|
||||||
<script>
|
},
|
||||||
import InsertRule from "./rules/InsertRule.vue";
|
methods: {
|
||||||
import DeleteRule from "./rules/DeleteRule.vue";
|
menuChange(index) {
|
||||||
import SerializationRule from "./rules/SerializationRule.vue";
|
this.currentIndex = index;
|
||||||
export default {
|
},
|
||||||
components: { InsertRule, DeleteRule, SerializationRule },
|
submit() {
|
||||||
props: ["editRule"],
|
let data = this.$refs["rule"].exportObj();
|
||||||
name: "Rule",
|
if (data != null) {
|
||||||
data() {
|
this.$emit("ruleAdd", data);
|
||||||
return {
|
}
|
||||||
currentIndex: "insert",
|
},
|
||||||
options: [{ label: "插入", value: "insert" }],
|
},
|
||||||
};
|
};
|
||||||
},
|
</script>
|
||||||
created() {
|
|
||||||
if (this.editRule) {
|
<style lang="less" scoped>
|
||||||
this.currentIndex = this.editRule.type;
|
.main {
|
||||||
}
|
display: flex;
|
||||||
},
|
height: 65vh;
|
||||||
methods: {
|
text-align: left;
|
||||||
menuChange(index) {
|
|
||||||
this.currentIndex = index;
|
.rule {
|
||||||
},
|
padding: 5px;
|
||||||
submit() {
|
flex: 1;
|
||||||
let data = this.$refs["rule"].exportObj();
|
}
|
||||||
if (data != null) {
|
}
|
||||||
this.$emit("ruleAdd", data);
|
</style>
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.main {
|
|
||||||
display: flex;
|
|
||||||
height: 65vh;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
.rule {
|
|
||||||
padding: 5px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import App from './App.vue';
|
import App from "./App.vue";
|
||||||
import router from './router';
|
import router from "./router";
|
||||||
import ElementPlus, { ElMessage } from 'element-plus';
|
import ElementPlus, { ElMessage } from "element-plus";
|
||||||
import 'element-plus/lib/theme-chalk/index.css';
|
import "element-plus/dist/index.css";
|
||||||
|
|
||||||
const vueInstance = createApp(App);
|
const vueInstance = createApp(App);
|
||||||
vueInstance.use(router).use(ElementPlus).mount('#app');
|
vueInstance.use(router).use(ElementPlus).mount("#app");
|
||||||
vueInstance.config.globalProperties.$message = ElMessage;
|
vueInstance.config.globalProperties.$message = ElMessage;
|
||||||
window.vueInstance = vueInstance;
|
window.vueInstance = vueInstance;
|
||||||
|
@ -1,65 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-loading="loading" element-loading-text="后台处理中,请稍候">
|
<div v-loading="loading" element-loading-text="后台处理中,请稍候">
|
||||||
<el-button type="primary" @click="dialogVisible = true" size="small">1.新增文件</el-button>
|
<br />
|
||||||
<el-button type="primary" @click="showResult" size="small">2.预览</el-button>
|
<el-button type="primary" @click="submit" size="default">重命名</el-button>
|
||||||
<el-button type="primary" @click="submit" size="small">3.重命名</el-button>
|
<br /><br />
|
||||||
<!-- 规则列表 -->
|
<!-- 规则列表 -->
|
||||||
<div class="ruleList">
|
<rule-block @ruleUpdate="ruleUpdate" />
|
||||||
<div class="menu">
|
|
||||||
<span>应用规则</span>
|
|
||||||
<el-button type="primary" size="mini" @click="ruleDialogShow = true">新增</el-button>
|
|
||||||
<el-button type="primary" size="mini" v-if="checkedRules.length == 1" @click="editClick">编辑</el-button>
|
|
||||||
<el-button type="warning" size="mini" @click="block">禁用/启用</el-button>
|
|
||||||
<el-button type="danger" size="mini" @click="deleteRule">删除</el-button>
|
|
||||||
<el-button type="primary" size="mini" @click="saveOrUpdate">保存模板</el-button>
|
|
||||||
<el-button type="primary" size="mini" @click="ruleTemplateShow = true">选择模板</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="ruleBlock">
|
|
||||||
<el-checkbox v-model="item.checked" v-for="(item, index) in ruleList" :key="index">
|
|
||||||
<s v-if="item.blocked">{{ item.message }}</s>
|
|
||||||
<span v-else>{{ item.message }}</span>
|
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 文件预览列表 -->
|
<!-- 文件预览列表 -->
|
||||||
<div class="fileList">
|
<div class="fileList">
|
||||||
<div>
|
<div>
|
||||||
文件列表
|
文件列表
|
||||||
|
<el-button type="primary" @click="dialogVisible = true" size="small">新增</el-button>
|
||||||
<el-button type="primary" size="mini" @click="selectAllFiles">反选</el-button>
|
<el-button type="primary" size="mini" @click="selectAllFiles">反选</el-button>
|
||||||
<el-button type="danger" size="mini" @click="deleteCheckedFiles">删除</el-button>
|
<el-button type="danger" size="mini" @click="deleteCheckedFiles">删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="fileBlock">
|
<div class="fileBlock">
|
||||||
<!-- 左侧原始文件名 -->
|
<div class="oneLine" v-for="(item, index) in fileList" :key="index">
|
||||||
<el-checkbox style="display: block" v-for="(item, index) in fileList" :key="index" v-model="item.checked">
|
<!-- 左侧原始文件名 -->
|
||||||
<div class="oneFileName">
|
<el-checkbox v-model="item.checked" class="left">
|
||||||
{{ item.name }}
|
<div class="oneFileName">
|
||||||
<ArrowDownBold
|
{{ item.name }}
|
||||||
style="width: 20px; padding-left: 10px"
|
<ArrowDownBold
|
||||||
v-if="index < fileList.length - 1"
|
style="width: 20px; padding-left: 10px"
|
||||||
@click.stop.prevent="moveIndex(index + 1, index)"
|
v-if="index < fileList.length - 1"
|
||||||
/>
|
@click.stop.prevent="moveIndex(index + 1, index)"
|
||||||
<ArrowUpBold style="width: 20px; padding-left: 10px" v-if="index > 0" @click.stop.prevent="moveIndex(index - 1, index)" />
|
/>
|
||||||
|
<ArrowUpBold style="width: 20px; padding-left: 10px" v-if="index > 0" @click.stop.prevent="moveIndex(index - 1, index)" />
|
||||||
|
</div>
|
||||||
|
</el-checkbox>
|
||||||
|
<!-- 修改后的文件名 -->
|
||||||
|
<div class="right">
|
||||||
|
{{ changedFileList.length > index ? changedFileList[index].name : "" }}
|
||||||
</div>
|
</div>
|
||||||
</el-checkbox>
|
|
||||||
</div>
|
|
||||||
<div class="fileBlock">
|
|
||||||
<!-- 右侧修改后的文件名-->
|
|
||||||
<div v-for="(item, index) in changedFileList" :key="index">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 新增文件弹窗 -->
|
<!-- 新增文件弹窗 -->
|
||||||
<el-dialog title="新增规则" v-model="ruleDialogShow" width="70%">
|
|
||||||
<rule :editRule="editRule" v-if="ruleDialogShow" @ruleAdd="ruleAdd" />
|
|
||||||
</el-dialog>
|
|
||||||
<el-dialog title="新增文件" v-model="dialogVisible" width="70%">
|
<el-dialog title="新增文件" v-model="dialogVisible" width="70%">
|
||||||
<file-chose @addData="addData" />
|
<file-chose @addData="addData" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog title="选择规则模板" v-model="ruleTemplateShow" width="70%">
|
|
||||||
<application-rule-list />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -68,80 +47,41 @@
|
|||||||
import { ArrowDownBold, ArrowUpBold } from "@element-plus/icons";
|
import { ArrowDownBold, ArrowUpBold } from "@element-plus/icons";
|
||||||
import HttpUtil from "../../utils/HttpUtil";
|
import HttpUtil from "../../utils/HttpUtil";
|
||||||
import FileChose from "@/components/FileChose";
|
import FileChose from "@/components/FileChose";
|
||||||
import ApplicationRuleList from "./components/ApplicationRuleList";
|
import RuleBlock from "./components/RuleBlock.vue";
|
||||||
import Rule from "@/components/Rule";
|
|
||||||
export default {
|
export default {
|
||||||
name: "Home",
|
name: "Home",
|
||||||
components: {
|
components: {
|
||||||
FileChose,
|
FileChose,
|
||||||
Rule,
|
|
||||||
ArrowDownBold,
|
ArrowDownBold,
|
||||||
ArrowUpBold,
|
ArrowUpBold,
|
||||||
ApplicationRuleList,
|
RuleBlock,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false, //遮罩
|
loading: false, //遮罩
|
||||||
dialogVisible: false, //新增文件弹窗
|
dialogVisible: false, //新增文件弹窗
|
||||||
ruleDialogShow: false, //规则弹窗
|
ruleList: [], //当前生效的规则
|
||||||
ruleTemplateShow: false, //选择规则模板弹窗是否展示
|
|
||||||
fileList: [],
|
fileList: [],
|
||||||
changedFileList: [],
|
changedFileList: [],
|
||||||
ruleList: [],
|
|
||||||
editRule: null, //当前编辑的规则
|
|
||||||
needPreview: false, //需要点击预览
|
needPreview: false, //需要点击预览
|
||||||
applicationRule: null, //当前应用的应用规则模板
|
applicationRule: null, //当前应用的应用规则模板
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
//选中的规则
|
|
||||||
checkedRules() {
|
|
||||||
return this.ruleList.filter((item) => item.checked);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
//新增文件
|
//新增文件
|
||||||
async addData(data) {
|
async addData(data) {
|
||||||
data.forEach((item) => (item.checked = false));
|
data.forEach((item) => (item.checked = false));
|
||||||
this.fileList.push(...data);
|
this.fileList.push(...data);
|
||||||
this.dialogVisible = false;
|
this.dialogVisible = false;
|
||||||
console.log("asdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
|
||||||
this.needPreview = true;
|
this.needPreview = true;
|
||||||
await this.showResult();
|
await this.showResult();
|
||||||
},
|
},
|
||||||
//新增规则
|
async ruleUpdate(rules) {
|
||||||
async ruleAdd(data) {
|
this.ruleList = rules;
|
||||||
data.checked = false;
|
|
||||||
data.blocked = false;
|
|
||||||
if (this.editRule != null) {
|
|
||||||
let index = this.ruleList.indexOf(this.editRule);
|
|
||||||
this.ruleList.splice(index, 1, data);
|
|
||||||
this.editRule = null;
|
|
||||||
} else {
|
|
||||||
this.ruleList.push(data);
|
|
||||||
}
|
|
||||||
this.ruleDialogShow = false;
|
|
||||||
|
|
||||||
this.needPreview = true;
|
this.needPreview = true;
|
||||||
await this.showResult();
|
await this.showResult();
|
||||||
},
|
},
|
||||||
//禁用/启用
|
|
||||||
async block() {
|
|
||||||
this.ruleList.filter((item) => item.checked).forEach((item) => (item.blocked = !item.blocked));
|
|
||||||
this.needPreview = true;
|
|
||||||
await this.showResult();
|
|
||||||
},
|
|
||||||
//删除规则
|
|
||||||
async deleteRule() {
|
|
||||||
this.ruleList = this.ruleList.filter((item) => !item.checked);
|
|
||||||
this.needPreview = true;
|
|
||||||
await this.showResult();
|
|
||||||
},
|
|
||||||
//编辑规则
|
|
||||||
editClick() {
|
|
||||||
this.editRule = this.checkedRules[0];
|
|
||||||
this.ruleDialogShow = true;
|
|
||||||
},
|
|
||||||
//预览结果
|
//预览结果
|
||||||
async showResult() {
|
async showResult() {
|
||||||
if (!this.checkRuleAndFile()) {
|
if (!this.checkRuleAndFile()) {
|
||||||
@ -153,9 +93,11 @@ export default {
|
|||||||
ruleList: this.ruleList.filter((item) => !item.blocked),
|
ruleList: this.ruleList.filter((item) => !item.blocked),
|
||||||
};
|
};
|
||||||
this.changedFileList = await HttpUtil.post("/renamer/preview", null, body);
|
this.changedFileList = await HttpUtil.post("/renamer/preview", null, body);
|
||||||
|
this.fileList = [...this.fileList];
|
||||||
this.needPreview = false;
|
this.needPreview = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
|
//提交
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!this.checkRuleAndFile()) {
|
if (!this.checkRuleAndFile()) {
|
||||||
return;
|
return;
|
||||||
@ -212,30 +154,24 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.ruleList {
|
|
||||||
border: 1px solid black;
|
|
||||||
padding: 5px;
|
|
||||||
.menu {
|
|
||||||
display: flex;
|
|
||||||
justify-content: left;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.ruleBlock {
|
|
||||||
text-align: left;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: baseline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileList {
|
.fileList {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
.fileBlock {
|
.fileBlock {
|
||||||
text-align: left;
|
margin-top: 20px;
|
||||||
display: inline-block;
|
.oneLine {
|
||||||
width: 50%;
|
display: flex;
|
||||||
|
border-top: 1px solid rgb(228, 224, 224);
|
||||||
|
.left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.oneFileName {
|
.oneFileName {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,27 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-table :data="applicationRuleList" style="width: 100%">
|
<el-table :data="applicationRuleList" style="width: 100%">
|
||||||
<el-table-column prop="createdDate" label="创建时间" width="180" />
|
<el-table-column prop="createdDate" label="创建时间" width="180" />
|
||||||
<el-table-column prop="name" label="名称" width="180" />
|
<el-table-column prop="name" label="名称" width="180" />
|
||||||
<el-table-column prop="comment" label="备注" />
|
<el-table-column prop="comment" label="备注" />
|
||||||
</el-table>
|
<el-table-column label="操作" width="120">
|
||||||
</div>
|
<template #default="scope">
|
||||||
</template>
|
<el-button type="text" size="small" @click="ruleTemplateAction('chose', scope.row)">选择</el-button>
|
||||||
|
<el-button type="text" size="small" @click="ruleTemplateAction('delete', scope.row)">删除</el-button>
|
||||||
<script>
|
</template>
|
||||||
import HttpUtil from "../../../utils/HttpUtil";
|
</el-table-column>
|
||||||
export default {
|
</el-table>
|
||||||
name: "ApplicationRuleList",
|
</div>
|
||||||
data() {
|
</template>
|
||||||
return {
|
|
||||||
applicationRuleList: [],
|
<script>
|
||||||
};
|
import HttpUtil from "../../../utils/HttpUtil";
|
||||||
},
|
import dayjs from "dayjs";
|
||||||
async mounted() {
|
export default {
|
||||||
this.applicationRuleList = await HttpUtil.get("/applicationRule");
|
name: "ApplicationRuleList",
|
||||||
},
|
data() {
|
||||||
};
|
return {
|
||||||
</script>
|
applicationRuleList: [],
|
||||||
|
};
|
||||||
<style>
|
},
|
||||||
</style>
|
async created() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async init() {
|
||||||
|
this.applicationRuleList = await HttpUtil.get("/applicationRule");
|
||||||
|
this.applicationRuleList.forEach((item) => (item.createdDate = dayjs(item.createdDate).format("YYYY-MM-DD")));
|
||||||
|
},
|
||||||
|
//操作
|
||||||
|
async ruleTemplateAction(action, rowData) {
|
||||||
|
if (action === "chose") {
|
||||||
|
await this.$emit("update:modelValue", rowData);
|
||||||
|
await this.$emit("close");
|
||||||
|
} else {
|
||||||
|
await HttpUtil.delete("/applicationRule/" + rowData.id);
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
|
171
openRenamerFront/src/views/home/components/RuleBlock.vue
Normal file
171
openRenamerFront/src/views/home/components/RuleBlock.vue
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<div class="menu">
|
||||||
|
<span>应用规则</span>
|
||||||
|
<el-button type="primary" size="mini" @click="addRuleDialogShow = true">新增</el-button>
|
||||||
|
<el-button type="primary" size="mini" v-if="checkedRules.length == 1" @click="editClick">编辑</el-button>
|
||||||
|
<el-button type="warning" size="mini" @click="block">禁用/启用</el-button>
|
||||||
|
<el-button type="danger" size="mini" @click="deleteRule">删除</el-button>
|
||||||
|
<el-button type="primary" size="mini" v-if="chosedTemplate" @click="templateSubmit">保存规则</el-button>
|
||||||
|
<el-button type="primary" size="mini" v-if="chosedTemplate == null && ruleList.length > 0" @click="saveTemplateDilalogShow = true"
|
||||||
|
>存为模板</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="ruleBlock">
|
||||||
|
<el-checkbox v-model="item.checked" v-for="(item, index) in ruleList" :key="index">
|
||||||
|
<s v-if="item.blocked">{{ item.message }}</s>
|
||||||
|
<span v-else>{{ item.message }}</span>
|
||||||
|
</el-checkbox>
|
||||||
|
<div v-if="ruleList.length == 0 && chosedTemplate == null" class="choseTemplate">
|
||||||
|
<el-button type="primary" size="mini" @click="ruleTemplateShow = true">选择模板</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 弹窗 -->
|
||||||
|
<el-dialog title="新增规则" v-model="addRuleDialogShow" width="70%">
|
||||||
|
<rule :editRule="editRule" @ruleAdd="ruleAdd" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="选择规则模板" v-model="ruleTemplateShow" width="70%">
|
||||||
|
<application-rule-list v-if="ruleTemplateShow" v-model="chosedTemplate" @close="ruleTemplateShow = false" />
|
||||||
|
</el-dialog>
|
||||||
|
<el-dialog title="保存模板" v-model="saveTemplateDilalogShow" width="70%">
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="templateForm.name"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="描述">
|
||||||
|
<el-input v-model="templateForm.comment"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="saveTemplateDilalogShow = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="templateSubmit">提交</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Rule from "../../../components/Rule";
|
||||||
|
import ApplicationRuleList from "./ApplicationRuleList";
|
||||||
|
import HttpUtil from "../../../utils/HttpUtil";
|
||||||
|
export default {
|
||||||
|
name: "RuleBlock",
|
||||||
|
components: {
|
||||||
|
Rule,
|
||||||
|
ApplicationRuleList,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
addRuleDialogShow: false, //是否显示新增规则弹窗
|
||||||
|
ruleTemplateShow: false, //是否显示选择规则模板弹窗
|
||||||
|
saveTemplateDilalogShow: false, //是否显示保存模板弹窗
|
||||||
|
ruleList: [],
|
||||||
|
editRule: null, //当前编辑的规则
|
||||||
|
chosedTemplate: null,
|
||||||
|
templateForm: {
|
||||||
|
name: "",
|
||||||
|
comment: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
//选中的规则
|
||||||
|
checkedRules() {
|
||||||
|
return this.ruleList.filter((item) => item.checked);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
chosedTemplate(newVal, oldVal) {
|
||||||
|
this.ruleList = JSON.parse(newVal.content);
|
||||||
|
this.ruleUpdate();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
//规则更新
|
||||||
|
ruleUpdate() {
|
||||||
|
this.$emit(
|
||||||
|
"ruleUpdate",
|
||||||
|
this.ruleList.filter((item) => !item.blocked)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
//保存还是更新模板
|
||||||
|
saveOrUpdateTemplate() {
|
||||||
|
if (this.chosedTemplate != null) {
|
||||||
|
this.templateSubmit();
|
||||||
|
} else {
|
||||||
|
this.saveTemplateDilalogShow = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//模板内容提交
|
||||||
|
async templateSubmit() {
|
||||||
|
let body;
|
||||||
|
if (this.chosedTemplate) {
|
||||||
|
this.chosedTemplate.content = JSON.stringify(this.ruleList);
|
||||||
|
body = this.chosedTemplate;
|
||||||
|
} else {
|
||||||
|
body = {
|
||||||
|
name: this.templateForm.name,
|
||||||
|
comment: this.templateForm.comment,
|
||||||
|
content: JSON.stringify(this.ruleList),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.chosedTemplate = await HttpUtil.post("/applicationRule", null, body);
|
||||||
|
this.saveTemplateDilalogShow = false;
|
||||||
|
this.$message.success("操作成功");
|
||||||
|
},
|
||||||
|
//新增规则
|
||||||
|
async ruleAdd(data) {
|
||||||
|
data.checked = false;
|
||||||
|
data.blocked = false;
|
||||||
|
if (this.editRule != null) {
|
||||||
|
let index = this.ruleList.indexOf(this.editRule);
|
||||||
|
this.ruleList.splice(index, 1, data);
|
||||||
|
this.editRule = null;
|
||||||
|
} else {
|
||||||
|
this.ruleList.push(data);
|
||||||
|
}
|
||||||
|
this.ruleUpdate();
|
||||||
|
this.addRuleDialogShow = false;
|
||||||
|
},
|
||||||
|
//禁用/启用
|
||||||
|
async block() {
|
||||||
|
this.ruleList.filter((item) => item.checked).forEach((item) => (item.blocked = !item.blocked));
|
||||||
|
this.ruleUpdate();
|
||||||
|
},
|
||||||
|
//删除规则
|
||||||
|
async deleteRule() {
|
||||||
|
this.ruleList = this.ruleList.filter((item) => !item.checked);
|
||||||
|
this.ruleUpdate();
|
||||||
|
},
|
||||||
|
//编辑规则
|
||||||
|
editClick() {
|
||||||
|
this.editRule = this.checkedRules[0];
|
||||||
|
this.ruleDialogShow = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.main {
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 5px;
|
||||||
|
.menu {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ruleBlock {
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.choseTemplate {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2em;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user