💄 style(custom): 自定义alert样式

This commit is contained in:
nongyehong
2023-10-04 04:26:13 +08:00
committed by Dawn
parent 360dc459e1
commit 0c9977a4ac
15 changed files with 236 additions and 168 deletions

1
src/assets/svg/error.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -65,26 +65,4 @@ const { show, width } = defineProps<{
.modal-body {
margin: 20px 0;
}
/*
* 对于 transition="modal" 的元素来说
* 当通过 Vue.js 切换它们的可见性时
* 以下样式会被自动应用。
*
* 你可以简单地通过编辑这些样式
* 来体验该模态框的过渡效果。
*/
.modal-enter-from {
opacity: 0;
}
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
transform: scale(1.1);
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<!--自定义alert-->
<transition :enter-active-class="enterActive" :leave-active-class="leaveActive">
<div v-if="show" class="alert">
<div style="display: flex; align-items: center; gap: 20px">
<img :src="imgUrl" style="width: 100px; height: 100px" alt="" />
<div style="display: flex; flex-direction: column; gap: 5px">
<span style="display: flex; align-items: center; gap: 5px; color: #ee9f20">
<n-icon :size="20" :component="AlertTriangle" />{{ title }}
</span>
<span>{{ text }}</span>
</div>
</div>
<div style="padding: 5px 0; cursor: pointer" @click="alertOff">
<n-icon :component="X" />
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { AlertTriangle, X } from '@vicons/tabler'
defineOptions({ name: 'AlertIze' })
const { title, text, show, imgUrl, leaveActive, enterActive } = defineProps<{
imgUrl: string
show: boolean
title?: string
text?: string
enterActive: string
leaveActive: string
}>()
const emits = defineEmits(['alertOff'])
const alertOff = () => {
emits('alertOff')
}
</script>
<style scoped>
.alert {
width: 100%;
height: fit-content;
background: #fcf5eb;
box-sizing: border-box;
padding: 0 10px;
border-radius: 10px;
display: flex;
justify-content: space-between;
}
</style>

4
src/customize/index.ts Normal file
View File

@@ -0,0 +1,4 @@
import AlertIze from './alertIze.vue'
import { renderMessage } from './messageIze'
export { AlertIze, renderMessage }

View File

@@ -36,8 +36,7 @@ export const useLogin = () => {
const loginText = ref(t('login'))
const loginErrorMsg = ref<boolean>(false)
const loginErrorText = ref<string>()
const loginErrorType = ref()
const loginErrorTitle = ref()
const loginErrorTitle = ref<string>()
const statusCode = ref()
/**
* 用户登录校验
@@ -87,11 +86,9 @@ export const useLogin = () => {
loginErrorText.value = res.msg
if (res.code === RCodeEnum.FAIL) {
statusCode.value = res.code
loginErrorType.value = 'warning'
loginErrorTitle.value = t('account_error')
} else {
statusCode.value = res.code
loginErrorType.value = 'error'
loginErrorTitle.value = t('login_error')
}
})
@@ -152,7 +149,6 @@ export const useLogin = () => {
disabled,
loginErrorMsg,
loginErrorText,
loginErrorType,
loginErrorTitle,
statusCode,
showModal,

View File

@@ -42,7 +42,7 @@
</div>
</template>
<script setup lang="ts">
<script setup lang="tsx">
import type { MenuOption } from 'naive-ui'
import { NIcon } from 'naive-ui'
import { storeToRefs } from 'pinia'
@@ -85,41 +85,26 @@ const handleCollapsed = () => {
}
const renderIcon = (icon: string) => {
return () => h(NIcon, null, { default: () => h((vicons as any)[icon]) })
return () => <NIcon component={(vicons as any)[icon]} />
}
const menuOptions: MenuOption[] = menus.map((menu: Menu) => {
const menuOption: MenuOption = {
label: () =>
h(
RouterLink,
{
to: {
name: menu.page
}
},
{ default: () => menu.name }
),
key: menu.path as any,
label: () => <RouterLink to={{ name: menu.page }}>{() => menu.name}</RouterLink>,
key: menu.path as string,
icon: renderIcon(menu.icon)
}
if (menu.path) {
return menuOption
}
menuOption.children = menu.children?.map((child) => ({
label: () =>
h(
RouterLink,
{
to: {
name: child.page
}
},
{ default: () => child.name }
),
key: child.path as any,
label: () => <RouterLink to={{ name: child.page }}>{() => child.name}</RouterLink>,
key: child.path as string,
icon: renderIcon(child.icon)
}))
return menuOption
})
</script>

View File

@@ -25,12 +25,13 @@
<script lang="ts" setup>
import { computed } from 'vue'
import * as vicons from '@vicons/tabler'
import { Menu } from '@/services/types'
defineOptions({ name: 'SearchResult' })
const { value, options } = defineProps<{
value: string
options: any[]
options: Menu[]
}>()
interface Emits {
@@ -50,11 +51,11 @@ const active = computed({
})
/** 鼠标移入 */
async function handleMouse(item: any) {
const handleMouse = async (item: any) => {
active.value = item.path
}
function handleTo() {
const handleTo = () => {
emit('enter')
}
</script>

View File

@@ -20,9 +20,46 @@ defineOptions({ name: 'GlobalSearch' })
const { t } = i18n.global
const show = ref(false)
const shiftCount = ref(0)
let shiftTimeout: NodeJS.Timeout | null = null
const showSearch = () => {
show.value = true
// 重置计数器和延时器
shiftCount.value = 0
if (shiftTimeout !== null) {
clearTimeout(shiftTimeout)
shiftTimeout = null
}
}
// 监听键盘事件
window.addEventListener('keydown', (event) => {
if (event.key === 'Shift') {
shiftCount.value++
if (shiftCount.value === 1) {
// 如果按下了第一次 Shift 键,则设置延时器
shiftTimeout = setTimeout(() => {
shiftCount.value = 0
shiftTimeout = null
}, 1000) // 1秒内没有第二次 Shift 键按下,重置计数器
} else if (shiftCount.value === 2) {
// 如果按下了第二次 Shift 键,则触发 showSearch
showSearch()
shiftCount.value = 0
if (shiftTimeout !== null) {
clearTimeout(shiftTimeout)
shiftTimeout = null
}
}
} else {
shiftCount.value = 0
if (shiftTimeout !== null) {
clearTimeout(shiftTimeout)
shiftTimeout = null
}
}
})
</script>
<style scoped>

View File

@@ -1,14 +1,13 @@
<template>
<n-space vertical>
<div :class="animation" v-if="showWarn" class="alert">
<span style="display: flex; align-items: center; gap: 20px">
<img src="@/assets/svg/warning.svg" style="width: 100px; height: 100px" alt="" />
<span style="color: #ee9f20; font-weight: bold">{{ warn }}</span>
</span>
<div style="padding: 5px 0; cursor: pointer" @click="alertOff">
<n-icon :component="X" />
</div>
</div>
<AlertIze
img-url="./src/assets/svg/warning.svg"
:enter-active="'animate__animated animate__bounceIn'"
:leave-active="'animate__animated animate__fadeOutUp'"
title="警告"
:text="warn"
:show="showWarn"
@alertOff="alertOff" />
<div class="box">
<span>{{ t('eye_shield') }}</span>
<n-switch :rubber-band="false" :value="olForm.themeStatus" :loading="loading" @update:value="switchTheme">
@@ -34,13 +33,13 @@
</template>
<script setup lang="ts">
import { Moon, Sun, X } from '@vicons/tabler'
import { Moon, Sun } from '@vicons/tabler'
import { i18n } from '@/i18n'
import { mainStore } from '@/stores/main'
import { storeToRefs } from 'pinia'
import { darkTheme } from 'naive-ui'
import { cloneDeep } from 'lodash-es'
import { animation } from '@/components/modal/type'
import { AlertIze } from '@/customize'
const { t } = i18n.global
const store = mainStore()
@@ -57,6 +56,7 @@ const emit = defineEmits(['saveSettings', 'alertOff'])
/**
* 使用旧版解构
* const { warn, showWarn } = toRefs(props)
* 如果需要使用默认值withDefaults的时候使用新版解构方式会报错
* 新版vite.config开启解构语法可以直接解构并且具有响应式
* 解构出来的值是reactive类型
* */

View File

@@ -10,7 +10,11 @@
<n-drawer v-model:show="active" :width="drawerWidth">
<n-drawer-content :title="t('settings')" closable>
<Content @saveSettings="(args) => (Form = args)" @alertOff="handleAlertOff" :show-warn="showWarn" :warn="warn" />
<Content
@saveSettings="(args) => (Form = args)"
@alertOff="showWarn = false"
:show-warn="showWarn"
:warn="warn" />
<template #footer>
<n-button style="width: 100%" :loading="loading" secondary type="primary" @click="save(Form)">{{
t('save')
@@ -26,7 +30,6 @@ import { i18n } from '@/i18n'
import Content from './content.vue'
import { storeToRefs } from 'pinia'
import { mainStore } from '@/stores/main'
import { animation } from '@/components/modal/type'
const { t, locale } = i18n.global
const active = ref(false)
@@ -47,7 +50,6 @@ const showDrawer = () => {
}
const save = (val: any) => {
if (JSON.stringify({ ...val }) === JSON.stringify({ ...Form })) {
animation.value = 'animate__animated animate__bounceIn'
showWarn.value = true
warn.value = '表单内容没有修改'
return
@@ -61,13 +63,6 @@ const save = (val: any) => {
store.toggleTheme()
}, 1000)
}
/*处理警告关闭事件*/
const handleAlertOff = () => {
animation.value = 'animate__animated animate__fadeOutUp'
setTimeout(() => {
showWarn.value = false
}, 500)
}
watchEffect(() => {
/*监听drawer的宽度跟随语言切换而改变*/

View File

@@ -82,7 +82,7 @@
</template>
<template #footer>
<div style="display: flex; align-content: center; justify-content: space-between">
<n-button quaternary type="tertiary" @click="showModal = false">{{ t('cancel') }}</n-button>
<n-button quaternary type="tertiary" @click="cancel">{{ t('cancel') }}</n-button>
<n-button secondary type="error" @click="shutDown(formRef)">{{ t('close') }}</n-button>
</div>
</template>
@@ -105,7 +105,7 @@ import paging from '@/hooks/usePaging'
import UserVar from './UserVar'
import { Role, User } from '@/services/types'
import { userStore } from '@/stores/user'
import { renderMessage } from '@/customize/messageIze'
import { renderMessage } from '@/customize'
import { useAuth } from '@/hooks/useAuth'
import { handRelativeTime } from '@/utils/day'
import { animation } from '@/components/modal/type'
@@ -263,10 +263,22 @@ const clone = () => {
drawerShow.value = true
}
/*关闭弹框*/
const shutDown = (formRef: FormInst) => {
showModal.value = false
drawerShow.value = false
formRef.restoreValidation()
animation.value = 'modal-container animate__animated animate__fadeOutLeft'
setTimeout(() => {
showModal.value = false
drawerShow.value = false
formRef.restoreValidation()
}, 100)
}
/*取消*/
const cancel = () => {
animation.value = 'modal-container animate__animated animate__fadeOutDown'
setTimeout(() => {
showModal.value = false
}, 100)
}
</script>

View File

@@ -2,11 +2,16 @@
<div class="login">
<h1>{{ t('login') }} HuLa</h1>
<!-- 登录错误提示框 -->
<n-alert v-if="loginErrorMsg" class="login_alert" :title="loginErrorTitle" :type="loginErrorType" closable>
<template #default>
{{ loginErrorText }}
</template>
</n-alert>
<div style="padding: 0 10px 10px 5px">
<AlertIze
img-url="./src/assets/svg/error.svg"
:text="loginErrorText"
:title="loginErrorTitle"
:show="loginErrorMsg"
:enter-active="'animate__animated animate__bounceIn'"
:leave-active="'animate__animated animate__hinge'"
@alertOff="loginErrorMsg = false" />
</div>
<!-- 登录表单 -->
<n-card class="form">
<n-form ref="formRef" :show-require-mark="false" :rules="rules as any" :model="ruleForm">
@@ -23,12 +28,17 @@
</template>
</n-input>
</n-form-item>
<!--忘记密码-->
<div class="paw-title">
<p style="font-size: 14px; color: #cccccc">{{ t('password') }}</p>
<p style="font-size: 12px; color: #337ecc; cursor: pointer" @click="changePawBox">
{{ t('forgot_password') }}
</p>
<n-popover trigger="hover">
<template #trigger>
<p style="font-size: 12px; color: #337ecc; cursor: pointer" @click="changePawBox">
{{ t('forgot_password') }}
</p>
</template>
<img src="@/assets/svg/forgotPwd.svg" style="width: 140px; height: 140px" alt="" />
</n-popover>
</div>
<n-form-item :validation-status="ValidationStatus" path="password" :label="t('password')" :show-label="false">
<n-input
@@ -135,6 +145,7 @@ import useModal from '@/hooks/useModal'
import { useLogin } from '@/hooks/useLogin'
import { animation } from '@/components/modal/type'
import { Lock, User } from '@vicons/tabler'
import { AlertIze } from '@/customize'
const { t } = i18n.global
const store = mainStore()
@@ -147,7 +158,6 @@ const {
signInLoading,
formRef,
loginText,
loginErrorType,
loginErrorTitle,
showModal,
ruleForm,
@@ -205,6 +215,7 @@ const handleRemember = () => {
rememberOption.value = rememberMe
}
}
onMounted(() => {
handleRemember()
})

View File

@@ -18,9 +18,6 @@
</template>
<script setup lang="ts">
import Mit from '@/utils/Bus'
Mit.emit('pagination', false)
const buttonToggled = ref(false)
const numElements = ref(3)
const addElement = () => {

View File

@@ -1,77 +1,77 @@
module.exports = {
root: true,
extends: ['stylelint-config-standard', 'stylelint-config-html/vue', 'stylelint-config-recess-order'],
plugins: ['stylelint-order', 'stylelint-prettier', 'stylelint-scss'],
overrides: [
{
files: ['**/*.(css|html|vue)'],
customSyntax: 'postcss-html',
},
{
files: ['*.scss', '**/*.scss'],
customSyntax: 'postcss-scss',
extends: ['stylelint-config-standard-scss', 'stylelint-config-recommended-vue/scss'],
},
],
rules: {
'selector-class-pattern': null,
'no-descending-specificity': null,
'scss/dollar-variable-pattern': null,
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep', 'global'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
'use',
],
},
],
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
type: 'at-rule',
name: 'supports',
},
{
type: 'at-rule',
name: 'media',
},
'rules',
],
{ severity: 'warning' },
],
},
ignoreFiles: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx'],
root: true,
extends: ['stylelint-config-standard', 'stylelint-config-html/vue', 'stylelint-config-recess-order'],
plugins: ['stylelint-order', 'stylelint-prettier', 'stylelint-scss'],
overrides: [
{
files: ['**/*.(css|html|vue)'],
customSyntax: 'postcss-html'
},
{
files: ['*.scss', '**/*.scss'],
customSyntax: 'postcss-scss',
extends: ['stylelint-config-standard-scss', 'stylelint-config-recommended-vue/scss']
}
],
rules: {
'selector-class-pattern': null,
'no-descending-specificity': null,
'scss/dollar-variable-pattern': null,
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep', 'global']
}
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted']
}
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
'use'
]
}
],
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested']
}
],
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
type: 'at-rule',
name: 'supports'
},
{
type: 'at-rule',
name: 'media'
},
'rules'
],
{ severity: 'warning' }
]
},
ignoreFiles: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx']
}