页面完善

This commit is contained in:
24kycj
2024-11-07 17:06:51 +08:00
parent d375616372
commit ca1a2e11b5
58 changed files with 5467 additions and 837 deletions
-128
View File
@@ -1,128 +0,0 @@
<template>
<div id="wrapper">
<img id="logo" src="~@/assets/logo.png" alt="electron-vue">
<main>
<div class="left-side">
<span class="title">
Welcome to your new project!
</span>
<system-information></system-information>
</div>
<div class="right-side">
<div class="doc">
<div class="title">Getting Started</div>
<p>
electron-vue comes packed with detailed documentation that covers everything from
internal configurations, using the project structure, building your application,
and so much more.
</p>
<button @click="open('https://simulatedgreg.gitbooks.io/electron-vue/content/')">Read the Docs</button><br><br>
</div>
<div class="doc">
<div class="title alt">Other Documentation</div>
<button class="alt" @click="open('https://electron.atom.io/docs/')">Electron</button>
<button class="alt" @click="open('https://vuejs.org/v2/guide/')">Vue.js</button>
</div>
</div>
</main>
</div>
</template>
<script>
import SystemInformation from './LandingPage/SystemInformation'
export default {
name: 'landing-page',
components: { SystemInformation },
methods: {
open (link) {
this.$electron.shell.openExternal(link)
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body { font-family: 'Source Sans Pro', sans-serif; }
#wrapper {
background:
radial-gradient(
ellipse at top left,
rgba(255, 255, 255, 1) 40%,
rgba(229, 229, 229, .9) 100%
);
height: 100vh;
padding: 60px 80px;
width: 100vw;
}
#logo {
height: auto;
margin-bottom: 20px;
width: 420px;
}
main {
display: flex;
justify-content: space-between;
}
main > div { flex-basis: 50%; }
.left-side {
display: flex;
flex-direction: column;
}
.welcome {
color: #555;
font-size: 23px;
margin-bottom: 10px;
}
.title {
color: #2c3e50;
font-size: 20px;
font-weight: bold;
margin-bottom: 6px;
}
.title.alt {
font-size: 18px;
margin-bottom: 10px;
}
.doc p {
color: black;
margin-bottom: 10px;
}
.doc button {
font-size: .8em;
cursor: pointer;
outline: none;
padding: 0.75em 2em;
border-radius: 2em;
display: inline-block;
color: #fff;
background-color: #4fc08d;
transition: all 0.15s ease;
box-sizing: border-box;
border: 1px solid #4fc08d;
}
.doc button.alt {
color: #42b983;
background-color: transparent;
}
</style>
@@ -1,73 +0,0 @@
<template>
<div>
<div class="title">Information</div>
<div class="items">
<div class="item">
<div class="name">Path:</div>
<div class="value">{{ path }}</div>
</div>
<div class="item">
<div class="name">Route Name:</div>
<div class="value">{{ name }}</div>
</div>
<div class="item">
<div class="name">Vue.js:</div>
<div class="value">{{ vue }}</div>
</div>
<div class="item">
<div class="name">Electron:</div>
<div class="value">{{ electron }}</div>
</div>
<div class="item">
<div class="name">Node:</div>
<div class="value">{{ node }}</div>
</div>
<div class="item">
<div class="name">Platform:</div>
<div class="value">{{ platform }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
electron: process.versions.electron,
name: this.$route.name,
node: process.versions.node,
path: this.$route.path,
platform: require('os').platform(),
vue: require('vue/package.json').version
}
}
}
</script>
<style scoped>
.title {
color: #888;
font-size: 18px;
font-weight: initial;
letter-spacing: .25px;
margin-top: 10px;
}
.items { margin-top: 8px; }
.item {
display: flex;
margin-bottom: 6px;
}
.item .name {
color: #6a6a6a;
margin-right: 6px;
}
.item .value {
color: #35495e;
font-weight: bold;
}
</style>
+131
View File
@@ -0,0 +1,131 @@
<template>
<div>
<div class="flex_box flex_row_between top_box">
<div class="flex_box top_left">
<div @click="goHome" class="flex_box flex_row_center top_btn1">
<img src="@/assets/images/Home_icon.png" />
<div>回到主界面</div>
</div>
<div class="top_left_line"></div>
<div class="flex_box flex_col_top tab_box">
<div @click="goPath(item)" v-for="item in tabs" :key="item.id" class="flex_box flex_row_center flex_col tab_item" :class="{'tab_item1': active===item.id}">
<div class="tab_name">{{item.name}}</div>
<div class="tab_line"></div>
</div>
</div>
</div>
<user-info></user-info>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import UserInfo from '../userInfo/userInfo.vue';
export default {
name: 'TabBar',
components: { UserInfo },
props: {
active: {
type: Number,
default: 1
}
},
data () {
return {
tabs: [
{
id: 1,
name: '系统管理',
path: '/manage'
},
{
id: 2,
name: '用户管理',
path: '/user'
},
{
id: 3,
name: '系统配置',
path: '/set'
}
]
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goHome() {
this.$router.go(-1)
},
goPath(e) {
console.log(e)
if (e.id === this.active) {
return
}
this.$router.replace(e.path)
}
}
}
</script>
<style lang="scss" scoped>
.top_box {
height: 86px;
padding: 0 32px;
background-color: #ffffff;
border-bottom: 8px solid #F8F8F8;
.top_left {
.top_btn1 {
cursor: pointer;
padding: 0 16px;
height: 30px;
background: #00C325;
border-radius: 4px;
margin-right: 16px;
font-weight: 500;
font-size: 15px;
color: #FFFFFF;
img {
width: 18px;
height: 18px;
margin-right: 8px;
}
}
.top_left_line {
width: 2px;
height: 30px;
background: #E0E0E0;
margin: 0 16px;
}
.tab_box {
.tab_item {
padding: 20px 16px;
font-weight: 500;
font-size: 15px;
color: #000000;
line-height: 24px;
cursor: pointer;
.tab_name {
padding-bottom: 4px;
}
.tab_line {
display: none;
width: 24px;
height: 4px;
background: #00C325;
border-radius: 4px;
}
}
.tab_item1 {
color: #00C325;
font-weight: bold;
.tab_line {
display: block;
}
}
}
}
}
</style>
@@ -0,0 +1,49 @@
<template>
<div>
<div class="flex_box top_right">
<div class="top_right_name">你好{{name}}</div>
<div @click="goOut" class="top_right_btn">登出</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'UserInfo',
data () {
return {
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goOut() {
this.$store.dispatch('FedLogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
}
}
}
</script>
<style lang="scss" scoped>
.top_right {
.top_right_name {
font-weight: 500;
font-size: 15px;
color: #000000;
padding: 0 16px;
}
.top_right_btn {
font-weight: 500;
font-size: 15px;
color: #00C325;
line-height: 50px;
padding-left: 16px;
cursor: pointer;
}
}
</style>
@@ -0,0 +1,96 @@
// 第一步,导入必要的模块
const fs = require('fs');
const archiver = require('archiver');
archiver.registerFormat('zip-encrypted', require("archiver-zip-encrypted"));
function zip(filesList,zippath,callback,isPassword,pass)
{
// 第二步,创建可写流来写入数据
const output = fs.createWriteStream(zippath);// 将压缩包保存到当前项目的目录下,并且压缩包名为test.zip
if(isPassword)
{
const archive = archiver.create('zip-encrypted',
{
zlib: {
level: 8,//压缩等级
},
encryptionMethod: 'aes256',//加密方法
password:pass ,//解压密码
});
// 第三步,建立管道连接
archive.pipe(output);
// 第四步,压缩多个文件到压缩包中
for (let i in filesList) {
console.log("压缩:")
if(filesList[i].folder)
{
archive.directory(i, filesList[i].name);
console.log(filesList[i].name)
}else{
archive.append(i, {name: filesList[i].name});
console.log(filesList[i].name)
}
}
//监听所有archive数据都写完
output.on('close', function() {
setTimeout(() => {
callback(true);
}, 1000);
console.log('压缩完成', archive.pointer() / 1024 / 1024 + 'M');
});
archive.on('error', function(err) {
callback(false);
console.log("压缩失败!");
throw err;
});
// 第五步,完成压缩
archive.finalize();
}else{
const archive = archiver('zip', {zlib: {level: 9}});// 设置压缩等级
// 第三步,建立管道连接
archive.pipe(output);
// 第四步,压缩多个文件到压缩包中
for (let i in filesList) {
console.log("压缩:")
if(filesList[i].folder)
{
archive.directory(i, filesList[i].name);
console.log(filesList[i].name)
}else{
var stream = fs.createReadStream(i);
archive.append(stream, {name: filesList[i].name});
console.log(filesList[i].name)
}
}
//监听所有archive数据都写完
output.on('close', function() {
setTimeout(() => {
callback(true);
}, 1000);
console.log('压缩完成', archive.pointer() / 1024 / 1024 + 'M');
});
archive.on('error', function(err) {
callback(false);
console.log("压缩失败!");
throw err;
});
// 第五步,完成压缩
archive.finalize();
}
}
export {
zip
}
@@ -0,0 +1,139 @@
<template>
<div>
<div class="progress">
<div class="zip_text" v-if="isFalse" style="color: rgb(245, 31, 31)">
{{ $t("dialog.compressFailed") }}
</div>
<div class="zip_text" v-else-if="isOver" style="color: rgb(103, 194, 58)">
{{ $t("dialog.compressOver") }}
</div>
<div class="zip_text" v-else style="color: rgb(77, 77, 77)">
{{ $t("dialog.compressing") }}
</div>
<el-progress :percentage="percentage" :status="status"></el-progress>
</div>
<!-- status="success" -->
<!-- <el-progress :percentage="100" status="warning"></el-progress>
<el-progress :percentage="50" status="exception"></el-progress> -->
<div class="last">
<div class="text" v-if="isOver || isFalse">
{{ $t("dialog.closeTips") }}
</div>
<div class="text" v-else>{{ $t("dialog.waitTips") }}</div>
<div class="button">
<el-button
type="success"
@click="changevisiable(false)"
:disabled="!isOver"
>{{ $t("dialog.over") }}</el-button
>
</div>
</div>
</div>
</template>
<script>
let interval;
export default {
name: "archiverdialog",
props: {
isOver: {
type: Boolean,
},
isFalse: {
type: Boolean,
},
changevisiable: {
type: Function,
},
},
data() {
return {
percentage: 0,
status: "",
};
},
mounted() {
interval = setInterval(() => {
this.percentage++;
}, 250);
},
watch: {
percentage: {
handler(val) {
if (this.percentage > 98) {
clearInterval(interval);
}
},
immediate: true,
deep: true,
},
isOver: {
handler(val) {
if (this.isOver) {
clearInterval(interval);
this.percentage = 100;
this.status = "success";
}
},
immediate: true,
deep: true,
},
isFalse: {
handler(val) {
if (this.isFalse) {
clearInterval(interval);
this.status = "exception";
}
},
immediate: true,
deep: true,
},
},
};
</script>
<style lang="less" scoped>
.progress {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.zip_text {
font-size: 17px;
font-weight: bold;
}
.el-progress {
width: 80%;
}
}
.last {
height: 30px;
width: 100%;
margin-top: 35px;
.button {
float: right;
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
width: 50%;
.el-button {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 50px;
}
}
.text {
line-height: 30px;
float: left;
width: 50%;
font-size: 12px;
color: gray;
}
}
</style>
@@ -0,0 +1,58 @@
const fs = require("fs");
const path = require("path");
// 使用promisify方法来promise化指定方法
const { promisify } = require("util");
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
// 异步
export async function calcSize(dirPath, callback) {
let fileSize = 0;
let error = null;
async function calc(dirPath) {
try {
const statObj = await stat(dirPath);
if (statObj.isDirectory()) {
const files = await readdir(dirPath);
let dirs = files.map((item) => {
return path.join(dirPath, item);
});
let index = 0;
async function next() {
if (index < dirs.length) {
let current = dirs[index++];
await calc(current);
await next();
}
}
return await next();
} else {
fileSize += statObj.size;
}
} catch (err) {
error = err;
}
}
await calc(dirPath);
callback(error, fileSize, dirPath);
}
export function getFileName(name) {
return name.substring(name.lastIndexOf("\\") + 1);
}
export function getExtension(name) {
return name.substring(name.lastIndexOf(".") + 1);
}
export function bytesToSize(bytes) {
if (bytes === 0) return "0 B";
var k = 1024,
sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i];
}
export function isFolder(path) {
let _stat = fs.lstatSync(path);
return _stat.isDirectory();
}
@@ -0,0 +1,33 @@
const fs = require("fs");
const path = require("path");
function copy(src, srcafter, isFolder, callback, fileList) {
if (isFolder) {
//复制文件夹
fs.cp(src, srcafter, { recursive: true }, (err) => {
if (err) {
console.error(err);
callback(fileList,false);
}else{
console.log("copyover");
console.log("err");
callback(fileList,true);
}
});
} else {
// 复制文件
fs.cp(src, srcafter, (err) => {
if (err) {
console.error(err);
console.log("err");
callback(fileList,false);
}else{
console.log("copyover");
callback(fileList,true);
}
});
}
}
export { copy };
//'./src/renderer/components/files/test'
//'./src/renderer/components/files/testafter/tset'
@@ -0,0 +1,369 @@
<template>
<div class="files">
<div class="btn-group" v-show="false">
<div class="btn" @click="addFolder()" style="color: #67c23a">
<i class="el-icon-folder" />
{{ $t("file.addFolder") }}
</div>
<div class="btn" @click="addFile()" style="color: #67c23a">
<i class="el-icon-files" />
{{ $t("file.addFile") }}
</div>
</div>
<fileEmpty v-if="allNumber == 0" />
<fileList v-else :list="filesList" :del="delFile" :key="key" />
<div v-if="progressVisible">
<el-dialog
:close-on-click-modal="false"
width="500px"
:visible.sync="progressVisible"
:append-to-body="true"
class="prodialog"
>
<progressdialog
:list="filesList"
:overNumber="overNumber"
:allNumber="allNumber"
:changevisiable="changeProgressvisible"
:changestate="changestate"
:isSucess="isSucess"
ref="progressdialog"
></progressdialog>
</el-dialog>
</div>
<div v-if="archiverVisible">
<el-dialog
:close-on-click-modal="false"
width="500px"
:visible.sync="archiverVisible"
:append-to-body="true"
class="archiverDialog"
:show-close="false"
>
<archiverdialog
:isOver="archiverIsover"
:isFalse="archiverIsfalse"
:changevisiable="changeArchivervisible"
></archiverdialog>
</el-dialog>
</div>
<slot></slot>
</div>
</template>
<script>
const { dialog } = require("@electron/remote");
const fs = require("fs");
import fileEmpty from "./fileEmpty";
import fileList from "./fileList";
import progressdialog from "./progressdialog";
import archiverdialog from "./archiverdialog";
import { calcSize, getFileName, isFolder } from "./calc";
import { copy } from "./copy";
import { zip } from "./archiver";
export default {
name: "Files",
props: {
onSizechange: {
type: Function,
},
complete: {
type: Function,
},
saveWorkList: {
type: Object | undefined,
},
},
components: {
fileEmpty,
fileList,
progressdialog,
archiverdialog,
},
data() {
return {
filesList: {},
key: 0,
allSize: 0,
progressVisible: false,
archiverVisible: false,
overNumber: 0,
allNumber: 0,
nowstate: true,
archiverIsover: false,
archiverIsfalse: false,
zip_path: "",
isCopy: false,
isSucess: true,
copyPath: "",
};
},
mounted() {
// this.$set(this, filesList, this.saveWorkList)
const _this = this;
document.addEventListener("drop", (e) => {
e.preventDefault();
// e.stopPropagation();
for (const f of e.dataTransfer.files) {
const isFolder = _this.dropFolderCheck(f);
_this.insertList({
name: getFileName(f.path),
path: f.path,
size: isFolder ? -1 : f.size,
folder: isFolder,
});
if (isFolder) {
calcSize(f.path, _this.folderCalcCallback);
}
}
});
document.addEventListener("dragover", (e) => {
e.preventDefault();
// e.stopPropagation();
});
},
methods: {
insertList(f) {
if (!this.filesList[f.path]) {
this.allNumber++;
this.$set(this.filesList, f.path, f);
if (f.size != -1) {
// 是文件夹
this.sizeChange(f.size);
}
return true;
} else {
//有了,不用加入
return false;
}
},
addFile() {
const _this = this;
dialog
.showOpenDialog({
properties: ["multiSelections"],
})
.then(async (res) => {
for (const item of res.filePaths) {
await fs.stat(item, function (err, res) {
if (err) {
return false;
}
_this.insertList({
name: getFileName(item),
path: item,
size: res.size,
folder: false,
});
});
}
});
},
addFolder() {
const _this = this;
dialog
.showOpenDialog({
properties: ["openDirectory", "multiSelections"],
})
.then((res) => {
for (const item of res.filePaths) {
const result = _this.insertList({
name: getFileName(item),
path: item,
size: -1,
folder: true,
});
if (result) {
calcSize(item, _this.folderCalcCallback);
}
}
});
},
folderCalcCallback(err, res, path) {
if (this.filesList[path]) {
this.filesList[path].size = res;
this.$set(this.filesList, path, this.filesList[path]);
this.sizeChange(this.filesList[path].size);
}
},
delFile(path) {
this.sizeChange(-this.filesList[path].size);
delete this.filesList[path];
this.key++;
this.allNumber--;
},
dropFolderCheck(f) {
//T是文件夹 F不是文件夹
//拖放无法从参数判断是否为文件夹,需要额外处理
if (f.size != 0 && f.size != 4096) {
//返回大小不是0,则不是文件夹
return false;
}
if (f.type != "") {
//如果type不是空,则不是文件夹
return false;
}
return isFolder(f.path);
},
sizeChange(size) {
this.allSize = this.allSize + size;
if (this.allSize <= 0) {
this.allSize = 0;
}
this.onSizechange(this.allSize);
},
fileBack(fileList, Sucess) {
if (Sucess) {
if (this.nowstate) {
this.isSucess = true;
fileList.state = true;
this.overNumber++;
if (this.overNumber == this.allNumber) {
setTimeout(() => {
this.changeProgressvisible(false);
this.complete();
}, 500);
}
} else {
console.log("终止!!!!");
}
} else {
this.isSucess = false;
if (this.nowstate) {
fileList.state = false;
} else {
console.log("终止!!!!");
}
}
},
archiverBack(state) {
if (state) {
this.archiverIsover = true;
this.complete();
} else {
this.archiverIsfalse = true;
}
setTimeout(() => {
this.changeArchivervisible(false);
}, 500);
},
resume(file_form) {
//if (file_form == 0) {
if (1) {
if (this.isCopy) {
this.overNumber = 0;
this.changeProgressvisible(true);
for (let i in this.filesList) {
// const path = "D:\\copytest\\1\\" + this.filesList[i].name;
const path = this.copyPath + this.filesList[i].name;
console.log(this.copyPath);
console.log(path);
copy(
i,
path,
this.filesList[i].folder,
this.fileBack,
this.filesList[i]
);
}
} else {
//2023-04-24修改为所有都只上传文件路径,不需要压缩
//文件和文件夹
this.overNumber = 0;
for (let i in this.filesList) {
// const path = "D:\\copytest\\1\\" + this.filesList[i].name;
// copy(i, path, this.filesList[i].folder, this.back, this.filesList[i]);
this.fileBack(this.filesList[i], true);
}
}
} else if (file_form == 1) {
//电子光盘
} else if (file_form == 2) {
//zip
this.archiverIsover = false;
this.archiverIsfalse = false;
this.zip_path = "D:/archivertest/1.zip";
zip(this.filesList, this.zip_path, this.archiverBack, false);
} else if (file_form == 3) {
//加密zip
this.archiverIsover = false;
this.archiverIsfalse = false;
this.zip_path = "D:/archivertest/2.zip";
let password = "123456";
zip(this.filesList, this.zip_path, this.archiverBack, true, password);
} else if (file_form == 4) {
//u盘
}
},
changeProgressvisible(visiable) {
this.progressVisible = visiable;
},
changeArchivervisible(visiable) {
this.archiverVisible = visiable;
},
changestate(state) {
this.nowstate = state;
},
getLists() {
return this.filesList;
},
haveFolderIsCalc() {},
},
computed: {},
watch: {},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="less" scoped>
.prodialog {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.archiverDialog {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
/deep/.el-dialog__header {
padding: 10px;
}
}
.files {
width: 100%;
background-color: #f5f5f5;
margin: 10px auto 0;
font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
position: relative;
height: 375px;
}
.btn-group {
height: 30px;
background-color: #e7e7ec;
.btn {
color: #53809f;
display: inline-block;
height: 30px;
line-height: 30px;
font-size: 14px;
padding-left: 10px;
padding-right: 10px;
cursor: pointer;
&:hover {
background-color: #dfdfe4;
}
&:active {
background-color: #d1d1d7;
}
}
}
</style>
@@ -0,0 +1,28 @@
<template>
<div :style="'background-image:url(' + bgi + ');'" class="empty"></div>
</template>
<script>
export default {
name: "fileEmpty",
data() {
return {
fileList: [],
};
},
methods: {},
computed: {
bgi() {
return this.$t("work.dropBg");
},
},
};
</script>
<style lang="scss" scoped>
.empty {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: cover;
}
</style>
@@ -0,0 +1,52 @@
<template>
<div class="list">
<listItem v-for="(item, key) in list" :key="key" :info="item" :del="del" />
</div>
</template>
<script>
import listItem from "./listItem";
export default {
name: "fileList",
props: {
list: {
type: Object | Array,
},
del: {
type: Function,
},
},
components: {
listItem,
},
data() {
return {};
},
methods: {},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="less" scoped>
.list {
height: 374px;
max-height: 374px;
overflow-x: hidden;
overflow-y: auto;
&::-webkit-scrollbar {
/*滚动条整体样式*/
width: 10px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
&::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #c7c7cb;
}
&::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 10px;
background: #ededed;
}
}
</style>
@@ -0,0 +1,129 @@
<template>
<div class="listItem">
<div class="item-body">
<el-row :gutter="10" class="row">
<el-col class="col file-name" :span="16">
<img :src="getIcon(info.name)" class="icon" />
<div class="name">{{ info.name }}</div>
</el-col>
<el-col class="col file-size" :span="5">{{
getSize(info.size)
}}</el-col>
<el-col class="col file-delete" :span="3">
<i class="el-icon-close close" @click="del(info.path)"></i>
</el-col>
</el-row>
<div></div>
</div>
</div>
</template>
<script>
import folder from "../../assets/files/folder.png";
import pdf from "../../assets/files/pdf.png";
import compress from "../../assets/files/compress.png";
import unknown from "../../assets/files/unknown.png";
import { bytesToSize, getExtension } from "./calc";
const types = { compress: ["zip", "rar", "7z"], pdf: ["pdf"] };
export default {
name: "listItem",
props: {
info: {
type: Object,
},
del: {
type: Function,
},
},
data() {
return {};
},
mounted() {
console.log(this.info);
},
methods: {
getSize(size) {
if (size == -1) {
return this.$t("file.calculate");
}
return bytesToSize(size);
},
getIcon(name) {
if (this.info.folder) {
return folder;
}
console.log(getExtension(name));
const ext = getExtension(name);
let icon = unknown;
for (const typeName in types) {
if (types[typeName].indexOf(ext) != -1) {
//属于这个分类
icon = this.typeToObj(typeName);
break;
}
}
return icon;
},
typeToObj(typeName) {
if (typeName == "compress") {
return compress;
} else if (typeName == "pdf") {
return pdf;
} else if (typeName == "unknown") {
return unknown;
}
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="less" scoped>
.listItem {
height: 50px;
width: 100%;
padding-left: 10px;
padding-right: 10px;
box-sizing: border-box;
}
.item-body {
width: 100%;
height: 100%;
border-bottom: 1px solid #cdcdcd;
}
.row {
height: 100%;
}
.col {
height: 100%;
}
.file-name {
padding-left: 20px !important;
display: flex;
align-items: center;
.icon {
width: 30px;
height: 30px;
}
.name {
font-size: 14px;
margin-left: 10px;
}
}
.file-size {
display: flex;
align-items: center;
font-size: 14px;
}
.file-delete {
display: flex;
align-items: center;
.close {
font-size: 16px;
cursor: pointer;
font-weight: bold;
&:hover {
color: red;
}
}
}
</style>
@@ -0,0 +1,254 @@
<template>
<div class="dialog">
<div class="progress">
<el-progress
:percentage="parseInt(fake.progress * 100)"
:format="format"
></el-progress>
<div class="txt">
<div class="over">{{ overNumber }}</div>
<div class="all">/{{ allNumber }}</div>
</div>
</div>
<div class="records">
<div class="record" v-for="(item, index) in progressList" :key="index">
<div class="name">{{ item.name }}</div>
<div class="state">
<div v-if="item.state === true">{{ $t("dialog.haveOver") }}</div>
<div v-else>{{ $t("dialog.notOver") }}</div>
</div>
</div>
</div>
<div class="last">
<div class="text" v-if="allNumber === overNumber && isSucess">
{{ $t("dialog.closeTips") }}
</div>
<div v-else-if="!isSucess" style="color: red">文件上传失败</div>
<div class="button">
<el-button
ref="btnStop"
type="success"
@click="stop()"
:disabled="overNumber == allNumber || !isSucess"
>{{ $t("dialog.stop") }}</el-button
>
<el-button
ref="btnOver"
type="success"
@click="changevisiable(flase)"
:disabled="overNumber != allNumber && isSucess"
>{{ $t("dialog.over") }}</el-button
>
</div>
</div>
</div>
</template>
<script>
import FakeProgress from "fake-progress";
export default {
name: "progressdialog",
props: {
list: {
type: Object | Array,
},
overNumber: {
type: Number,
},
allNumber: {
type: Number,
},
changevisiable: {
type: Function,
},
changestate: {
type: Function,
},
isSucess: {
type: Boolean,
},
},
data() {
return {
percentage: 0,
progressList: [],
fake: new FakeProgress({}),
sub: null,
};
},
methods: {
format(percentage) {
return;
},
getPercent(num, total) {
num = parseFloat(num);
total = parseFloat(total);
if (isNaN(num) || isNaN(total)) {
return "-";
}
return total <= 0 ? "0" : Math.round((num / total) * 10000) / 10000.0;
},
stop() {
this.changestate(false);
this.isSucess = false;
this.$message({offset:100,
message: this.$t("dialog.haveStop"),
type: "warning",
});
},
},
mounted() {
//const aProgress = this.fake.createSubProgress({
// timeConstant: 1000,
// end: 0.1,
// autoStart: true
// })
},
watch: {
overNumber: {
// 数据发生变化就会调用这个函数
handler(newVal, oldVal) {
if (this.sub) {
this.sub.end();
//console.log("sub end");
}
const newlist = [];
for (let i in this.list) {
if (this.list[i].state == true) {
newlist.push(this.list[i]);
} else {
newlist.unshift(this.list[i]);
}
}
this.progressList = newlist;
if (this.overNumber == this.allNumber) {
return;
}
this.sub = this.fake.createSubProgress({
timeConstant: 10000,
end: this.getPercent(this.overNumber + 1, this.allNumber),
autoStart: true,
});
//console.log(
// "overNumber/allNumber:" +
// this.getPercent(this.overNumber + 1, this.allNumber)
//);
},
// 立即处理 进入页面就触发
immediate: true,
deep: true,
},
isSucess:{
// 数据发生变化就会调用这个函数
handler(newVal, oldVal) {
if(!this.isSucess){
this.sub.stop();
}
},
// 立即处理 进入页面就触发
immediate: true,
deep: true,
}
},
};
</script>
<style lang="less" scoped>
.dialog {
}
.progress {
// border: 1px black solid;
/deep/ .el-progress-bar {
width: 97%;
}
/deep/.el-progress__text {
float: right;
}
.txt {
position: absolute;
top: 55px;
right: 20px;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
// border: 1px black solid;
.over {
// border: 1px black solid;
color: rgb(103, 194, 58);
font-size: 16px;
font-weight: 600;
}
.all {
// border: 1px black solid;
color: black;
font-size: 18px;
}
}
}
.records {
max-height: 400px;
overflow: auto;
&::-webkit-scrollbar {
/*滚动条整体样式*/
width: 10px; /*高宽分别对应横竖滚动条的尺寸*/
height: 1px;
}
&::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #c7c7cb;
}
&::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
border-radius: 10px;
background: #ededed;
}
}
.record {
margin-top: 15px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
.name {
}
.state {
margin-right: 20px;
}
}
.last {
height: 30px;
width: 100%;
margin-top: 15px;
.button {
float: right;
display: flex;
justify-content: flex-end;
align-items: center;
flex-wrap: wrap;
width: 50%;
.el-button {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 50px;
}
}
.text {
line-height: 30px;
float: left;
width: 50%;
font-size: 12px;
color: gray;
}
}
</style>
+845
View File
@@ -0,0 +1,845 @@
<template>
<div class="add_box">
<el-dialog center :visible.sync="addShow" width="80%" top="10vh" title="新建作业" :close-on-press-escape="false" :close-on-click-modal="false" :show-close="false">
<div @click="close" class="close_box">
<img src="@/assets/images/Close_icon.png" />
</div>
<div class="work_box">
<div class="flex_box flex_row_between work_top">
<div class="flex_box">
<div class="flex_box work_top_item">
<div class="work_top_label">容量</div>
<div class="work_top_val">
<el-select v-model="form.capacity" placeholder="请选择">
<el-option v-for="item in capacityList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box work_top_item">
<div class="work_top_label">拷贝类型</div>
<div class="work_top_val">
<el-select v-model="form.copy" placeholder="请选择">
<el-option v-for="item in copyList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
</div>
<el-popover v-model="setShow" placement="bottom-end" trigger="click">
<div class="set_box">
<div class="set_list">
<div class="flex_box set_item">
<div class="set_label">优先级</div>
<div class="set_val">
<el-select v-model="setForm.priority" placeholder="请选择">
<el-option v-for="item in priorityList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">目标工作站</div>
<div class="set_val">
<el-select v-model="setForm.stage" placeholder="请选择">
<el-option v-for="item in stageList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录速率</div>
<div class="set_val">
<el-select v-model="setForm.speed" placeholder="请选择">
<el-option v-for="item in speedList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后校验光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isCheck" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后关闭光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isClose" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后删除缓存</div>
<div class="set_val">
<el-switch v-model="setForm.isDel" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">作业失败打印标签</div>
<div class="set_val">
<el-switch v-model="setForm.isPrint" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">允许跨盘</div>
<div class="set_val">
<el-switch v-model="setForm.isArrow" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box flex_col_top set_item">
<div class="set_label">分盘设置</div>
<div class="set_val">
<el-radio-group v-model="setForm.disk">
<el-radio :label="item.id" v-for="(item, index) in diskSet" :key="index">
<div class="set_disk_name">{{ item.name }}</div>
<div class="set_disk_desc">{{ item.desc }}</div>
</el-radio>
</el-radio-group>
</div>
</div>
</div>
<div class="flex_box flex_row_center set_btns">
<el-button @click="setCancel" class="set_btn1">取消</el-button>
<el-button @click="setSure" class="set_btn2" type="primary">确定</el-button>
</div>
</div>
<div class="flex_box work_top_set" slot="reference">
<img src="@/assets/images/Setting_icon.png" />
<div>高级作业设置</div>
</div>
</el-popover>
</div>
<div class="flex_box flex_col_top work_main">
<div class="work_left">
<div class="flex_box flex_row_between work_left_top">
<div class="flex_box">
<div class="work_left_top_label">内容</div>
<div @click="addFolder" class="flex_box work_left_top_btn">
<img src="@/assets/images/file_icon.png" />
<div>添加文件夹</div>
</div>
<div @click="addFile" class="flex_box work_left_top_btn">
<img src="@/assets/images/paper_icon.png" />
<div>添加文件</div>
</div>
</div>
<div class="work_left_top_input">
<el-input placeholder="请输入卷标" v-model="form.volume">
<template slot="prepend">卷标</template>
</el-input>
</div>
</div>
<div class="work_files">
<div class="work_files_box">
<!-- 空文件 -->
<file-empty v-if="!fileList.length"></file-empty>
</div>
<div class="work_files_progress">
<div class="work_files_progress_content" :style="{ width: fileProgess + '%' }"></div>
<div class="work_files_progress_text">0.0MB/0MB</div>
</div>
</div>
</div>
<div class="work_right">
<div class="flex_box work_right_top">
<div class="flex_box">
<div class="work_right_top_label">标签</div>
<el-select v-model="form.capacity" placeholder="选择一个标签或点击创建标签">
<el-option v-for="item in capacityList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
<div class="work_right_top_file">···</div>
</div>
<div class="work_right_top_add">新建标签</div>
</div>
<div class="wook_soon" :class="{ wook_soon1: !showList }">
<div class="flex_box flex_row_center wook_soon_top">
<img class="wook_soon_img" src="@/assets/images/soon.jpg" />
<div @click="showList = !showList" class="flex_box flex_row_center wook_soon_btn" :class="{ wook_soon_btn1: !showList }">
<img src="@/assets/images/Arrow_up_icon.png" />
</div>
</div>
<div class="wook_soon_list">
<div v-for="(item, index) in soonList" :key="index" class="flex_box wook_soon_item">
<div class="wook_soon_label">OUT_TEXT_4[正面]</div>
<div class="wook_soon_input">
<el-input v-model="item.value" placeholder="请输入"></el-input>
</div>
<div class="wook_soon_add">添加字段</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="flex_box flex_row_between footer_box">
<div class="flex_box footer_main">
<div class="footer_input">
<el-input placeholder="请输入数量" v-model="form.number">
<template slot="prepend">数量</template>
</el-input>
</div>
</div>
<div class="flex_box footer_btns">
<el-button type="primary" class="footer_save" @click="workSave">保存</el-button>
<el-button type="primary" class="footer_submit" @click="workSubmit">提交</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
const { dialog } = require('@electron/remote')
import { mapGetters } from 'vuex'
import fileEmpty from './files/fileEmpty'
export default {
name: 'UserInfo',
components: { fileEmpty },
data() {
return {
addShow: true,
form: {
number: 1,
capacity: 1,
copy: 1,
volume: '2024-10-17'
},
capacityList: [
{
value: 1,
label: '4GB'
},
{
value: 2,
label: '8GB'
},
{
value: 3,
label: '16GB'
}
],
copyList: [
{
value: 1,
label: '复制'
},
{
value: 2,
label: '打印'
},
{
value: 3,
label: '复制与打印'
}
],
// 文件列表
fileList: [],
fileProgess: 30,
// soon文件数据
soonList: [
{
label: '正面',
value: 'tj44545fsa13yt544r15e6h4r6'
},
{
label: '二维码',
value: 'soon'
}
],
showList: true,
setShow: false,
setForm: {
priority: 2,
stage: 0,
speed: 0,
isCheck: true,
isDel: true,
isClose: true,
isArrow: true,
isPrint: true,
disk: 1
},
priorityList: [
{
value: 1,
label: '低'
},
{
value: 2,
label: '正常'
},
{
value: 3,
label: '高'
}
],
stageList: [
{
value: 0,
label: '任何'
},
{
value: 1,
label: '002A工作站'
}
],
speedList: [
{
value: 0,
label: 'Auto'
},
{
value: 1,
label: '低'
},
{
value: 2,
label: '中'
},
{
value: 3,
label: '高'
}
],
diskSet: [
{
id: 1,
name: '文件大小',
desc: '按照文件大小进行排序分盘'
},
{
id: 2,
name: '文件名称',
desc: '按照文件名称进行升序分盘'
},
{
id: 3,
name: '文件修改时间',
desc: '按照文件修改时间进行升序分盘'
}
]
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
// 选择文件
addFile() {
const _this = this
dialog
.showOpenDialog({
properties: ['multiSelections']
})
.then(async (res) => {
// for (const item of res.filePaths) {
// await fs.stat(item, function (err, res) {
// if (err) {
// return false
// }
// _this.insertList({
// name: getFileName(item),
// path: item,
// size: res.size,
// folder: false
// })
// })
// }
})
},
addFolder() {
const _this = this
dialog
.showOpenDialog({
properties: ['openDirectory', 'multiSelections']
})
.then((res) => {
// for (const item of res.filePaths) {
// const result = _this.insertList({
// name: getFileName(item),
// path: item,
// size: -1,
// folder: true
// })
// if (result) {
// calcSize(item, _this.folderCalcCallback)
// }
// }
})
},
// 保存
workSave() {
},
// 提交
workSubmit() {
},
show() {
this.addShow = true
},
close() {
this.addShow = false
},
// 高级设置
setCancel() {
this.setShow = false
},
setSure() {
this.setShow = false
}
}
}
</script>
<style lang="scss" scoped>
.add_box {
::v-deep {
.el-dialog {
border-radius: 6px;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.2);
.el-dialog__header {
padding: 28px 28px 18px 0 0;
.el-dialog__title {
font-size: 24px;
font-weight: 500;
color: #000;
}
}
.el-dialog__body {
padding: 0;
}
.el-dialog__footer {
padding: 0;
}
}
}
.close_box {
position: absolute;
z-index: 99;
top: 0;
right: 0;
cursor: pointer;
padding: 24px 24px 0 0;
img {
width: 28px;
height: 28px;
}
}
.work_box {
padding: 0 28px;
.work_top {
height: 80px;
.work_top_item {
margin-right: 50px;
.work_top_label {
font-weight: 500;
font-size: 19px;
color: #000000;
margin-right: 12px;
}
.work_top_val {
.el-input {
height: 32px;
line-height: 32px;
}
::v-deep {
.el-input__inner {
width: 180px;
height: 32px;
line-height: 32px;
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__icon {
height: 32px;
line-height: 32px;
font-weight: bold;
color: #000;
}
}
}
}
.work_top_set {
cursor: pointer;
height: 36px;
padding: 0 22px;
background: rgba(0, 195, 37, 0.12);
border-radius: 4px;
border: 1px solid rgba(0, 195, 37, 0.32);
font-weight: 500;
font-size: 18px;
color: #00c325;
img {
width: 22px;
height: 22px;
margin-right: 10px;
}
}
}
.work_main {
background-color: #f8f8f8;
border-radius: 4px;
.work_left {
width: 50%;
border-right: 1px solid #aaaaaa;
padding: 0 16px 16px;
.work_left_top {
height: 72px;
.work_left_top_label {
font-size: 19px;
font-weight: 500;
color: #000;
padding-right: 18px;
}
.work_left_top_btn {
cursor: pointer;
height: 32px;
padding: 0 22px;
background: rgba(0, 195, 37, 0.12);
border-radius: 4px;
border: 1px solid rgba(0, 195, 37, 0.32);
font-weight: 500;
font-size: 16px;
color: #00c325;
margin-right: 22px;
img {
width: 20px;
height: 20px;
margin-right: 10px;
}
}
.work_left_top_input {
.el-input {
height: 32px;
line-height: 32px;
}
::v-deep {
.el-input-group__prepend {
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__inner {
width: 180px;
height: 32px;
line-height: 32px;
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__icon {
font-size: 16px;
height: 32px;
line-height: 32px;
font-weight: bold;
color: #000;
}
}
}
}
.work_files {
height: 500px;
cursor: pointer;
.work_files_box {
height: 478px;
overflow-y: scroll;
}
.work_files_progress {
height: 22px;
padding: 2px 0;
position: relative;
border-radius: 12px;
background-color: #ffe6e6;
.work_files_progress_content {
transition: all 0.5s;
width: 0;
height: 18px;
background: #e54d4d;
border-radius: 10px;
}
.work_files_progress_text {
font-weight: 500;
font-size: 10px;
color: #ffffff;
line-height: 22px;
padding-left: 16px;
position: absolute;
left: 0;
top: 0;
z-index: 8;
}
}
}
}
.work_right {
width: 50%;
padding: 0 16px 16px;
.work_right_top {
height: 76px;
.work_right_top_label {
font-size: 19px;
font-weight: 500;
color: #000;
padding-right: 18px;
}
::v-deep {
.el-input__inner {
width: 300px;
height: 32px;
line-height: 32px;
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__icon {
font-size: 16px;
height: 32px;
line-height: 32px;
font-weight: bold;
color: #000;
}
}
.work_right_top_file {
width: 63px;
height: 32px;
cursor: pointer;
background: rgba(195, 189, 0, 0.12);
border-radius: 4px;
border: 1px solid rgba(182, 195, 0, 0.4);
font-weight: 500;
font-size: 16px;
color: #d9b028;
line-height: 32px;
text-align: center;
margin: 0 22px;
}
.work_right_top_add {
width: 123px;
height: 32px;
cursor: pointer;
background: #d8f6ff;
border-radius: 4px;
border: 1px solid #75d5f2;
font-weight: 500;
font-size: 16px;
color: #25aed9;
line-height: 32px;
text-align: center;
}
}
.wook_soon {
height: 500px;
.wook_soon_top {
height: 200px;
position: relative;
transition: all 0.5s;
.wook_soon_img {
height: 100%;
}
.wook_soon_btn {
position: absolute;
right: 0;
bottom: 10px;
width: 56px;
height: 56px;
z-index: 9;
background: #ffffff;
border-radius: 4px;
transition: all 0.5s;
cursor: pointer;
img {
width: 25px;
height: 16px;
}
}
.wook_soon_btn1 {
transform: rotate(180deg);
}
}
.wook_soon_list {
transition: all 0.5s;
margin-top: 18px;
height: 282px;
background: #ffffff;
border-radius: 2px;
border: 1px solid #eaeaea;
overflow-y: scroll;
padding: 0 22px;
.wook_soon_item {
height: 68px;
border-bottom: 2px solid #e1f5e5;
.wook_soon_label {
width: 200px;
font-weight: 500;
font-size: 16px;
color: #000000;
line-height: 28px;
}
.wook_soon_input {
width: 300px;
height: 32px;
border-radius: 4px;
::v-deep {
.el-input__inner {
width: 300px;
height: 32px;
line-height: 32px;
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__icon {
font-size: 16px;
height: 32px;
line-height: 32px;
font-weight: bold;
color: #000;
}
}
}
.wook_soon_add {
width: 110px;
height: 32px;
cursor: pointer;
background: #d8f6ff;
border-radius: 4px;
border: 1px solid #75d5f2;
font-weight: 500;
font-size: 16px;
color: #25aed9;
line-height: 30px;
text-align: center;
margin-left: 30px;
}
}
}
}
.wook_soon1 {
.wook_soon_top {
height: 100%;
}
.wook_soon_list {
height: 0;
margin: 0;
border: none;
}
}
}
}
}
.footer_box {
height: 90px;
padding: 0 28px;
.footer_main {
.footer_input {
}
}
.footer_btns {
.footer_save {
width: 166px;
height: 45px;
background: #00bcc3;
border-radius: 4px;
font-weight: 500;
font-size: 19px;
color: #ffffff;
padding: 0;
margin-right: 22px;
}
.footer_submit {
width: 166px;
height: 45px;
background: #00c325;
border-radius: 4px;
font-weight: 500;
font-size: 19px;
color: #ffffff;
padding: 0;
}
}
}
}
// 高级设置
.set_box {
.set_list {
padding: 24px;
.set_item {
padding: 10px 0;
.set_label {
width: 180px;
font-weight: 500;
font-size: 16px;
color: #000000;
line-height: 32px;
text-align: right;
padding-right: 12px;
}
.set_val {
::v-deep {
.el-input__inner {
width: 180px;
height: 32px;
line-height: 32px;
font-size: 16px;
color: #000;
font-weight: 500;
}
.el-input__icon {
height: 32px;
line-height: 32px;
font-weight: bold;
color: #000;
}
}
.el-switch {
zoom: 1.3;
}
.el-radio-group {
padding: 10px 0;
.el-radio {
display: flex;
.set_disk_name {
font-weight: 500;
font-size: 16px;
color: #000000;
line-height: 16px;
margin-bottom: 4px;
}
.set_disk_desc {
font-weight: 500;
font-size: 13px;
color: #666666;
line-height: 20px;
padding-bottom: 10px;
}
}
}
}
}
}
.set_btns {
border-top: 1px solid #e1f5e5;
height: 62px;
.set_btn1 {
width: 120px;
height: 32px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #000000;
font-weight: 500;
font-size: 16px;
color: #000000;
padding: 0;
margin: 0 12px;
}
.set_btn2 {
width: 120px;
height: 32px;
background: #00c325;
border-radius: 4px;
font-weight: 500;
font-size: 16px;
padding: 0;
margin: 0 12px;
}
}
}
</style>