diff --git a/package-lock.json b/package-lock.json index e3f10fbf..cd96c8ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1351,9 +1351,9 @@ }, "dependencies": { "core-js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.2.tgz", - "integrity": "sha512-FfApuSRgrR6G5s58casCBd9M2k+4ikuu4wbW6pJyYU7bd9zvFc9qf7vr5xmrZOhT9nn+8uwlH1oRR9jTnFoA3A==" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.0.tgz", + "integrity": "sha512-PyFBJaLq93FlyYdsndE5VaueA9K5cNB7CGzeCj191YYLhkQM0gdZR2SKihM70oF0wdqKSKClv/tEBOpoRmdOVQ==" } } }, @@ -3113,9 +3113,9 @@ } }, "ant-design-vue": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-1.7.2.tgz", - "integrity": "sha512-iVskTSG62OSiptyGQkvyhoeLlLMiqKtAOTcWZ7MmsMrj38h0TCpGtqSYS4/fwq4yYgyzloYSteBo8U8TrV99RA==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-1.7.3.tgz", + "integrity": "sha512-OuJBZmpcy32OvdvP7iW1EqdqU6l/dta1zcQ/U0fep7EGksPviVK7ssifI5QMZFQDDsti0dYQhCEOZWUG0Y/xxQ==", "requires": { "@ant-design/icons": "^2.1.1", "@ant-design/icons-vue": "^2.0.0", @@ -5382,7 +5382,7 @@ }, "component-classes": { "version": "1.2.6", - "resolved": "https://registry.npm.taobao.org/component-classes/download/component-classes-1.2.6.tgz", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=", "requires": { "component-indexof": "0.0.3" @@ -5396,7 +5396,7 @@ }, "component-indexof": { "version": "0.0.3", - "resolved": "https://registry.npm.taobao.org/component-indexof/download/component-indexof-0.0.3.tgz", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" }, "compressible": { @@ -6519,7 +6519,7 @@ }, "dom-closest": { "version": "0.2.0", - "resolved": "https://registry.npm.taobao.org/dom-closest/download/dom-closest-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz", "integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=", "requires": { "dom-matches": ">=1.0.1" @@ -6542,7 +6542,7 @@ }, "dom-matches": { "version": "2.0.0", - "resolved": "https://registry.npm.taobao.org/dom-matches/download/dom-matches-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz", "integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw=" }, "dom-scroll-into-view": { @@ -7844,6 +7844,11 @@ "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.25.1.tgz", "integrity": "sha512-8UPPTzfpHZRUtJl3dEcGMLYOygg8taJZpYTv1WaEkvzeI+X0JX9+agMJ+5Lu1EKtdt9dGq/9Gud+EnNRBaGl1A==" }, + "filepond-plugin-file-validate-type": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.5.tgz", + "integrity": "sha512-SsdYD6N+PDnetrPrBHfl13ZC9jRv5BuGdai5cORfANhuE/N32OwU/IclCNQ4qlpIa88hzqBg9dj6IINkiW+4HA==" + }, "filepond-plugin-image-preview": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/filepond-plugin-image-preview/-/filepond-plugin-image-preview-4.6.4.tgz", @@ -9135,7 +9140,7 @@ }, "intersperse": { "version": "1.0.0", - "resolved": "https://registry.npm.taobao.org/intersperse/download/intersperse-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/intersperse/-/intersperse-1.0.0.tgz", "integrity": "sha1-8lYfsc/vn1J3zDNHoiiGtDUaUYE=" }, "invariant": { @@ -10444,7 +10449,7 @@ }, "json2mq": { "version": "0.2.0", - "resolved": "https://registry.npm.taobao.org/json2mq/download/json2mq-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", "integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=", "requires": { "string-convert": "^0.2.0" @@ -14935,7 +14940,7 @@ }, "string-convert": { "version": "0.2.1", - "resolved": "https://registry.npm.taobao.org/string-convert/download/string-convert-0.2.1.tgz", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c=" }, "string-length": { diff --git a/package.json b/package.json index 1c436e68..db5d8ac5 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ "test:unit": "vue-cli-service test:unit" }, "dependencies": { - "ant-design-vue": "^1.7.2", + "ant-design-vue": "^1.7.3", "axios": "^0.21.1", "dayjs": "^1.10.4", "enquire.js": "^2.1.6", "filepond": "^4.25.1", + "filepond-plugin-file-validate-type": "^1.2.5", "filepond-plugin-image-preview": "^4.6.4", "flv.js": "^1.5.0", "halo-editor": "^2.8.2", diff --git a/src/components/Button/ReactiveButton.vue b/src/components/Button/ReactiveButton.vue index 6d1cad4d..0fcbfc8e 100644 --- a/src/components/Button/ReactiveButton.vue +++ b/src/components/Button/ReactiveButton.vue @@ -4,6 +4,8 @@ @click="handleClick" :icon="computedIcon" :loading="loading" + :size="size" + :block="block" >{{ computedText }} diff --git a/src/styles/animate.less b/src/styles/animate.less index e1e6c664..26203685 100644 --- a/src/styles/animate.less +++ b/src/styles/animate.less @@ -74,6 +74,20 @@ animation-name: fadeInUp; } +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.fadeIn { + animation-name: fadeIn; +} + .animated { -webkit-animation-duration: 1s; animation-duration: 1s; diff --git a/src/styles/style.less b/src/styles/style.less index 5d162d15..1b1a3483 100644 --- a/src/styles/style.less +++ b/src/styles/style.less @@ -10,31 +10,31 @@ padding: 18px 28px 28px 28px; box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1); - .halo-logo { - margin-bottom: 20px; - text-align: center; - - span { - vertical-align: text-bottom; - font-size: 38px; - display: inline-block; - font-weight: 600; - color: #1790fe; - background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%); - -webkit-text-fill-color: transparent; - -webkit-background-clip: text; - background-clip: text; - - small { - margin-left: 5px; - font-size: 35%; - } - } - } - .tip { cursor: pointer; margin-left: 0.5rem; float: right; } } + +.halo-logo { + margin-bottom: 20px; + text-align: center; + + span { + vertical-align: text-bottom; + font-size: 38px; + display: inline-block; + font-weight: 600; + color: #1790fe; + background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%); + -webkit-text-fill-color: transparent; + -webkit-background-clip: text; + background-clip: text; + + small { + margin-left: 5px; + font-size: 35%; + } + } +} diff --git a/src/views/interface/ThemeList.vue b/src/views/interface/ThemeList.vue index 87a85430..d2993305 100644 --- a/src/views/interface/ThemeList.vue +++ b/src/views/interface/ThemeList.vue @@ -149,7 +149,7 @@ -
+
- - - - - - - - - - + + +
+ + + 全新安装 + + + 数据导入 + + +
- + 管理员信息 +
+ - + - + - + - + - - - - - - + 站点信息 + + - + -
- +
- -
- 上一步 - 下一步 -
- + 安装 -
+ @callback="handleInstallCallback" + :loading="form.installing" + :errored="form.installErrored" + text="安装" + loadedText="安装成功" + erroredText="安装失败" + > + +
@@ -221,124 +209,149 @@ import { mapActions } from 'vuex' export default { data() { const confirmPasswordValidate = (rule, value, callback) => { - if (value !== this.installation.password) { + if (value !== this.form.model.password) { callback(new Error('确认密码与所输入的密码不一致')) } else { callback() } } return { - installation: {}, - stepCurrent: 0, - migrationData: null, - installing: false, + installationMode: 'new', // new or import + form: { + model: {}, + rules: { + username: [ + { required: true, message: '* 用户名不能为空', trigger: ['change'] }, + { max: 50, message: '* 用户名的字符长度不能超过 50', trigger: ['change'] }, + ], + nickname: [ + { required: true, message: '* 用户昵称不能为空', trigger: ['change'] }, + { max: 255, message: '* 用户昵称的字符长度不能超过 255', trigger: ['change'] }, + ], + email: [ + { required: true, message: '* 电子邮件地址不能为空', trigger: ['change'] }, + { max: 127, message: '* 电子邮件地址的字符长度不能超过 127', trigger: ['change'] }, + { + pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/g, + message: '* 电子邮件地址的格式不正确', + trigger: ['change'], + }, + ], + password: [ + { required: true, message: '* 密码不能为空', trigger: ['change'] }, + { min: 8, max: 100, message: '* 密码的字符长度必须在 8 - 100 之间', trigger: ['change'] }, + ], + confirmPassword: [ + { required: true, message: '* 确认密码不能为空', trigger: ['change'] }, + { validator: confirmPasswordValidate, trigger: ['change'] }, + ], + url: [{ required: true, message: '* 博客地址不能为空', trigger: ['change'] }], + title: [{ required: true, message: '* 博客标题不能为空', trigger: ['change'] }], + }, + installing: false, + installErrored: false, - generalRules: { - username: [ - { required: true, message: '* 用户名不能为空', trigger: ['change'] }, - { max: 50, message: '* 用户名的字符长度不能超过 50', trigger: ['change'] } - ], - nickname: [ - { required: true, message: '* 用户昵称不能为空', trigger: ['change'] }, - { max: 255, message: '* 用户昵称的字符长度不能超过 255', trigger: ['change'] } - ], - email: [ - { required: true, message: '* 电子邮件地址不能为空', trigger: ['change'] }, - { max: 127, message: '* 电子邮件地址的字符长度不能超过 127', trigger: ['change'] }, - { - pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/g, - message: '* 电子邮件地址的格式不正确', - trigger: ['change'] - } - ], - password: [ - { required: true, message: '* 密码不能为空', trigger: ['change'] }, - { min: 8, max: 100, message: '* 密码的字符长度必须在 8 - 100 之间', trigger: ['change'] } - ], - confirmPassword: [ - { required: true, message: '* 确认密码不能为空', trigger: ['change'] }, - { validator: confirmPasswordValidate, trigger: ['change'] } - ] + importing: false, + importErrored: false, + importData: null, }, - blogRules: { - url: [{ required: true, message: '* 博客地址不能为空', trigger: ['change'] }], - title: [{ required: true, message: '* 博客标题不能为空', trigger: ['change'] }] - } } }, beforeMount() { this.handleVerifyIsInstall() - this.$set(this.installation, 'url', window.location.protocol + '//' + window.location.host) + this.$set(this.form.model, 'url', window.location.protocol + '//' + window.location.host) + }, + computed: { + isInstallMode() { + return this.installationMode === 'new' + }, + isImportMode() { + return this.installationMode === 'import' + }, }, methods: { ...mapActions(['installCleanToken']), async handleVerifyIsInstall() { - await adminApi.isInstalled().then((response) => { - if (response.data.data) { - this.$router.push({ name: 'Login' }) + const response = await adminApi.isInstalled() + if (response.data.data) { + this.$router.push({ name: 'Login' }) + } + }, + handleInstall() { + this.$refs.installationForm.validate((valid) => { + if (valid) { + this.form.installing = true + this.installCleanToken(this.form.model) + .then((response) => { + this.$log.debug('Installation response', response) + }) + .catch(() => { + this.form.installErrored = true + }) + .finally(() => { + setTimeout(() => { + this.form.installing = false + }, 400) + }) } }) }, - handleNextStep() { - if (this.stepCurrent === 0) { - this.$refs.generalForm.validate((valid) => { - if (valid) { - this.stepCurrent++ - } else { - return false - } - }) - } else if (this.stepCurrent === 1) { - this.$refs.blogForm.validate((valid) => { - if (valid) { - this.stepCurrent++ - } else { - return false - } - }) + handleInstallCallback() { + if (this.form.installErrored) { + this.form.installErrored = false + } else { + this.$message.success('安装成功!') + this.$router.push({ name: 'Login' }) } }, - handleMigrationUpload(data) { + onImportUpload(data) { this.$log.debug('Selected data', data) - this.migrationData = data + this.form.importData = data return new Promise((resolve, reject) => { this.$log.debug('Handle uploading') resolve() }) }, - install() { - this.installCleanToken(this.installation) + handleImport() { + if (!this.form.importData) { + this.$message.warning('请先上传数据文件!') + return + } + this.form.importing = true + migrateApi + .migrate(this.form.importData) .then((response) => { - this.$log.debug('Installation response', response) - this.$message.success('安装成功!') - setTimeout(() => { - this.$router.push({ name: 'Login' }) - }, 200) + this.$log.debug('Migrated successfullly') + }) + .catch(() => { + this.form.importErrored = true }) .finally(() => { setTimeout(() => { - this.installing = false + this.form.importing = false }, 400) }) }, - handleInstall() { - this.installing = true - if (this.migrationData) { - const hide = this.$message.loading('数据导入中...', 0) - migrateApi - .migrate(this.migrationData) - .then((response) => { - this.$log.debug('Migrated successfullly') - this.$message.success('数据导入成功!') - this.install() - }) - .finally(() => { - hide() - }) + handleImportCallback() { + if (this.form.importErrored) { + this.form.importErrored = false } else { - this.install() + this.$message.success('导入成功!') + this.$router.push({ name: 'Login' }) } + }, + }, +} + + diff --git a/src/views/system/ToolList.vue b/src/views/system/ToolList.vue index 982ea77e..0fd0600a 100644 --- a/src/views/system/ToolList.vue +++ b/src/views/system/ToolList.vue @@ -103,7 +103,7 @@ diff --git a/src/views/user/Login.vue b/src/views/user/Login.vue index 18592f78..6520c23e 100644 --- a/src/views/user/Login.vue +++ b/src/views/user/Login.vue @@ -97,19 +97,19 @@ import { mapActions, mapGetters, mapMutations } from 'vuex' import LoginForm from '@/components/Login/LoginForm' export default { components: { - LoginForm + LoginForm, }, data() { return { resetPasswordButtonVisible: false, apiForm: { apiUrl: window.location.host, - visible: false - } + visible: false, + }, } }, computed: { - ...mapGetters({ defaultApiUrl: 'apiUrl' }) + ...mapGetters({ defaultApiUrl: 'apiUrl' }), }, beforeMount() { const _this = this @@ -124,14 +124,13 @@ export default { ...mapActions(['refreshUserCache', 'refreshOptionsCache']), ...mapMutations({ setApiUrl: 'SET_API_URL', - restoreApiUrl: 'RESTORE_API_URL' + restoreApiUrl: 'RESTORE_API_URL', }), - handleVerifyIsInstall() { - adminApi.isInstalled().then((response) => { - if (!response.data.data) { - this.$router.push({ name: 'Install' }) - } - }) + async handleVerifyIsInstall() { + const response = await adminApi.isInstalled() + if (!response.data.data) { + this.$router.push({ name: 'Install' }) + } }, onLoginSucceed() { // Refresh the user info @@ -156,7 +155,7 @@ export default { if (this.apiForm.visible) { this.apiForm.apiUrl = this.defaultApiUrl } - } - } + }, + }, }