This commit is contained in:
eoao
2025-04-26 17:24:39 +08:00
commit 24ba4772e6
107 changed files with 13612 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
<template>
<div class="box">
<div class="header-actions">
<Icon class="icon" icon="material-symbols-light:arrow-back-ios-new" width="20" height="20" @click="handleBack"/>
<Icon class="icon" icon="uiw:delete" width="16" height="16" @click="handleDelete"/>
<Icon class="icon" @click="changeStar" v-if="email.isStar" icon="fluent-color:star-16" width="20" height="20"/>
<Icon class="icon" @click="changeStar" v-else icon="solar:star-line-duotone" width="18" height="18"/>
</div>
<div></div>
<el-scrollbar class="scrollbar">
<div class="container">
<div class="email-title">
{{ email.subject }}
</div>
<div class="content">
<div class="email-info">
<div>
<div class="send"><span class="send-source">发件人</span>
<div class="send-name">
<span class="send-name-title">{{ email.name }}</span>
<span><{{ email.sendEmail }}></span>
</div>
</div>
<div class="receive"><span class="source">收件人</span><span>{{ email.receiveEmail }}</span></div>
<div class="date">
<div>{{ formatDetailDate(email.createTime) }}</div>
</div>
</div>
<div class="icon">
</div>
</div>
<el-scrollbar class="htm-scrollbar">
<div v-html="email.content"/>
</el-scrollbar>
<div class="att" v-if="atts.length > 0">
<div class="att-title">附件列表</div>
<div class="att-item" v-for="att in atts" :key="att.attId">
<img v-if="isImage(att.filename)" class="att-image" :src="cvtR2Url(att.key)"/>
<div class="att-icon" v-else>
<Icon :icon="toIcon(att.filename)" width="20" height="20"/>
</div>
<div class="att-name">
{{ att.filename }}
</div>
<div>{{ formatBytes(att.size) }}</div>
<div class="down-icon att-icon">
<a :href="cvtR2Url(att.key)" download>
<Icon icon="system-uicons:push-down" width="22" height="22"/>
</a>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import {reactive, watch} from "vue";
import {useRouter} from 'vue-router'
import {ElMessage, ElMessageBox} from 'element-plus'
import {attList, emailDelete} from "@/request/email.js";
import {Icon} from "@iconify/vue";
import {useEmailStore} from "@/store/email.js";
import {useAccountStore} from "@/store/account.js";
import day from "@/day/day.js";
import {starAdd, starCancel} from "@/request/star.js";
import {getExtName, formatBytes} from "@/utils/file-utils.js";
import {cvtR2Url} from "@/utils/convert-utils.js";
const accountStore = useAccountStore();
const emailStore = useEmailStore();
const router = useRouter()
const email = emailStore.readEmail
const atts = reactive([])
attList(email.emailId).then(list => {
atts.push(...list)
})
watch(() => accountStore.currentAccountId, () => {
handleBack()
})
function toIcon(filename) {
const extName = getExtName(filename)
if (['zip', 'rar', '7z', 'tar', 'tgz'].includes(extName)) return 'hugeicons:file-zip';
if (['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv'].includes(extName)) return 'fluent:video-clip-24-regular';
if (['txt', 'doc', 'docx', 'md'].includes(extName)) return 'hugeicons:google-doc'
if (['xls', 'csv', 'xlsx'].includes(extName)) return 'codicon:table';
if (['mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a'].includes(extName)) return 'mynaui:music';
if (['.ppt', 'pptx', 'pps', 'potx', 'pot'].includes(extName)) return 'lsicon:file-ppt-filled'
if (extName === 'pdf') return 'hugeicons:pdf-02';
if (extName === 'apk') return 'proicons:android';
if (extName === 'exe') return 'bi:filetype-exe';
return "hugeicons:attachment-01"
}
function isImage(filename) {
return ['png', 'jpg', 'jpeg', 'bmp', 'gif'].includes(getExtName(filename))
}
const formatDetailDate = (time) => {
return day(time).format('YYYY年M月D日 ddd AH:mm')
}
function changeStar() {
if (email.isStar) {
email.isStar = 0;
starCancel(email.emailId).then(() => {
email.isStar = 0;
emailStore.emailScroll?.editEmailStar(email.emailId, 0)
emailStore.starScroll?.deleteEmail([email.emailId])
}).catch((e) => {
console.error(e)
email.isStar = 1;
})
} else {
email.isStar = 1;
starAdd(email.emailId).then(() => {
email.isStar = 1;
emailStore.emailScroll?.editEmailStar(email.emailId, 1)
emailStore.starScroll?.addItemStar(email)
}).catch((e) => {
console.error(e)
email.isStar = 0;
})
}
}
const handleBack = () => {
router.back()
}
const handleDelete = () => {
ElMessageBox.confirm('确认删除该邮件吗?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
emailDelete(email.value.emailId).then(() => {
ElMessage({
message: '删除成功',
type: 'success',
plain: true,
})
emailStore.deleteIds = [email.value.emailId]
})
router.back()
})
}
</script>
<style scoped lang="scss">
.box {
height: 100%;
overflow: hidden;
}
.header-actions {
padding: 9px 15px;
display: flex;
align-items: center;
gap: 28px;
box-shadow: inset 0 -1px 0 0 rgba(100, 121, 143, 0.12);
font-size: 18px;
.icon {
cursor: pointer;
}
}
.scrollbar {
height: calc(100% - 38px);
width: 100%;
}
.container {
font-size: 14px;
padding-left: 15px;
padding-right: 15px;
padding-top: 10px;
.email-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
}
.htm-scrollbar {
padding-bottom: 15px;
word-break: break-all;
}
.content {
display: flex;
flex-direction: column;
.att {
margin-top: 20px;
padding-top: 10px;
border-top: 1px solid #e7e9ec;
display: flex;
flex-direction: column;
gap: 10px;
.att-title {
font-weight: bold;
}
.att-item {
div {
align-self: center;
}
padding: 10px;
border-radius: 4px;
align-self: start;
border: 1px solid #e7e9ec;
display: grid;
grid-template-columns: auto 1fr auto 20px;
gap: 10px;
.att-icon {
display: grid;
}
.att-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.att-image {
width: 60px;
height: 60px;
object-fit: contain;
}
.down-icon {
cursor: pointer;
}
}
}
.email-info {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #e7e9ec;
margin-bottom: 15px;
/*.date {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}*/
.date {
color: #585d69;
margin-bottom: 6px;
}
.send {
display: flex;
margin-bottom: 6px;
.send-name {
color: #585d69;
display: flex;
flex-wrap: wrap;
}
.send-name-title {
padding-right: 10px;
}
}
.receive {
margin-bottom: 6px;
span:nth-child(2) {
color: #585d69;
}
}
.send-source {
white-space: nowrap;
font-weight: bold;
padding-right: 10px;
}
.source {
white-space: nowrap;
font-weight: bold;
padding-right: 10px;
}
}
}
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<emailScroll ref="scroll"
:cancel-success="cancelStar"
:star-success="addStar"
:getEmailList="getEmailList"
:emailDelete="emailDelete"
:star-add="starAdd"
:star-cancel="starCancel"/>
</template>
<script setup>
import {useAccountStore} from "@/store/account.js";
import {useEmailStore} from "@/store/email.js";
import {useSettingStore} from "@/store/setting.js";
import emailScroll from "@/components/email-scroll/index.vue"
import {emailList, emailDelete, emailLatest} from "@/request/email.js";
import {starAdd, starCancel} from "@/request/star.js";
import {defineOptions, onMounted, ref, watch} from "vue";
import {sleep} from "@/utils/time-utils.js";
defineOptions({
name: 'email'
})
const emailStore = useEmailStore();
const accountStore = useAccountStore();
const settingStore = useSettingStore();
const scroll = ref({})
onMounted(() => {
emailStore.emailScroll = scroll;
latest()
})
watch(() => accountStore.currentAccountId, () => {
scroll.value.refreshList();
})
async function latest() {
while (true) {
const latestId = scroll.value.emailList[0]?.emailId ?? 0
if (!scroll.value.firstLoad && settingStore.settings.autoRefreshTime) {
try {
const accountId = accountStore.currentAccountId
const list = await emailLatest(latestId,accountId)
if (accountId === accountStore.currentAccountId) {
scroll.value.emailList.unshift(...list)
}
} catch (e) {
console.error(e)
}
}
await sleep(settingStore.settings.autoRefreshTime * 1000)
}
}
function addStar(email) {
emailStore.starScroll?.addItemStar(email)
}
function cancelStar(email) {
emailStore.starScroll?.deleteEmail([email.emailId])
}
function getEmailList(emailId, size) {
return emailList(accountStore.currentAccountId, emailId, size)
}
</script>

View File

@@ -0,0 +1,451 @@
<template>
<div id="box">
<div id="background-wrap">
<div class="x1 cloud"></div>
<div class="x2 cloud"></div>
<div class="x3 cloud"></div>
<div class="x4 cloud"></div>
<div class="x5 cloud"></div>
</div>
<div class="form-wrapper">
<el-form autocomplete="off">
<div class="container" >
<span class="form-title">{{ settingStore.settings.title }}</span>
<div class="custom-style" v-if="settingStore.settings.register === 0">
<el-segmented v-model="show" :options="options" />
</div>
<div v-if="show === 'login'">
<el-input v-model="form.email" type="text" placeholder="邮箱" autocomplete="off">
<template #prefix>
<Icon icon="weui:email-outlined" width="22" height="22" />
</template>
<template #append>
<div @click.stop="openSelect">
<el-select
ref="mySelect"
v-model="suffix"
placeholder="请选择"
class="select"
>
<el-option
v-for="item in domainList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div style="color: #333">
<span >{{suffix}}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20" />
</div>
</div>
</template>
</el-input>
<el-input v-model="form.password" placeholder="密码" type="password" autocomplete="off">
<template #prefix>
<Icon icon="carbon:password" width="22" height="22" />
</template>
</el-input>
<el-button class="btn" type="primary" @click="submit" :loading="uiStore.loginLoading"
>登录
</el-button>
</div>
<div v-else>
<el-input v-model="registerForm.email" type="text" placeholder="邮箱" autocomplete="off">
<template #prefix>
<Icon icon="weui:email-outlined" width="22" height="22" />
</template>
<template #append>
<div @click.stop="openSelect">
<el-select
ref="mySelect"
v-model="suffix"
placeholder="请选择"
class="select"
>
<el-option
v-for="item in domainList"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div style="color: #333">
<span>{{suffix}}</span>
<Icon class="setting-icon" icon="mingcute:down-small-fill" width="20" height="20" />
</div>
</div>
</template>
</el-input>
<el-input v-model="registerForm.password" placeholder="密码" type="password" autocomplete="off">
<template #prefix>
<Icon icon="carbon:password" width="22" height="22" />
</template>
</el-input>
<el-input v-model="registerForm.confirmPassword" placeholder="确认密码" type="password" autocomplete="off">
<template #prefix>
<Icon icon="carbon:password" width="22" height="22" />
</template>
</el-input>
<el-button class="btn" type="primary" @click="submitRegister" :loading="registerLoading"
>注册
</el-button>
</div>
</div>
</el-form>
</div>
<el-dialog
v-model="verifyShow"
title="验证你是不是人"
width="332"
align-center
>
<div
class="register-turnstile"
:data-sitekey="settingStore.settings.siteKey"
data-callback="onTurnstileSuccess"
></div>
</el-dialog>
</div>
</template>
<script setup>
import router from "@/router";
import {nextTick, reactive, ref} from "vue";
import {login} from "@/request/login.js";
import {register} from "@/request/login.js";
import {ElMessage} from 'element-plus'
import {isEmail} from "@/utils/verify-utils.js";
import {useUiStore} from "@/store/ui.js";
import {useSettingStore} from "@/store/setting.js";
import {Icon} from "@iconify/vue";
const settingStore = useSettingStore();
const uiStore = useUiStore()
const show = ref('login')
const form = reactive({
email: '',
password: '',
});
const options = [{label: '登录', value: 'login'},{label: '注册', value: 'register'}];
const mySelect = ref()
const suffix = ref('')
const registerForm = reactive({
email: '',
password: '',
confirmPassword: ''
})
const domainList = settingStore.domainList;
const registerLoading = ref(false)
suffix.value = domainList[0]
const verifyShow = ref(false)
let verifyToken = ''
let turnstileId = ''
window.onTurnstileSuccess = (token) => {
verifyToken = token;
setTimeout(() => {
verifyShow.value = false
},1500)
};
const openSelect = () => {
mySelect.value.toggleMenu()
}
const submit = () => {
if (!form.email) {
ElMessage({
message: '邮箱不能为空',
type: 'error',
plain: true,
})
return
}
if (!isEmail(form.email + suffix.value)) {
ElMessage({
message: '输入的邮箱不合法',
type: 'error',
plain: true,
})
return
}
if (!form.password) {
ElMessage({
message: '密码不能为空',
type: 'error',
plain: true,
})
return
}
if (form.password.length < 6) {
ElMessage({
message: '密码最少六位',
type: 'error',
plain: true,
})
return
}
uiStore.loginLoading = true
login(form.email+suffix.value, form.password).then(data => {
localStorage.setItem('token', data.token)
router.replace({name: 'layout'})
}).catch(() => {
uiStore.loginLoading = false
})
}
function submitRegister() {
if (!registerForm.email) {
ElMessage({
message: '邮箱不能为空',
type: 'error',
plain: true,
})
return
}
if (!isEmail(registerForm.email + suffix.value)) {
ElMessage({
message: '输入的邮箱不合法',
type: 'error',
plain: true,
})
return
}
if (!registerForm.password) {
ElMessage({
message: '密码不能为空',
type: 'error',
plain: true,
})
return
}
if (registerForm.password.length < 6) {
ElMessage({
message: '密码最少六位',
type: 'error',
plain: true,
})
return
}
if (registerForm.password !== registerForm.confirmPassword) {
ElMessage({
message: '两次密码输入不一致',
type: 'error',
plain: true,
})
return
}
if (!verifyToken && settingStore.settings.registerVerify === 0) {
verifyShow.value = true
if (!turnstileId) {
nextTick(() => {
turnstileId = window.turnstile.render('.register-turnstile')
})
} else {
nextTick(() => {
window.turnstile.reset(turnstileId);
})
}
return;
}
registerLoading.value = true
register({email: registerForm.email + suffix.value, password: registerForm.password,token: verifyToken}).then(() => {
show.value = 'login'
registerForm.email = ''
registerForm.password = ''
registerForm.confirmPassword = ''
registerLoading.value = false
verifyToken = ''
ElMessage({
message: '注册成功',
type: 'success',
plain: true,
})
}).catch(res => {
if (res.code === 400) {
verifyToken = ''
window.turnstile.reset(turnstileId)
verifyShow.value = true
}
registerLoading.value = false
});
}
</script>
<style lang="scss" scoped>
.form-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 110;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: #FFFFFF;
padding: 20px;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.switch {
padding-top: 10px;
font-size: 14px;
}
.btn {
width: 100%;
}
.form-title {
font-weight: bold;
font-size: 18px;
margin-bottom: 20px;
}
.el-input {
width: 100%;
margin-bottom: 15px;
}
}
form{
max-width: 410px;
padding: 0 20px;
}
.setting-icon {
position: relative;
top: 6px;
}
:deep(.el-input-group__append) {
padding: 0 !important;
padding-left: 8px !important;
background: #FFFFFF;
}
.select {
position: absolute;
right: 30px;
width: 100px;
opacity: 0;
pointer-events: none;
}
.custom-style {
margin-bottom: 10px;
}
.custom-style .el-segmented {
--el-border-radius-base: 8px;
width: 180px;
}
#box {
background: linear-gradient(to bottom, #2980b9, #6dd5fa, #fff);
color: #333;
font: 100% Arial, sans-serif;
height: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
#background-wrap {
height: 100%;
z-index: 0;
}
@keyframes animateCloud {
0% {
margin-left: -500px;
}
100% {
margin-left: 100%;
}
}
.x1 {
animation: animateCloud 30s linear infinite;
transform: scale(0.65);
}
.x2 {
animation: animateCloud 15s linear infinite;
transform: scale(0.3);
}
.x3 {
animation: animateCloud 25s linear infinite;
transform: scale(0.5);
}
.x4 {
animation: animateCloud 13s linear infinite;
transform: scale(0.4);
}
.x5 {
animation: animateCloud 20s linear infinite;
transform: scale(0.55);
}
.cloud {
background: linear-gradient(to bottom, #fff 5%, #f1f1f1 100%);
border-radius: 100px;
box-shadow: 0 8px 5px rgba(0, 0, 0, 0.1);
height: 120px;
width: 350px;
position: relative;
}
.cloud:after,
.cloud:before {
content: "";
position: absolute;
background: #fff;
z-index: -1;
}
.cloud:after {
border-radius: 100px;
height: 100px;
left: 50px;
top: -50px;
width: 100px;
}
.cloud:before {
border-radius: 200px;
height: 180px;
width: 180px;
right: 50px;
top: -90px;
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<div class="box">
<div class="pass">
<div class="title">账户与密码</div>
<div class="pass-item">
<div>邮箱</div>
<div>{{ userStore.user.email }}</div>
</div>
<div class="pass-item">
<div>密码</div>
<div>
<el-button type="primary" @click="pwdShow = true">修改密码</el-button>
</div>
</div>
</div>
<div class="del-email">
<div class="title">删除账户</div>
<div style="color: #585d69;">
此操作将永久删除您的账户及其所有数据无法恢复
</div>
<div>
<el-button type="primary" @click="deleteConfirm">删除账户</el-button>
</div>
</div>
<el-dialog v-model="pwdShow" title="修改密码" width="340">
<form>
<div class="update-pwd">
<el-input type="password" placeholder="新的密码" v-model="form.password"/>
<el-input type="password" placeholder="确认密码" v-model="form.newPwd"/>
<el-button type="primary" :loading="setPwdLoading" @click="submitPwd">保存</el-button>
</div>
</form>
</el-dialog>
</div>
</template>
<script setup>
import {defineOptions} from "vue";
import {reactive, ref} from 'vue'
import {resetPassword, userDelete} from "@/request/user.js";
import {ElMessage, ElMessageBox} from "element-plus";
import {useUserStore} from "@/store/user.js";
import router from "@/router/index.js";
const userStore = useUserStore();
const setPwdLoading = ref(false)
defineOptions({
name: 'setting'
})
const pwdShow = ref(false)
const form = reactive({
password: '',
newPwd: '',
})
const deleteConfirm = () => {
ElMessageBox.confirm('确认删除当前账号及所有数据吗?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userDelete().then(() => {
localStorage.removeItem('token');
router.replace('/login');
ElMessage({
message: '删除成功',
type: 'success',
plain: true,
})
})
})
}
function submitPwd() {
if (!form.password) {
ElMessage({
message: '密码不能为空',
type: 'error',
plain: true,
})
return
}
if (form.password.length < 6) {
ElMessage({
message: '密码不能小于6位',
type: 'error',
plain: true,
})
return
}
if (form.password !== form.newPwd) {
ElMessage({
message: '两次密码输入不一致',
type: 'error',
plain: true,
})
return
}
setPwdLoading.value = true
resetPassword(form.password).then(() => {
ElMessage({
message: '修改成功',
type: 'success',
plain: true,
})
pwdShow.value = false
setPwdLoading.value = false
form.password = ''
form.newPwd = ''
}).catch(() => {
setPwdLoading.value = false
})
}
</script>
<style scoped lang="scss">
.box {
padding: 40px 40px;
.update-pwd {
display: flex;
flex-direction: column;
gap: 15px;
}
.title {
font-size: 18px;
font-weight: bold;
}
.pass {
font-size: 14px;
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 40px;
.pass-item {
display: grid;
grid-template-columns: 50px 1fr;
gap: 140px;
@media (max-width: 767px) {
gap: 80px;
}
div:first-child {
font-weight: bold;
}
div:last-child {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.del-email {
font-size: 14px;
display: flex;
flex-direction: column;
gap: 20px;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<emailScroll type="star" ref="scroll"
:allow-star="false"
:cancel-success="cancelStar"
:getEmailList="starList"
:emailDelete="emailDelete"
:star-add="starAdd"
:star-cancel="starCancel"/>
</template>
<script setup>
import emailScroll from "@/components/email-scroll/index.vue"
import {emailDelete} from "@/request/email.js";
import {starAdd, starCancel, starList} from "@/request/star.js";
import {useEmailStore} from "@/store/email.js";
import {defineOptions, onMounted, ref} from "vue";
defineOptions({
name: 'star'
})
const scroll = ref({})
const emailStore = useEmailStore();
function cancelStar(email) {
emailStore.emailScroll?.editEmailStar(email.emailId, 0)
scroll.value.deleteEmail([email.emailId])
}
onMounted(() => {
emailStore.starScroll = scroll
})
</script>

View File

@@ -0,0 +1,248 @@
<template>
<div class="box">
<div class="setting">
<div class="title">网站设置</div>
<div class="setting-item">
<div><span>用户注册</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.register"/>
</div>
</div>
<div class="setting-item">
<div><span>添加邮箱</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.addEmail"/>
</div>
</div>
<div class="setting-item">
<div><span>邮件接收</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.receive"/>
</div>
</div>
<div class="setting-item">
<div><span>注册验证</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.registerVerify"/>
</div>
</div>
<div class="setting-item">
<div><span>添加验证</span></div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.addEmailVerify"/>
</div>
</div>
<div class="setting-item">
<div>
<span>多号模式</span>
<el-popover
content="开启后账号栏出现一个用户可以添加多个邮箱"
>
<template #reference>
<Icon class="warning" icon="fe:warning" width="20" height="20" />
</template>
</el-popover>
</div>
<div>
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
v-model="setting.manyEmail"/>
</div>
</div>
<div class="setting-item ">
<div>
<span>轮询刷新</span>
<el-popover
content="轮询请求服务器获取最新邮件人多可能会超出免费额度"
>
<template #reference>
<Icon class="warning" icon="fe:warning" width="20" height="20" />
</template>
</el-popover>
</div>
<div class="item-select">
<el-select
@change="change"
style="width: 80px;"
v-model="setting.autoRefreshTime"
placeholder="Select"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
<div class="setting-item">
<div class="title-item"><span>网站标题</span></div>
<div class="email-title">
<div>{{ setting.title }}</div>
<div @click="editTitleShow = true">
<Icon icon="iconamoon:edit-light" width="24" height="24"/>
</div>
</div>
</div>
</div>
<el-dialog v-model="editTitleShow" title="修改标题" width="340" @close="editTitle = ''">
<form>
<el-input type="text" placeholder="网站标题" v-model="editTitle"/>
<el-button type="primary" :loading="editTitleLoading" @click="saveTitle">保存</el-button>
</form>
</el-dialog>
</div>
</template>
<script setup>
import {defineOptions} from "vue";
import {ref} from 'vue'
import {settingSet} from "@/request/setting.js";
import {ElMessage} from "element-plus";
import {useSettingStore} from "@/store/setting.js";
import {useUserStore} from "@/store/user.js";
import {useAccountStore} from "@/store/account.js";
import {Icon} from "@iconify/vue";
defineOptions({
name: 'sys-setting'
})
const accountStore = useAccountStore();
const userStore = useUserStore();
const editTitleShow = ref(false)
const settingStore = useSettingStore();
let setting = ref(settingStore.settings)
const editTitle = ref('')
const editTitleLoading = ref(false)
let backup = {}
const options = [
{
label: '关闭',
value: 0,
},
{
label: '3s',
value: 3,
},
{
label: '5s',
value: 5,
},
{
label: '7s',
value: 7,
},
{
label: '10s',
value: 10,
},
{
label: '15s',
value: 15,
},
{
label: '20s',
value: 20,
}
]
function beforeChange() {
backup = JSON.stringify(setting.value)
return true
}
function change() {
editSetting(setting)
}
function saveTitle() {
backup = JSON.stringify(setting.value)
setting.value.title = editTitle.value
editSetting(setting)
}
function editSetting(setting) {
editTitleLoading.value = true
settingSet(setting.value).then(() => {
editTitleLoading.value = false
ElMessage({
message: "设置成功",
type: "success",
plain: true
})
if (setting.value.manyEmail === 1) {
accountStore.currentAccountId = userStore.user.accountId;
}
editTitleShow.value = false
}).catch(() => {
editTitleLoading.value = false
setting.value = JSON.parse(backup)
})
}
</script>
<style scoped lang="scss">
.box {
padding: 40px 40px;
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.setting {
font-size: 14px;
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 40px;
.setting-item {
display: flex;
gap: 140px;
font-weight: bold;
position: relative;
@media (max-width: 767px) {
gap: 80px;
}
>div:first-child {
display: flex;
justify-content: center;
align-items: center;
}
}
.warning {
position: absolute;
left: 65px;
color: gray;
}
}
}
.email-title {
font-weight: normal !important;
display: flex;
gap: 10px;
div:first-child {
display: flex;
align-items: center;
}
}
.el-button {
margin-top: 15px;
width: 100%;
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<el-scrollbar>
<div class="scrollbar-flex-content">
<p v-for="item in 1000" :key="item" class="scrollbar-demo-item">
{{ item }}
</p>
</div>
</el-scrollbar>
</template>
<style scoped>
.scrollbar-flex-content {
display: grid;
grid-template-columns: 200px 200px 200px 200px 200px 200px 200px 200px 200px 200px 200px;
width: 40px;
}
.scrollbar-demo-item {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 50px;
margin: 10px;
text-align: center;
border-radius: 4px;
background: var(--el-color-danger-light-9);
color: var(--el-color-danger);
}
</style>