From 98fc21995aa87a629b397c9683fb57eaac6d73f4 Mon Sep 17 00:00:00 2001 From: sudoooooo Date: Fri, 30 Aug 2024 12:01:39 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- README_EN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02093549..d475636b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@
-  **XIAOJUSURVEY**是一套轻量、安全的问卷系统,提供面向个人和企业的一站式产品级解决方案,用于构建各类问卷、考试、测评和复杂表单,快速满足各类线上调研场景。 +  **XIAOJUSURVEY**是一套轻量、安全的调研系统,提供面向个人和企业的一站式产品级解决方案,用于构建各类问卷、考试、测评和复杂表单,快速满足各类线上调研场景。   内部系统已沉淀 40+种题型,累积精选模板 100+,适用于市场调研、客户满意度调研、在线考试、投票、报道、测评等众多场景。数据能力上,经过上亿量级打磨,沉淀了分题统计、交叉分析、多渠道分析等在线报表能力,快速满足专业化分析。 diff --git a/README_EN.md b/README_EN.md index 84902ad4..aa0624ba 100644 --- a/README_EN.md +++ b/README_EN.md @@ -29,7 +29,7 @@
-  XIAOJUSURVEY is an open-source form builder and analytics platform to create questionnaires, exams, polls, quizzes, and analyze data online. +  XIAOJUSURVEY is an enterprises form builder and analytics platform to create questionnaires, exams, polls, quizzes, and analyze data online.   The internal system has accumulated over 40 question types and more than 100 selected templates, suitable for market research, customer satisfaction surveys, online exams, voting, reporting, evaluations, and many other scenarios. In terms of data capabilities, it has been honed through hundreds of millions of iterations, resulting in the ability to provide online reports with per-question statistics, cross-analysis, and multi-channel analysis, quickly meeting professional analysis needs. From 3d31245ae5512ff653a0863edfe46b70490e45c3 Mon Sep 17 00:00:00 2001 From: Stahsf <30379566+50431040@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:58:53 +0800 Subject: [PATCH 2/9] =?UTF-8?q?[Feature]:=20=E5=AF=86=E7=A0=81=E5=A4=8D?= =?UTF-8?q?=E6=9D=82=E5=BA=A6=E6=A3=80=E6=B5=8B=20(#407)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 密码复杂度检测 * chore: 改为服务端校验 --- server/src/enums/exceptionCode.ts | 1 + .../auth/__test/auth.controller.spec.ts | 13 ++++ .../auth/controllers/auth.controller.ts | 54 ++++++++++++- web/src/management/api/auth.js | 9 +++ web/src/management/pages/login/LoginPage.vue | 77 +++++++++++++++++-- 5 files changed, 148 insertions(+), 6 deletions(-) diff --git a/server/src/enums/exceptionCode.ts b/server/src/enums/exceptionCode.ts index 85cffaa7..6e139ad9 100644 --- a/server/src/enums/exceptionCode.ts +++ b/server/src/enums/exceptionCode.ts @@ -6,6 +6,7 @@ export enum EXCEPTION_CODE { USER_EXISTS = 2001, // 用户已存在 USER_NOT_EXISTS = 2002, // 用户不存在 USER_PASSWORD_WRONG = 2003, // 用户名或密码错误 + PASSWORD_INVALID = 2004, // 密码无效 NO_SURVEY_PERMISSION = 3001, // 没有问卷权限 SURVEY_STATUS_TRANSFORM_ERROR = 3002, // 问卷状态转换报错 SURVEY_TYPE_ERROR = 3003, // 问卷类型错误 diff --git a/server/src/modules/auth/__test/auth.controller.spec.ts b/server/src/modules/auth/__test/auth.controller.spec.ts index 9c705944..eb7e0ca7 100644 --- a/server/src/modules/auth/__test/auth.controller.spec.ts +++ b/server/src/modules/auth/__test/auth.controller.spec.ts @@ -82,6 +82,19 @@ describe('AuthController', () => { new HttpException('验证码不正确', EXCEPTION_CODE.CAPTCHA_INCORRECT), ); }); + + it('should throw HttpException with PASSWORD_INVALID code when password is invalid', async () => { + const mockUserInfo = { + username: 'testUser', + password: '无效的密码abc123', + captchaId: 'testCaptchaId', + captcha: 'testCaptcha', + }; + + await expect(controller.register(mockUserInfo)).rejects.toThrow( + new HttpException('密码无效', EXCEPTION_CODE.PASSWORD_INVALID), + ); + }); }); describe('login', () => { diff --git a/server/src/modules/auth/controllers/auth.controller.ts b/server/src/modules/auth/controllers/auth.controller.ts index 9d5d6d08..764e4d81 100644 --- a/server/src/modules/auth/controllers/auth.controller.ts +++ b/server/src/modules/auth/controllers/auth.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Post, Body, HttpCode } from '@nestjs/common'; +import { Controller, Post, Body, HttpCode, Get, Query } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { UserService } from '../services/user.service'; import { CaptchaService } from '../services/captcha.service'; @@ -7,6 +7,9 @@ import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { create } from 'svg-captcha'; import { ApiTags } from '@nestjs/swagger'; + +const passwordReg = /^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+$/; + @ApiTags('auth') @Controller('/api/auth') export class AuthController { @@ -28,6 +31,24 @@ export class AuthController { captcha: string; }, ) { + if (!userInfo.password) { + throw new HttpException('密码无效', EXCEPTION_CODE.PASSWORD_INVALID); + } + + if (userInfo.password.length < 6 || userInfo.password.length > 16) { + throw new HttpException( + '密码长度在 6 到 16 个字符', + EXCEPTION_CODE.PASSWORD_INVALID, + ); + } + + if (!passwordReg.test(userInfo.password)) { + throw new HttpException( + '密码只能输入数字、字母、特殊字符', + EXCEPTION_CODE.PASSWORD_INVALID, + ); + } + const isCorrect = await this.captchaService.checkCaptchaIsCorrect({ captcha: userInfo.captcha, id: userInfo.captchaId, @@ -162,4 +183,35 @@ export class AuthController { }, }; } + + /** + * 密码强度 + */ + @Get('register/password/strength') + @HttpCode(200) + async getPasswordStrength(@Query('password') password: string) { + const numberReg = /[0-9]/.test(password); + const letterReg = /[a-zA-Z]/.test(password); + const symbolReg = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password); + // 包含三种、且长度大于8 + if (numberReg && letterReg && symbolReg && password.length >= 8) { + return { + code: 200, + data: 'Strong', + }; + } + + // 满足任意两种 + if ([numberReg, letterReg, symbolReg].filter(Boolean).length >= 2) { + return { + code: 200, + data: 'Medium', + }; + } + + return { + code: 200, + data: 'Weak', + }; + } } diff --git a/web/src/management/api/auth.js b/web/src/management/api/auth.js index d654488a..502d4ae5 100644 --- a/web/src/management/api/auth.js +++ b/web/src/management/api/auth.js @@ -7,3 +7,12 @@ export const register = (data) => { export const login = (data) => { return axios.post('/auth/login', data) } + +/** 获取密码强度 */ +export const getPasswordStrength = (password) => { + return axios.get('/auth/register/password/strength', { + params: { + password + } + }) +} diff --git a/web/src/management/pages/login/LoginPage.vue b/web/src/management/pages/login/LoginPage.vue index 6564c9bf..3ebe51e5 100644 --- a/web/src/management/pages/login/LoginPage.vue +++ b/web/src/management/pages/login/LoginPage.vue @@ -27,6 +27,15 @@ + + + +
@@ -62,7 +71,7 @@ import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import 'element-plus/theme-chalk/src/message.scss' -import { login, register } from '@/management/api/auth' +import { getPasswordStrength, login, register } from '@/management/api/auth' import { refreshCaptcha as refreshCaptchaApi } from '@/management/api/captcha' import { CODE_MAP } from '@/management/api/base' import { useUserStore } from '@/management/stores/user' @@ -89,6 +98,55 @@ const formData = reactive({ captchaId: '' }) +// 每个滑块不同强度的颜色,索引0对应第一个滑块 +const strengthColor = reactive([ + { + Strong: '#67C23A', + Medium: '#ebb563', + Weak: '#f78989' + }, + { + Strong: '#67C23A', + Medium: '#ebb563', + Weak: '#2a598a' + }, + { + Strong: '#67C23A', + Medium: '#2a598a', + Weak: '#2a598a' + } +]) + +// 密码内容校验 +const passwordValidator = (_: any, value: any, callback: any) => { + if (!value) { + callback(new Error('请输入密码')) + passwordStrength.value = undefined + return + } + + if (value.length < 6 || value.length > 16) { + callback(new Error('长度在 6 到 16 个字符')) + passwordStrength.value = undefined + return + } + + if (!/^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+$/.test(value)) { + callback(new Error('只能输入数字、字母、特殊字符')) + passwordStrength.value = undefined + return + } + passwordStrengthHandle(value) + callback() +} + +const passwordStrengthHandle = async (value: string) => { + const res: any = await getPasswordStrength(value) + if (res.code === CODE_MAP.SUCCESS) { + passwordStrength.value = res.data + } +} + const rules = { name: [ { required: true, message: '请输入账号', trigger: 'blur' }, @@ -100,11 +158,8 @@ const rules = { } ], password: [ - { required: true, message: '请输入密码', trigger: 'blur' }, { - min: 8, - max: 16, - message: '长度在 8 到 16 个字符', + validator: passwordValidator, trigger: 'blur' } ], @@ -128,6 +183,7 @@ const pending = reactive({ const captchaImgData = ref('') const formDataRef = ref(null) +const passwordStrength = ref<'Strong' | 'Medium' | 'Weak'>() const submitForm = (type: 'login' | 'register') => { formDataRef.value.validate(async (valid: boolean) => { @@ -258,5 +314,16 @@ const refreshCaptcha = async () => { } } } + + .strength { + display: inline-block; + width: 20%; + height: 6px; + border-radius: 8px; + background: red; + &:not(:first-child) { + margin-left: 10px; + } + } } From 70c236c8799ff4623db2089109add425f95824e7 Mon Sep 17 00:00:00 2001 From: sudoooooo Date: Mon, 2 Sep 2024 17:36:18 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/management/hooks/useJumpLogicInfo.js | 1 - web/src/management/pages/login/LoginPage.vue | 14 ++-- web/src/management/styles/icon.scss | 9 ++- .../EditOptions/Options/OptionEditBar.vue | 8 +- web/src/render/components/QuestionWrapper.vue | 80 +++++++++---------- web/src/render/stores/question.js | 6 +- web/src/render/stores/survey.js | 1 - web/src/render/utils/index.js | 2 +- 8 files changed, 59 insertions(+), 62 deletions(-) diff --git a/web/src/management/hooks/useJumpLogicInfo.js b/web/src/management/hooks/useJumpLogicInfo.js index 2cd906ff..0f02cf0c 100644 --- a/web/src/management/hooks/useJumpLogicInfo.js +++ b/web/src/management/hooks/useJumpLogicInfo.js @@ -3,7 +3,6 @@ import { useQuestionInfo } from './useQuestionInfo' import { useEditStore } from '../stores/edit' import { storeToRefs } from 'pinia' - // 目标题的显示逻辑提示文案 export const useJumpLogicInfo = (field) => { const editStore = useEditStore() diff --git a/web/src/management/pages/login/LoginPage.vue b/web/src/management/pages/login/LoginPage.vue index 3ebe51e5..b17aa0b7 100644 --- a/web/src/management/pages/login/LoginPage.vue +++ b/web/src/management/pages/login/LoginPage.vue @@ -38,7 +38,7 @@
- +
@@ -71,6 +71,8 @@ import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import 'element-plus/theme-chalk/src/message.scss' +import { debounce as _debounce } from 'lodash-es' + import { getPasswordStrength, login, register } from '@/management/api/auth' import { refreshCaptcha as refreshCaptchaApi } from '@/management/api/captcha' import { CODE_MAP } from '@/management/api/base' @@ -159,8 +161,8 @@ const rules = { ], password: [ { - validator: passwordValidator, - trigger: 'blur' + validator: _debounce(passwordValidator, 500), + trigger: 'change' } ], captcha: [ @@ -311,18 +313,20 @@ const refreshCaptcha = async () => { cursor: pointer; :deep(> svg) { max-height: 40px; + width: 120px; + margin-left: 20px; } } } .strength { display: inline-block; - width: 20%; + width: 30%; height: 6px; border-radius: 8px; background: red; &:not(:first-child) { - margin-left: 10px; + margin-left: 8px; } } } diff --git a/web/src/management/styles/icon.scss b/web/src/management/styles/icon.scss index f79122d3..aae3e7f6 100644 --- a/web/src/management/styles/icon.scss +++ b/web/src/management/styles/icon.scss @@ -1,8 +1,9 @@ @font-face { - font-family: 'iconfont'; /* Project id 4263849 */ - src: url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.woff2?t=1723600417360') format('woff2'), - url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.woff?t=1723600417360') format('woff'), - url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.ttf?t=1723600417360') format('truetype'); + font-family: 'iconfont'; /* Project id 4263849 */ + src: + url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.woff2?t=1723600417360') format('woff2'), + url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.woff?t=1723600417360') format('woff'), + url('//at.alicdn.com/t/c/font_4263849_2re4gm4ryc3.ttf?t=1723600417360') format('truetype'); } .iconfont { diff --git a/web/src/materials/questions/widgets/EditOptions/Options/OptionEditBar.vue b/web/src/materials/questions/widgets/EditOptions/Options/OptionEditBar.vue index e9689c47..1e1405be 100644 --- a/web/src/materials/questions/widgets/EditOptions/Options/OptionEditBar.vue +++ b/web/src/materials/questions/widgets/EditOptions/Options/OptionEditBar.vue @@ -15,7 +15,7 @@ diff --git a/web/src/render/stores/question.js b/web/src/render/stores/question.js index 0f810353..3efa602b 100644 --- a/web/src/render/stores/question.js +++ b/web/src/render/stores/question.js @@ -189,14 +189,14 @@ export const useQuestionStore = defineStore('question', () => { return questionData.value[field].index } const addNeedHideFields = (fields) => { - fields.forEach(field => { - if(!needHideFields.value.includes(field)) { + fields.forEach((field) => { + if (!needHideFields.value.includes(field)) { needHideFields.value.push(field) } }) } const removeNeedHideFields = (fields) => { - needHideFields.value = needHideFields.value.filter(field => !fields.includes(field)) + needHideFields.value = needHideFields.value.filter((field) => !fields.includes(field)) } return { voteMap, diff --git a/web/src/render/stores/survey.js b/web/src/render/stores/survey.js index c0cc3c09..838ec827 100644 --- a/web/src/render/stores/survey.js +++ b/web/src/render/stores/survey.js @@ -41,7 +41,6 @@ export const useSurveyStore = defineStore('survey', () => { const formValues = ref({}) const whiteData = ref({}) const pageConf = ref([]) - const router = useRouter() const questionStore = useQuestionStore() diff --git a/web/src/render/utils/index.js b/web/src/render/utils/index.js index 7f9cf2b2..610e506f 100644 --- a/web/src/render/utils/index.js +++ b/web/src/render/utils/index.js @@ -29,4 +29,4 @@ export const formatLink = (url) => { return url } return `http://${url}` -} \ No newline at end of file +} From 9427e0efe56f454975501b10c491420cca4c0049 Mon Sep 17 00:00:00 2001 From: dayou <853094838@qq.com> Date: Mon, 2 Sep 2024 17:39:47 +0800 Subject: [PATCH 4/9] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E9=A1=B5=E5=9C=A8=E4=B8=8D=E5=90=8Celement=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=B8=8B=E8=A1=A8=E7=8E=B0=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20(#406)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 通过声明element最低版本来确定tab样式表现 * fix lint --- web/package.json | 2 +- .../pages/edit/modules/questionModule/CatalogPanel.vue | 2 -- .../pages/edit/modules/questionModule/SetterPanel.vue | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/web/package.json b/web/package.json index fcacfdeb..24781d81 100644 --- a/web/package.json +++ b/web/package.json @@ -23,7 +23,7 @@ "clipboard": "^2.0.11", "crypto-js": "^4.2.0", "echarts": "^5.5.0", - "element-plus": "^2.7.0", + "element-plus": "^2.8.1", "lodash-es": "^4.17.21", "moment": "^2.29.4", "nanoid": "^5.0.7", diff --git a/web/src/management/pages/edit/modules/questionModule/CatalogPanel.vue b/web/src/management/pages/edit/modules/questionModule/CatalogPanel.vue index 65f794cb..4cd5a3ed 100644 --- a/web/src/management/pages/edit/modules/questionModule/CatalogPanel.vue +++ b/web/src/management/pages/edit/modules/questionModule/CatalogPanel.vue @@ -22,8 +22,6 @@ const tabSelected = ref('0') height: 100%; box-shadow: none; border: none; - display: flex; - flex-direction: column; :deep(.el-tabs__nav) { width: 100%; } diff --git a/web/src/management/pages/edit/modules/questionModule/SetterPanel.vue b/web/src/management/pages/edit/modules/questionModule/SetterPanel.vue index 0b3540b9..03c33fb9 100644 --- a/web/src/management/pages/edit/modules/questionModule/SetterPanel.vue +++ b/web/src/management/pages/edit/modules/questionModule/SetterPanel.vue @@ -102,8 +102,6 @@ watch( width: 360px; height: 100%; border: none; - display: flex; - flex-direction: column; .el-tabs__nav { width: 100%; From 949a989dcfc1f16c73665d878f7a5695830d9b3d Mon Sep 17 00:00:00 2001 From: Jiangchunfu Date: Tue, 3 Sep 2024 15:54:42 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat(=E9=80=89=E9=A1=B9=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=89=A9=E5=B1=95)=EF=BC=9A=E9=80=89=E6=8B=A9=E7=B1=BB?= =?UTF-8?q?=E9=A2=98=E5=9E=8B=E5=A2=9E=E5=8A=A0=E9=80=89=E9=A1=B9=E6=8E=92?= =?UTF-8?q?=E5=88=97=E9=85=8D=E7=BD=AE=20(#403)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: add optimizeDeps packages * feat(选项设置扩展):选择类题型增加选项排列配置 * feat(选项设置扩展): 验收问题修复 --------- Co-authored-by: jiangchunfu --- .../questions/widgets/BaseChoice/index.jsx | 5 ++- .../questions/widgets/BaseChoice/style.scss | 32 ++++++++++++++++++- .../widgets/BinaryChoiceModule/meta.js | 30 ++++++++++++++++- .../widgets/CheckboxModule/index.jsx | 1 + .../questions/widgets/CheckboxModule/meta.js | 23 ++++++++++++- .../questions/widgets/RadioModule/meta.js | 30 ++++++++++++++++- .../questions/widgets/VoteModule/index.jsx | 2 +- .../questions/widgets/VoteModule/meta.js | 2 ++ web/vite.config.ts | 4 ++- 9 files changed, 120 insertions(+), 9 deletions(-) diff --git a/web/src/materials/questions/widgets/BaseChoice/index.jsx b/web/src/materials/questions/widgets/BaseChoice/index.jsx index 3aba05c8..fe41bbe1 100644 --- a/web/src/materials/questions/widgets/BaseChoice/index.jsx +++ b/web/src/materials/questions/widgets/BaseChoice/index.jsx @@ -97,7 +97,7 @@ export default defineComponent({ return (
-
+
{getOptions.map((item, index) => { return ( !item.hide && ( @@ -106,11 +106,10 @@ export default defineComponent({ style={this.choiceStyle} class={['choice-outer']} > -
+
{!/^\s*$/.test(item.text) && (
{{ selectMore: (scoped) => { diff --git a/web/src/materials/questions/widgets/CheckboxModule/meta.js b/web/src/materials/questions/widgets/CheckboxModule/meta.js index a6ffe433..24165e4f 100644 --- a/web/src/materials/questions/widgets/CheckboxModule/meta.js +++ b/web/src/materials/questions/widgets/CheckboxModule/meta.js @@ -1,5 +1,4 @@ import basicConfig from '@materials/questions/common/config/basicConfig' - const meta = { title: '多选', type: 'checkbox', @@ -83,6 +82,12 @@ const meta = { propType: Number, description: '最多选择数', defaultValue: 0 + }, + { + name: 'layout', + propType: String, + description: '排列方式', + defaultValue: 'vertical' } ], formConfig: [ @@ -92,6 +97,22 @@ const meta = { title: '选项配置', type: 'Customed', content: [ + { + label: '排列方式', + type: 'RadioGroup', + key: 'layout', + value: 'vertical', + options: [ + { + label: '竖排', + value: 'vertical' + }, + { + label: '横排', + value: 'horizontal' + }, + ] + }, { label: '至少选择数', type: 'InputNumber', diff --git a/web/src/materials/questions/widgets/RadioModule/meta.js b/web/src/materials/questions/widgets/RadioModule/meta.js index 1568ad14..bbdb3f42 100644 --- a/web/src/materials/questions/widgets/RadioModule/meta.js +++ b/web/src/materials/questions/widgets/RadioModule/meta.js @@ -71,9 +71,37 @@ const meta = { hash: '115020' } ] + }, + { + name: 'layout', + propType: String, + description: '排列方式', + defaultValue: 'vertical' } ], - formConfig: [basicConfig], + formConfig: [basicConfig, { + name: 'optionConfig', + title: '选项配置', + type: 'Customed', + content: [ + { + label: '排列方式', + type: 'RadioGroup', + key: 'layout', + value: 'vertical', + options: [ + { + label: '竖排', + value: 'vertical' + }, + { + label: '横排', + value: 'horizontal' + }, + ] + }, + ] + }], editConfigure: { optionEdit: { show: true diff --git a/web/src/materials/questions/widgets/VoteModule/index.jsx b/web/src/materials/questions/widgets/VoteModule/index.jsx index 8869e794..481a34c8 100644 --- a/web/src/materials/questions/widgets/VoteModule/index.jsx +++ b/web/src/materials/questions/widgets/VoteModule/index.jsx @@ -103,7 +103,7 @@ export default defineComponent({ return ( Date: Tue, 3 Sep 2024 16:47:33 +0800 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/materials/questions/widgets/VoteModule/index.jsx | 1 - web/src/materials/questions/widgets/VoteModule/meta.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/web/src/materials/questions/widgets/VoteModule/index.jsx b/web/src/materials/questions/widgets/VoteModule/index.jsx index 481a34c8..f3d611a1 100644 --- a/web/src/materials/questions/widgets/VoteModule/index.jsx +++ b/web/src/materials/questions/widgets/VoteModule/index.jsx @@ -103,7 +103,6 @@ export default defineComponent({ return ( Date: Tue, 3 Sep 2024 17:55:59 +0800 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/management/pages/login/LoginPage.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/management/pages/login/LoginPage.vue b/web/src/management/pages/login/LoginPage.vue index b17aa0b7..b4057941 100644 --- a/web/src/management/pages/login/LoginPage.vue +++ b/web/src/management/pages/login/LoginPage.vue @@ -38,8 +38,8 @@
- -
+ +
@@ -278,13 +278,14 @@ const refreshCaptcha = async () => { background: #fff; box-shadow: 4px 0 20px 0 rgba(82, 82, 102, 0.15); margin-top: -150px; + width: 580px; .button-group { margin-top: 40px; } .button { - width: 160px; + width: 200px; height: 40px; font-size: 14px; } From d3c2180ac82dbbdfd2e617bf68af0a6de27e31a7 Mon Sep 17 00:00:00 2001 From: Stahsf <30379566+50431040@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:37:03 +0800 Subject: [PATCH 8/9] =?UTF-8?q?test:=20=E5=AF=86=E7=A0=81=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E7=94=A8=E4=BE=8B=20(#411)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 密码复杂度检测 * chore: 改为服务端校验 * test: 修改用例 * test: 添加getPasswordStrength测试用例 --- .../auth/__test/auth.controller.spec.ts | 30 ++++++++++++++++++- .../_test/workspace.controller.spec.ts | 7 ++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/server/src/modules/auth/__test/auth.controller.spec.ts b/server/src/modules/auth/__test/auth.controller.spec.ts index eb7e0ca7..2aed3d04 100644 --- a/server/src/modules/auth/__test/auth.controller.spec.ts +++ b/server/src/modules/auth/__test/auth.controller.spec.ts @@ -92,7 +92,10 @@ describe('AuthController', () => { }; await expect(controller.register(mockUserInfo)).rejects.toThrow( - new HttpException('密码无效', EXCEPTION_CODE.PASSWORD_INVALID), + new HttpException( + '密码只能输入数字、字母、特殊字符', + EXCEPTION_CODE.PASSWORD_INVALID, + ), ); }); }); @@ -217,4 +220,29 @@ describe('AuthController', () => { expect(typeof result.data.img).toBe('string'); }); }); + + describe('password strength', () => { + it('it should return strong', async () => { + await expect( + controller.getPasswordStrength('abcd&1234'), + ).resolves.toEqual({ + code: 200, + data: 'Strong', + }); + }); + + it('it should return medium', async () => { + await expect(controller.getPasswordStrength('abc123')).resolves.toEqual({ + code: 200, + data: 'Medium', + }); + }); + + it('it should return weak', async () => { + await expect(controller.getPasswordStrength('123456')).resolves.toEqual({ + code: 200, + data: 'Weak', + }); + }); + }); }); diff --git a/server/src/modules/workspace/_test/workspace.controller.spec.ts b/server/src/modules/workspace/_test/workspace.controller.spec.ts index 73d84ab3..9431b6ce 100644 --- a/server/src/modules/workspace/_test/workspace.controller.spec.ts +++ b/server/src/modules/workspace/_test/workspace.controller.spec.ts @@ -245,8 +245,10 @@ describe('WorkspaceController', () => { const req = { user: { _id: new ObjectId() } }; const workspaceId = new ObjectId(); - const memberList = [{ workspaceId, userId: new ObjectId() }]; + const userId = new ObjectId(); + const memberList = [{ workspaceId, userId: userId }]; const workspaces = [{ _id: workspaceId, name: 'Test Workspace' }]; + const userList = [{ _id: userId, username: 'Test User' }]; jest .spyOn(workspaceService, 'findAllByUserId') @@ -254,6 +256,9 @@ describe('WorkspaceController', () => { jest .spyOn(workspaceMemberService, 'batchSearchByWorkspace') .mockResolvedValue(memberList as unknown as Array); + jest + .spyOn(userService, 'getUserListByIds') + .mockResolvedValue(userList as User[]); const result = await controller.getWorkspaceAndMember(req); From 43001a12c7ee29caa38390559542877b5375231d Mon Sep 17 00:00:00 2001 From: Jiangchunfu Date: Fri, 6 Sep 2024 15:51:12 +0800 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8=E4=B8=8A=E4=BC=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=20(#410)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复题目标题插入图片异常问题 * fix: 修改事件监听顺序,避免编辑图片百分比时重新渲染工具条而找不到点击的dom --------- Co-authored-by: jiangchunfu --- .../questions/widgets/TitleModules/TitleContent/index.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/materials/questions/widgets/TitleModules/TitleContent/index.jsx b/web/src/materials/questions/widgets/TitleModules/TitleContent/index.jsx index 0cba4d9a..b19b4a27 100644 --- a/web/src/materials/questions/widgets/TitleModules/TitleContent/index.jsx +++ b/web/src/materials/questions/widgets/TitleModules/TitleContent/index.jsx @@ -48,9 +48,9 @@ export default defineComponent({ watch(status, (v) => { if (v === 'edit') { - document.addEventListener('click', handleDocumentClick) + document.addEventListener('click', handleDocumentClick, {capture: true}) } else { - document.removeEventListener('click', handleDocumentClick) + document.removeEventListener('click', handleDocumentClick, {capture: true}) } }) @@ -96,7 +96,8 @@ export default defineComponent({ function handleDocumentClick(e) { const richEditorDOM = moduleTitleRef.value.querySelector('.rich-editor') - const isClickRichEditor = richEditorDOM?.contains(e.target) + const isUploadImage = e.target.type === 'file' && e.target.tagName.toLowerCase() === 'input' // 富文本上传图片点击事件触发到input file 元素上了, 该元素插入到body了 + const isClickRichEditor = richEditorDOM?.contains(e.target) || isUploadImage if (status.value === 'edit' && richEditorDOM && !isClickRichEditor) { // 监听编辑状态时点击非编辑区域