This commit is contained in:
Fifteen 2024-01-25 07:17:01 +00:00 committed by GitHub
commit 787f4c31f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 262 additions and 27 deletions

View File

@ -56,7 +56,8 @@
"vue3-text-clamp": "^0.1.1",
"vuedraggable": "^4.1.0",
"vuex": "^4.0.0-0",
"xlsx": "^0.18.5"
"xlsx": "^0.18.5",
"@zxcvbn-ts/core": "^3.0.4"
},
"devDependencies": {
"@commitlint/cli": "^17.3.0",

View File

@ -17,6 +17,9 @@ dependencies:
'@wangeditor/editor-for-vue':
specifier: ^5.1.12
version: 5.1.12(@wangeditor/editor@5.1.23)(vue@3.3.13)
'@zxcvbn-ts/core':
specifier: ^3.0.4
version: 3.0.4
axios:
specifier: ^0.27.2
version: 0.27.2
@ -204,7 +207,7 @@ devDependencies:
version: 0.7.3(vite@3.2.7)(vue@3.3.13)
vite:
specifier: ^3.0.0
version: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
version: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
vite-plugin-compression:
specifier: ^0.5.1
version: 0.5.1(vite@3.2.7)
@ -400,7 +403,7 @@ packages:
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
ts-node: 10.9.2(@types/node@18.19.3)(typescript@4.9.5)
typescript: 4.9.5
transitivePeerDependencies:
- '@swc/core'
@ -776,7 +779,6 @@ packages:
dependencies:
undici-types: 5.26.5
dev: true
optional: true
/@types/node@20.10.5:
resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
@ -1124,7 +1126,7 @@ packages:
vite: ^3.0.0
vue: ^3.2.25
dependencies:
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
vue: 3.3.13(typescript@4.9.5)
dev: true
@ -1465,6 +1467,12 @@ packages:
snabbdom: 3.5.1
dev: false
/@zxcvbn-ts/core@3.0.4:
resolution: {integrity: sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw==}
dependencies:
fastest-levenshtein: 1.0.16
dev: false
/JSONStream@1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true
@ -2353,7 +2361,7 @@ packages:
dependencies:
'@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@4.9.5)
ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
ts-node: 10.9.2(@types/node@18.19.3)(typescript@4.9.5)
typescript: 4.9.5
dev: true
@ -3438,6 +3446,11 @@ packages:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
dev: true
/fastest-levenshtein@1.0.16:
resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
engines: {node: '>= 4.9.1'}
dev: false
/fastq@1.16.0:
resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==}
dependencies:
@ -6675,7 +6688,7 @@ packages:
typescript: 4.9.5
dev: true
/ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5):
/ts-node@10.9.2(@types/node@18.19.3)(typescript@4.9.5):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
@ -6694,7 +6707,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.5.1
'@types/node': 18.19.3
acorn: 8.11.2
acorn-walk: 8.3.1
arg: 4.1.3
@ -6940,7 +6953,7 @@ packages:
dependencies:
acorn: 8.11.2
chokidar: 3.5.3
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.6
dev: true
@ -6964,7 +6977,7 @@ packages:
dependencies:
acorn: 8.11.2
chokidar: 3.5.3
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.6
dev: true
@ -7138,7 +7151,7 @@ packages:
chalk: 4.1.2
debug: 4.3.4(supports-color@9.4.0)
fs-extra: 10.1.0
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
transitivePeerDependencies:
- supports-color
dev: true
@ -7155,7 +7168,7 @@ packages:
fs-extra: 10.1.0
magic-string: 0.25.9
pathe: 0.2.0
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
dev: true
/vite-plugin-svg-icons@2.0.1(vite@3.2.7):
@ -7171,7 +7184,7 @@ packages:
pathe: 0.2.0
svg-baker: 1.7.0
svgo: 2.8.0
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
transitivePeerDependencies:
- supports-color
dev: true
@ -7183,10 +7196,10 @@ packages:
dependencies:
'@vue/compiler-sfc': 3.3.13
magic-string: 0.25.9
vite: 3.2.7(@types/node@20.5.1)(sass@1.69.5)
vite: 3.2.7(@types/node@18.19.3)(sass@1.69.5)
dev: true
/vite@3.2.7(@types/node@20.5.1)(sass@1.69.5):
/vite@3.2.7(@types/node@18.19.3)(sass@1.69.5):
resolution: {integrity: sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@ -7211,7 +7224,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 20.5.1
'@types/node': 18.19.3
esbuild: 0.15.18
postcss: 8.4.32
resolve: 1.22.8

View File

@ -0,0 +1,76 @@
.inputStrength {
width: 100%;
:deep(.#{'el'}-input__clear) {
margin-left: 5px;
}
&-input {
&_icon {
cursor: pointer;
}
}
&-line {
background-color: var(--el-text-color-disabled);
border-radius: var(--el-border-radius-base);
position: relative;
margin-bottom: 6px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
height: 6px;
&::before,
&::after {
position: absolute;
z-index: 10;
display: block;
width: 20%;
height: inherit;
background-color: transparent;
border-color: var(--el-color-white);
border-style: solid;
border-width: 0 5px;
content: '';
}
&::before {
left: 20%;
}
&::after {
right: 20%;
}
&_visual {
position: absolute;
width: 0;
height: inherit;
background-color: transparent;
border-radius: inherit;
transition: width 0.5s ease-in-out, background 0.25s;
&[data-score='0'] {
width: 20%;
background-color: var(--el-color-danger);
}
&[data-score='1'] {
width: 40%;
background-color: var(--el-color-danger);
}
&[data-score='2'] {
width: 60%;
background-color: var(--el-color-warning);
}
&[data-score='3'] {
width: 80%;
background-color: var(--el-color-success);
}
&[data-score='4'] {
width: 100%;
background-color: var(--el-color-success);
}
}
}
}

View File

@ -0,0 +1,72 @@
<template>
<div class="inputStrength">
<el-input v-model="inputValue" placeholder="请输入密码" :type="textType" v-bind="$attrs" class="inputStrength-input">
<template #suffix>
<div class="inputStrength-input_icon" @click="changeTextType">
<svg-icon :icon-class="passwordType ? 'eye' : 'eye-open'" />
</div>
</template>
</el-input>
<div v-if="strength" class="inputStrength-line">
<div class="inputStrength-line_visual" :data-score="getPasswordStrength"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { watch, unref, ref, computed, PropType } from 'vue'
import type { ZxcvbnResult } from '@zxcvbn-ts/core'
import { zxcvbn } from '@zxcvbn-ts/core'
const props = defineProps({
//
strength: {
type: Boolean as PropType<boolean>,
default: false,
},
modelValue: {
type: String as PropType<string>,
default: '',
},
})
const emits = defineEmits(['update:modelValue'])
//
const inputValue = ref(props.modelValue)
//
const textType = ref<'password' | 'text'>('password')
//
const changeTextType = () => {
textType.value = unref(textType) === 'text' ? 'password' : 'text'
}
//icon
const passwordType = computed(() => unref(textType) === 'password')
//
const getPasswordStrength = computed(() => {
const value = unref(inputValue)
const zxcvbnRef = zxcvbn(inputValue.value) as ZxcvbnResult
return value ? zxcvbnRef.score : -1
})
watch(
() => props.modelValue,
(val: string) => {
if (val === unref(inputValue)) return
inputValue.value = val
},
)
watch(
() => inputValue.value,
(val: string) => {
emits('update:modelValue', val)
},
)
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -1,14 +1,17 @@
<template>
<el-dialog v-model="dialogVisible" title="修改密码" width="40%">
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize">
<el-form-item label="姓名">
<el-form-item label="用户名称">
<el-input v-model="ruleForm.name" disabled></el-input>
</el-form-item>
<el-form-item label="旧的密码" prop="password">
<el-input v-model="ruleForm.password" type="password"></el-input>
<el-form-item label="旧密码" prop="oldPassword">
<inputStrength v-model="ruleForm.oldPassword" placeholder="请输入旧密码"></inputStrength>
</el-form-item>
<el-form-item label="新的密码" prop="configPassword">
<el-input v-model="ruleForm.configPassword" type="password"></el-input>
<el-form-item label="新密码" prop="newPassword">
<inputStrength v-model="ruleForm.newPassword" strength placeholder="请输入新密码"></inputStrength>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<inputStrength v-model="ruleForm.confirmPassword" strength placeholder="请确认新密码"></inputStrength>
</el-form-item>
</el-form>
<template #footer>
@ -21,8 +24,10 @@
</template>
<script lang="ts" setup>
import { ref, defineExpose, reactive } from 'vue'
import { ref, computed, defineExpose, reactive } from 'vue'
import type { ElForm } from 'element-plus'
import inputStrength from '@/components/InputStrength/index.vue'
const dialogVisible = ref(false)
import { useUserStore } from '@/store/modules/user'
const UserStore = useUserStore()
@ -34,21 +39,37 @@
}
type FormInstance = InstanceType<typeof ElForm>
const userInfo = computed(() => UserStore.userInfo)
const formSize = ref('')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive({
name: UserStore.userInfo.username,
password: UserStore.userInfo.password,
configPassword: '',
name: userInfo.value?.username,
oldPassword: userInfo.value?.password,
newPassword: '',
confirmPassword: '',
})
//
const equalToPassword = (_rule, value, callback) => {
ruleForm.newPassword !== value ? callback(new Error('两次输入密码不一致')) : callback()
}
const rules = reactive({
configPassword: [
newPassword: [
{
required: true,
message: '请输入新的密码',
message: '请输入新密码',
trigger: 'blur',
},
],
confirmPassword: [
{
required: true,
message: '请输入确认密码',
trigger: 'blur',
},
{ required: true, validator: equalToPassword, trigger: 'blur' },
],
})
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return

View File

@ -103,6 +103,12 @@ const othersRouter = [
name: 'text-clamp',
meta: { title: '多行文本省略', icon: 'MenuIcon' },
},
{
path: '/other/pass-strength',
component: () => import('@/views/other/passStrength/index.vue'),
name: 'pass-strength',
meta: { title: '密码强度校验', icon: 'MenuIcon' },
},
],
},
]

View File

@ -0,0 +1,46 @@
<template>
<PageWrapLayout class="components-container">
<div class="m-pass-strength">
<el-card style="margin-bottom: 10px">
<template #header>
<span>密码强度示例</span>
</template>
<el-form :model="ruleForm" label-width="120px">
<el-form-item label="新的密码" prop="newPassword">
<inputStrength v-model="ruleForm.newPassword" strength placeholder="请输入新密码"></inputStrength>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<inputStrength v-model="ruleForm.confirmPassword" strength placeholder="请确认新密码"></inputStrength>
</el-form-item>
</el-form>
</el-card>
<el-descriptions title="配置项" :column="1" border class="descriptions">
<el-descriptions-item label="strength"> 是否显示密码强度默认为 false </el-descriptions-item>
<el-descriptions-item label="value"> 双向绑定的 value 使用示例v-model='passValue' </el-descriptions-item>
<el-descriptions-item label="常见场景">
<el-tag type="info">修改密码</el-tag> <el-tag type="info"></el-tag></el-descriptions-item
>
<el-descriptions-item label="参考地址">
<a href="https://github.com/zxcvbn-ts/zxcvbn" target="_blank">https://github.com/zxcvbn-ts/zxcvbn</a>
</el-descriptions-item>
</el-descriptions>
</div>
</PageWrapLayout>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import inputStrength from '@/components/InputStrength/index.vue'
const ruleForm = reactive({
newPassword: '',
confirmPassword: '',
})
</script>
<style lang="scss">
.m-pass-strength {
width: 100%;
height: 100%;
.descriptions {
margin-top: 50px;
}
}
</style>