From 01e87acfd19a4d48e5bf398d46b087ef2ff7ac75 Mon Sep 17 00:00:00 2001 From: molong Date: Mon, 26 Jan 2026 09:44:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 11 +- src/api/auth.js | 76 +-- src/assets/style/auth.scss | 510 ++++++++++---------- src/boot.js | 3 +- src/components/scEditor/UploadAdapter.js | 132 ++--- src/components/scEditor/index.vue | 258 +++++----- src/config/index.js | 2 +- src/config/upload.js | 22 +- src/hooks/useI18n.js | 2 +- src/i18n/index.js | 4 +- src/i18n/locales/en-US.js | 20 +- src/i18n/locales/zh-CN.js | 20 +- src/layouts/components/breadcrumb.vue | 0 src/layouts/components/menu.vue | 0 src/layouts/components/tags.vue | 0 src/layouts/components/userbar.vue | 0 src/layouts/index.vue | 60 ++- src/layouts/other/404.vue | 52 +- src/layouts/other/empty.vue | 58 ++- src/pages/home/index.vue | 8 + src/pages/ucenter/forgot-password/index.vue | 98 ++-- src/pages/ucenter/login/index.vue | 83 ++-- src/pages/ucenter/register/index.vue | 160 +++--- src/router/index.js | 28 +- src/stores/modules/i18n.js | 49 +- src/stores/modules/user.js | 10 +- src/utils/request.js | 8 +- src/utils/tool.js | 392 +++++++-------- 28 files changed, 1016 insertions(+), 1050 deletions(-) create mode 100644 src/layouts/components/breadcrumb.vue create mode 100644 src/layouts/components/menu.vue create mode 100644 src/layouts/components/tags.vue create mode 100644 src/layouts/components/userbar.vue create mode 100644 src/pages/home/index.vue diff --git a/src/App.vue b/src/App.vue index c7ecc3d..2c818cc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,12 @@ + + - - diff --git a/src/api/auth.js b/src/api/auth.js index 39d6891..6739a27 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -21,97 +21,97 @@ export default { }, users: { list: { - name: "获得用户列表", + name: '获得用户列表', get: async function (params) { - return await request.get('auth/users/index', { params }); + return await request.get('auth/users/index', { params }) }, }, add: { - name: "添加用户", + name: '添加用户', post: async function (params) { - return await request.post('auth/users/add', params); + return await request.post('auth/users/add', params) }, }, edit: { - name: "编辑用户", + name: '编辑用户', post: async function (params) { - return await request.put('auth/users/edit', params); + return await request.put('auth/users/edit', params) }, }, uppasswd: { - name: "修改密码", + name: '修改密码', post: async function (params) { - return await request.put('auth/users/passwd', params); + return await request.put('auth/users/passwd', params) }, }, uprole: { - name: "设置角色", + name: '设置角色', post: async function (params) { - return await request.put('auth/users/uprole', params); + return await request.put('auth/users/uprole', params) }, }, delete: { - name: "删除用户", + name: '删除用户', post: async function (params) { - return await request.delete('auth/users/delete', params); + return await request.delete('auth/users/delete', params) }, }, }, role: { list: { - name: "获得角色列表", + name: '获得角色列表', get: async function (params) { - return await request.get('auth/role/index', { params }); + return await request.get('auth/role/index', { params }) }, }, add: { - name: "添加角色", + name: '添加角色', post: async function (params) { - return await request.post('auth/role/add', params); + return await request.post('auth/role/add', params) }, }, edit: { - name: "编辑角色", + name: '编辑角色', post: async function (params) { - return await request.put('auth/role/edit', params); + return await request.put('auth/role/edit', params) }, }, auth: { - name: "角色授权", + name: '角色授权', post: async function (params) { - return await request.put('auth/role/auth', params); + return await request.put('auth/role/auth', params) }, }, delete: { - name: "删除角色", + name: '删除角色', post: async function (params) { - return await request.delete('auth/role/delete', params); + return await request.delete('auth/role/delete', params) }, }, }, department: { list: { - name: "获得部门列表", + name: '获得部门列表', get: async function (params) { - return await request.get('auth/department/index', { params }); + return await request.get('auth/department/index', { params }) }, }, add: { - name: "添加部门", + name: '添加部门', post: async function (params) { - return await request.post('auth/department/add', params); + return await request.post('auth/department/add', params) }, }, edit: { - name: "编辑部门", + name: '编辑部门', post: async function (params) { - return await request.put('auth/department/edit', params); + return await request.put('auth/department/edit', params) }, }, delete: { - name: "删除部门", + name: '删除部门', post: async function (params) { - return await request.delete('auth/department/delete', params); + return await request.delete('auth/department/delete', params) }, }, }, @@ -123,27 +123,27 @@ export default { }, }, list: { - name: "获取菜单", + name: '获取菜单', get: async function (params) { - return await request.get('auth/menu/index', { params }); + return await request.get('auth/menu/index', { params }) }, }, add: { - name: "添加菜单", + name: '添加菜单', post: async function (params) { - return await request.post('auth/menu/add', params); + return await request.post('auth/menu/add', params) }, }, edit: { - name: "编辑菜单", + name: '编辑菜单', post: async function (params) { - return await request.put('auth/menu/edit', params); + return await request.put('auth/menu/edit', params) }, }, delete: { - name: "删除菜单", + name: '删除菜单', post: async function (params) { - return await request.delete('auth/menu/delete', params); + return await request.delete('auth/menu/delete', params) }, }, }, diff --git a/src/assets/style/auth.scss b/src/assets/style/auth.scss index adb50b4..3b8849c 100644 --- a/src/assets/style/auth.scss +++ b/src/assets/style/auth.scss @@ -2,333 +2,333 @@ // Warm color palette with tech-inspired design :root { - --auth-primary: #ff6b35; - --auth-primary-light: #ff8c5a; - --auth-primary-dark: #e55a2b; - --auth-secondary: #ffb347; - --accent-orange: #ffa500; - --accent-coral: #ff7f50; - --accent-amber: #ffc107; + --auth-primary: #ff6b35; + --auth-primary-light: #ff8c5a; + --auth-primary-dark: #e55a2b; + --auth-secondary: #ffb347; + --accent-orange: #ffa500; + --accent-coral: #ff7f50; + --accent-amber: #ffc107; - --bg-gradient-start: #fff5f0; - --bg-gradient-end: #ffe8dc; - --card-bg: rgba(255, 255, 255, 0.95); + --bg-gradient-start: #fff5f0; + --bg-gradient-end: #ffe8dc; + --card-bg: rgba(255, 255, 255, 0.95); - --text-primary: #2d1810; - --text-secondary: #6b4423; - --text-muted: #a67c52; + --text-primary: #2d1810; + --text-secondary: #6b4423; + --text-muted: #a67c52; - --border-color: #ffd4b8; - --shadow-color: rgba(255, 107, 53, 0.15); + --border-color: #ffd4b8; + --shadow-color: rgba(255, 107, 53, 0.15); - --success: #28a745; - --warning: #ffc107; - --error: #dc3545; + --success: #28a745; + --warning: #ffc107; + --error: #dc3545; - --tech-blue: #007bff; - --tech-purple: #6f42c1; + --tech-blue: #007bff; + --tech-purple: #6f42c1; } .auth-container { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); - position: relative; - overflow: hidden; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + position: relative; + overflow: hidden; - // Tech pattern background - &::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: - radial-gradient(circle at 20% 50%, rgba(255, 107, 53, 0.03) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(255, 179, 71, 0.05) 0%, transparent 40%), - radial-gradient(circle at 40% 80%, rgba(255, 127, 80, 0.04) 0%, transparent 40%); - pointer-events: none; - } + // Tech pattern background + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + radial-gradient(circle at 20% 50%, rgba(255, 107, 53, 0.03) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 179, 71, 0.05) 0%, transparent 40%), radial-gradient(circle at 40% 80%, rgba(255, 127, 80, 0.04) 0%, transparent 40%); + pointer-events: none; + } - // Animated tech elements - &::after { - content: ''; - position: absolute; - width: 600px; - height: 600px; - background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); - border-radius: 50%; - top: -200px; - right: -200px; - animation: float 20s ease-in-out infinite; - pointer-events: none; - } + // Animated tech elements + &::after { + content: ''; + position: absolute; + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(255, 107, 53, 0.08) 0%, transparent 70%); + border-radius: 50%; + top: -200px; + right: -200px; + animation: float 20s ease-in-out infinite; + pointer-events: none; + } } @keyframes float { - 0%, 100% { - transform: translate(0, 0); - } - 50% { - transform: translate(-50px, 50px); - } + 0%, + 100% { + transform: translate(0, 0); + } + 50% { + transform: translate(-50px, 50px); + } } .auth-card { - width: 100%; - max-width: 440px; - background: var(--card-bg); - backdrop-filter: blur(20px); - border-radius: 24px; - padding: 48px 40px; - box-shadow: - 0 20px 60px var(--shadow-color), - 0 8px 24px rgba(0, 0, 0, 0.08); - position: relative; - z-index: 1; - margin: 20px; + width: 100%; + max-width: 440px; + background: var(--card-bg); + backdrop-filter: blur(20px); + border-radius: 24px; + padding: 48px 40px; + box-shadow: + 0 20px 60px var(--shadow-color), + 0 8px 24px rgba(0, 0, 0, 0.08); + position: relative; + z-index: 1; + margin: 20px; - // Tech accent line - &::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 80px; - height: 4px; - background: linear-gradient(90deg, var(--auth-primary), var(--auth-secondary)); - border-radius: 0 0 4px 4px; - } + // Tech accent line + &::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 80px; + height: 4px; + background: linear-gradient(90deg, var(--auth-primary), var(--auth-secondary)); + border-radius: 0 0 4px 4px; + } } .auth-header { - text-align: center; - margin-bottom: 40px; + text-align: center; + margin-bottom: 40px; - .auth-title { - font-size: 28px; - font-weight: 700; - color: var(--text-primary); - margin-bottom: 8px; - background: linear-gradient(135deg, var(--auth-primary-dark), var(--auth-primary)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - } + .auth-title { + font-size: 28px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; + background: linear-gradient(135deg, var(--auth-primary-dark), var(--auth-primary)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } - .auth-subtitle { - font-size: 14px; - color: var(--text-secondary); - line-height: 1.6; - } + .auth-subtitle { + font-size: 14px; + color: var(--text-secondary); + line-height: 1.6; + } } .auth-form { - .el-form-item { - margin-bottom: 24px; - } + .el-form-item { + margin-bottom: 24px; + } - .el-input { - --el-input-border-radius: 12px; - --el-input-border-color: var(--border-color); - --el-input-hover-border-color: var(--auth-primary-light); - --el-input-focus-border-color: var(--auth-primary); + .el-input { + --el-input-border-radius: 12px; + --el-input-border-color: var(--border-color); + --el-input-hover-border-color: var(--auth-primary-light); + --el-input-focus-border-color: var(--auth-primary); - .el-input__wrapper { - padding: 12px 16px; - box-shadow: 0 2px 8px rgba(255, 107, 53, 0.08); - transition: all 0.3s ease; + .el-input__wrapper { + padding: 12px 16px; + box-shadow: 0 2px 8px rgba(255, 107, 53, 0.08); + transition: all 0.3s ease; - &.is-focus { - box-shadow: 0 4px 16px rgba(255, 107, 53, 0.15); - } - } + &.is-focus { + box-shadow: 0 4px 16px rgba(255, 107, 53, 0.15); + } + } - .el-input__inner { - font-size: 14px; - color: var(--text-primary); - } - } + .el-input__inner { + font-size: 14px; + color: var(--text-primary); + } + } - .el-input__prefix { - color: var(--auth-primary); - font-size: 18px; - } + .el-input__prefix { + color: var(--auth-primary); + font-size: 18px; + } - .el-input__suffix { - color: var(--text-muted); - } + .el-input__suffix { + color: var(--text-muted); + } - .el-button { - --el-button-border-radius: 12px; - height: 48px; - font-size: 16px; - font-weight: 600; + .el-button { + --el-button-border-radius: 12px; + height: 48px; + font-size: 16px; + font-weight: 600; - &.el-button--primary { - background: linear-gradient(135deg, var(--auth-primary), var(--auth-primary-dark)); - border: none; - box-shadow: 0 8px 24px rgba(255, 107, 53, 0.35); - transition: all 0.3s ease; + &.el-button--primary { + background: linear-gradient(135deg, var(--auth-primary), var(--auth-primary-dark)); + border: none; + box-shadow: 0 8px 24px rgba(255, 107, 53, 0.35); + transition: all 0.3s ease; - &:hover { - background: linear-gradient(135deg, var(--auth-primary-light), var(--auth-primary)); - transform: translateY(-2px); - box-shadow: 0 12px 32px rgba(255, 107, 53, 0.45); - } + &:hover { + background: linear-gradient(135deg, var(--auth-primary-light), var(--auth-primary)); + transform: translateY(-2px); + box-shadow: 0 12px 32px rgba(255, 107, 53, 0.45); + } - &:active { - transform: translateY(0); - } - } - } + &:active { + transform: translateY(0); + } + } + } } .auth-links { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; - .remember-me { - .el-checkbox__label { - color: var(--text-secondary); - font-size: 14px; - } - } + .remember-me { + .el-checkbox__label { + color: var(--text-secondary); + font-size: 14px; + } + } - .forgot-password { - color: var(--auth-primary); - font-size: 14px; - text-decoration: none; - transition: color 0.3s ease; + .forgot-password { + color: var(--auth-primary); + font-size: 14px; + text-decoration: none; + transition: color 0.3s ease; - &:hover { - color: var(--auth-primary-dark); - } - } + &:hover { + color: var(--auth-primary-dark); + } + } } .auth-divider { - display: flex; - align-items: center; - margin: 32px 0; - color: var(--text-muted); - font-size: 13px; + display: flex; + align-items: center; + margin: 32px 0; + color: var(--text-muted); + font-size: 13px; - &::before, - &::after { - content: ''; - flex: 1; - height: 1px; - background: var(--border-color); - } + &::before, + &::after { + content: ''; + flex: 1; + height: 1px; + background: var(--border-color); + } - span { - padding: 0 16px; - } + span { + padding: 0 16px; + } } .auth-footer { - text-align: center; - margin-top: 24px; + text-align: center; + margin-top: 24px; - .auth-footer-text { - color: var(--text-secondary); - font-size: 14px; + .auth-footer-text { + color: var(--text-secondary); + font-size: 14px; - .auth-link { - color: var(--auth-primary); - text-decoration: none; - font-weight: 600; - margin-left: 4px; - transition: color 0.3s ease; + .auth-link { + color: var(--auth-primary); + text-decoration: none; + font-weight: 600; + margin-left: 4px; + transition: color 0.3s ease; - &:hover { - color: var(--auth-primary-dark); - } - } - } + &:hover { + color: var(--auth-primary-dark); + } + } + } } .tech-decoration { - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - pointer-events: none; - overflow: hidden; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + pointer-events: none; + overflow: hidden; - .tech-circle { - position: absolute; - border: 2px solid rgba(255, 107, 53, 0.1); - border-radius: 50%; - animation: pulse 4s ease-in-out infinite; - } + .tech-circle { + position: absolute; + border: 2px solid rgba(255, 107, 53, 0.1); + border-radius: 50%; + animation: pulse 4s ease-in-out infinite; + } - .tech-circle:nth-child(1) { - width: 300px; - height: 300px; - top: -150px; - left: -150px; - animation-delay: 0s; - } + .tech-circle:nth-child(1) { + width: 300px; + height: 300px; + top: -150px; + left: -150px; + animation-delay: 0s; + } - .tech-circle:nth-child(2) { - width: 200px; - height: 200px; - bottom: -100px; - right: -100px; - animation-delay: 1s; - } + .tech-circle:nth-child(2) { + width: 200px; + height: 200px; + bottom: -100px; + right: -100px; + animation-delay: 1s; + } - .tech-circle:nth-child(3) { - width: 150px; - height: 150px; - bottom: 20%; - left: -75px; - animation-delay: 2s; - } + .tech-circle:nth-child(3) { + width: 150px; + height: 150px; + bottom: 20%; + left: -75px; + animation-delay: 2s; + } } @keyframes pulse { - 0%, 100% { - opacity: 0.3; - transform: scale(1); - } - 50% { - opacity: 0.6; - transform: scale(1.05); - } + 0%, + 100% { + opacity: 0.3; + transform: scale(1); + } + 50% { + opacity: 0.6; + transform: scale(1.05); + } } // Responsive design @media (max-width: 768px) { - .auth-card { - padding: 40px 24px; - margin: 16px; - } + .auth-card { + padding: 40px 24px; + margin: 16px; + } - .auth-header { - .auth-title { - font-size: 24px; - } - } + .auth-header { + .auth-title { + font-size: 24px; + } + } } // Element Plus customizations for auth pages .el-form-item__error { - color: var(--error); - font-size: 12px; + color: var(--error); + font-size: 12px; } .el-message { - --el-message-bg: rgba(255, 255, 255, 0.98); - --el-message-border-color: var(--border-color); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + --el-message-bg: rgba(255, 255, 255, 0.98); + --el-message-border-color: var(--border-color); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); } diff --git a/src/boot.js b/src/boot.js index 97b84b0..f03da87 100644 --- a/src/boot.js +++ b/src/boot.js @@ -3,7 +3,6 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue' export default { install(app) { - for (let icon in AIcons) { app.component(`A${icon}`, AIcons[icon]) } @@ -11,5 +10,5 @@ export default { for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(`${key}`, component) } - } + }, } diff --git a/src/components/scEditor/UploadAdapter.js b/src/components/scEditor/UploadAdapter.js index f689603..af53009 100644 --- a/src/components/scEditor/UploadAdapter.js +++ b/src/components/scEditor/UploadAdapter.js @@ -1,144 +1,144 @@ export default class UploadAdapter { constructor(loader, options) { - this.loader = loader; - this.options = options; - this.timeout = 60000; // 60秒超时 + this.loader = loader + this.options = options + this.timeout = 60000 // 60秒超时 } upload() { return this.loader.file.then( (file) => new Promise((resolve, reject) => { - this._initRequest(); - this._initListeners(resolve, reject, file); - this._sendRequest(file); - this._initTimeout(reject); + this._initRequest() + this._initListeners(resolve, reject, file) + this._sendRequest(file) + this._initTimeout(reject) }), - ); + ) } abort() { if (this.xhr) { - this.xhr.abort(); + this.xhr.abort() } if (this.timeoutId) { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) } } _initRequest() { - const xhr = (this.xhr = new XMLHttpRequest()); + const xhr = (this.xhr = new XMLHttpRequest()) - xhr.open("POST", this.options.upload.uploadUrl, true); - xhr.responseType = "json"; + xhr.open('POST', this.options.upload.uploadUrl, true) + xhr.responseType = 'json' } _initListeners(resolve, reject, file) { - const xhr = this.xhr; - const loader = this.loader; - const genericErrorText = `Couldn't upload file: ${file.name}.`; + const xhr = this.xhr + const loader = this.loader + const genericErrorText = `Couldn't upload file: ${file.name}.` - xhr.addEventListener("error", () => { - console.error("[UploadAdapter] Upload error for file:", file.name); - reject(genericErrorText); - }); + xhr.addEventListener('error', () => { + console.error('[UploadAdapter] Upload error for file:', file.name) + reject(genericErrorText) + }) - xhr.addEventListener("abort", () => { - console.warn("[UploadAdapter] Upload aborted for file:", file.name); - reject(); - }); + xhr.addEventListener('abort', () => { + console.warn('[UploadAdapter] Upload aborted for file:', file.name) + reject() + }) - xhr.addEventListener("timeout", () => { - console.error("[UploadAdapter] Upload timeout for file:", file.name); - reject(`Upload timeout: ${file.name}. Please try again.`); - }); + xhr.addEventListener('timeout', () => { + console.error('[UploadAdapter] Upload timeout for file:', file.name) + reject(`Upload timeout: ${file.name}. Please try again.`) + }) - xhr.addEventListener("load", () => { - const response = xhr.response; + xhr.addEventListener('load', () => { + const response = xhr.response // 检查响应状态码 if (xhr.status >= 200 && xhr.status < 300) { if (!response) { - console.error("[UploadAdapter] Empty response for file:", file.name); - reject(genericErrorText); - return; + console.error('[UploadAdapter] Empty response for file:', file.name) + reject(genericErrorText) + return } // 检查业务状态码(假设 code=1 表示成功) if (response.code == 1 || response.code == undefined) { - const url = response.data?.url || response.data?.src; + const url = response.data?.url || response.data?.src if (!url) { - console.error("[UploadAdapter] No URL in response for file:", file.name, response); - reject("Upload succeeded but no URL returned"); - return; + console.error('[UploadAdapter] No URL in response for file:', file.name, response) + reject('Upload succeeded but no URL returned') + return } - resolve({ default: url }); + resolve({ default: url }) } else { - const errorMessage = response.message || genericErrorText; - console.error("[UploadAdapter] Upload failed for file:", file.name, "Error:", errorMessage); - reject(errorMessage); + const errorMessage = response.message || genericErrorText + console.error('[UploadAdapter] Upload failed for file:', file.name, 'Error:', errorMessage) + reject(errorMessage) } } else { - console.error("[UploadAdapter] HTTP error for file:", file.name, "Status:", xhr.status); - reject(`Server error (${xhr.status}): ${file.name}`); + console.error('[UploadAdapter] HTTP error for file:', file.name, 'Status:', xhr.status) + reject(`Server error (${xhr.status}): ${file.name}`) } - }); + }) // 上传进度监听 if (xhr.upload) { - xhr.upload.addEventListener("progress", (evt) => { + xhr.upload.addEventListener('progress', (evt) => { if (evt.lengthComputable) { - loader.uploadTotal = evt.total; - loader.uploaded = evt.loaded; + loader.uploadTotal = evt.total + loader.uploaded = evt.loaded } - }); + }) } } _initTimeout(reject) { // 清除之前的超时定时器(如果有) if (this.timeoutId) { - clearTimeout(this.timeoutId); + clearTimeout(this.timeoutId) } // 设置新的超时定时器 this.timeoutId = setTimeout(() => { if (this.xhr) { - this.xhr.abort(); - reject(new Error("Upload timeout")); + this.xhr.abort() + reject(new Error('Upload timeout')) } - }, this.timeout); + }, this.timeout) } _sendRequest(file) { // 设置请求超时 - this.xhr.timeout = this.timeout; + this.xhr.timeout = this.timeout // Set headers if specified. - const headers = this.options.upload.headers || {}; - const extendData = this.options.upload.extendData || {}; + const headers = this.options.upload.headers || {} + const extendData = this.options.upload.extendData || {} // Use the withCredentials flag if specified. - const withCredentials = this.options.upload.withCredentials || false; - const uploadName = this.options.upload.uploadName || "file"; + const withCredentials = this.options.upload.withCredentials || false + const uploadName = this.options.upload.uploadName || 'file' for (const headerName of Object.keys(headers)) { - this.xhr.setRequestHeader(headerName, headers[headerName]); + this.xhr.setRequestHeader(headerName, headers[headerName]) } - this.xhr.withCredentials = withCredentials; + this.xhr.withCredentials = withCredentials - const data = new FormData(); + const data = new FormData() for (const key of Object.keys(extendData)) { - data.append(key, extendData[key]); + data.append(key, extendData[key]) } - data.append(uploadName, file); + data.append(uploadName, file) - this.xhr.send(data); + this.xhr.send(data) } } export function UploadAdapterPlugin(editor) { - editor.plugins.get("FileRepository").createUploadAdapter = (loader) => { - return new UploadAdapter(loader, editor.config._config); - }; + editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { + return new UploadAdapter(loader, editor.config._config) + } } diff --git a/src/components/scEditor/index.vue b/src/components/scEditor/index.vue index 8be1b79..948c3e2 100644 --- a/src/components/scEditor/index.vue +++ b/src/components/scEditor/index.vue @@ -1,7 +1,6 @@ @@ -69,137 +68,119 @@ import { Underline, Undo, WordCount, -} from "ckeditor5"; -import { Ckeditor } from "@ckeditor/ckeditor5-vue"; -import { UploadAdapterPlugin } from "./UploadAdapter.js"; +} from 'ckeditor5' +import { Ckeditor } from '@ckeditor/ckeditor5-vue' +import { UploadAdapterPlugin } from './UploadAdapter.js' -import { ref, computed, watch } from "vue"; -import { useCurrentInstance } from "@/utils/tool"; +import { ref, computed, watch } from 'vue' +import { useCurrentInstance } from '@/utils/tool' -import coreTranslations from "ckeditor5/translations/zh-cn.js"; -import "ckeditor5/ckeditor5.css"; +import coreTranslations from 'ckeditor5/translations/zh-cn.js' +import 'ckeditor5/ckeditor5.css' -const { proxy } = useCurrentInstance(); +const { proxy } = useCurrentInstance() // 组件名称 defineOptions({ - name: "scCkeditor" -}); + name: 'scCkeditor', +}) // Props 定义 const props = defineProps({ modelValue: { type: String, - default: "", + default: '', }, placeholder: { type: String, - default: "请输入内容……", + default: '请输入内容……', }, toolbar: { type: String, - default: "basic", + default: 'basic', }, height: { type: String, - default: "400px", + default: '400px', }, disabled: { type: Boolean, default: false, }, -}); +}) // Emits 定义 -const emit = defineEmits(["update:modelValue"]); +const emit = defineEmits(['update:modelValue']) // 工具栏配置常量 const TOOLBARS = { full: [ - "sourceEditing", - "undo", - "redo", - "heading", - "style", - "|", - "superscript", - "subscript", - "removeFormat", - "bold", - "italic", - "underline", - "link", - "fontBackgroundColor", - "fontFamily", - "fontSize", - "fontColor", - "|", - "outdent", - "indent", - "alignment", - "bulletedList", - "numberedList", - "todoList", - "|", - "blockQuote", - "insertTable", - "imageInsert", - "mediaEmbed", - "highlight", - "horizontalLine", - "selectAll", - "showBlocks", - "specialCharacters", - "codeBlock", - "findAndReplace", + 'sourceEditing', + 'undo', + 'redo', + 'heading', + 'style', + '|', + 'superscript', + 'subscript', + 'removeFormat', + 'bold', + 'italic', + 'underline', + 'link', + 'fontBackgroundColor', + 'fontFamily', + 'fontSize', + 'fontColor', + '|', + 'outdent', + 'indent', + 'alignment', + 'bulletedList', + 'numberedList', + 'todoList', + '|', + 'blockQuote', + 'insertTable', + 'imageInsert', + 'mediaEmbed', + 'highlight', + 'horizontalLine', + 'selectAll', + 'showBlocks', + 'specialCharacters', + 'codeBlock', + 'findAndReplace', ], basic: [ - "sourceEditing", - "undo", - "redo", - "heading", - "|", - "removeFormat", - "bold", - "italic", - "underline", - "link", - "fontBackgroundColor", - "fontFamily", - "fontSize", - "fontColor", - "|", - "outdent", - "indent", - "alignment", - "bulletedList", - "numberedList", - "todoList", - "|", - "insertTable", - "imageInsert", - "mediaEmbed", + 'sourceEditing', + 'undo', + 'redo', + 'heading', + '|', + 'removeFormat', + 'bold', + 'italic', + 'underline', + 'link', + 'fontBackgroundColor', + 'fontFamily', + 'fontSize', + 'fontColor', + '|', + 'outdent', + 'indent', + 'alignment', + 'bulletedList', + 'numberedList', + 'todoList', + '|', + 'insertTable', + 'imageInsert', + 'mediaEmbed', ], - simple: [ - "undo", - "redo", - "heading", - "|", - "removeFormat", - "bold", - "italic", - "underline", - "link", - "fontBackgroundColor", - "fontFamily", - "fontSize", - "fontColor", - "|", - "insertTable", - "imageInsert", - "mediaEmbed", - ], -}; + simple: ['undo', 'redo', 'heading', '|', 'removeFormat', 'bold', 'italic', 'underline', 'link', 'fontBackgroundColor', 'fontFamily', 'fontSize', 'fontColor', '|', 'insertTable', 'imageInsert', 'mediaEmbed'], +} // 插件配置常量 const PLUGINS = [ @@ -265,16 +246,16 @@ const PLUGINS = [ Undo, WordCount, UploadAdapterPlugin, -]; +] // 响应式数据 -const editorData = ref(""); -const editorHeight = ref(props.height); -const editor = ClassicEditor; +const editorData = ref('') +const editorHeight = ref(props.height) +const editor = ClassicEditor // 编辑器配置 const editorConfig = computed(() => ({ - language: { ui: "zh-cn", content: "zh-cn" }, + language: { ui: 'zh-cn', content: 'zh-cn' }, translations: [coreTranslations], plugins: PLUGINS, toolbar: { @@ -283,27 +264,18 @@ const editorConfig = computed(() => ({ }, placeholder: props.placeholder, image: { - styles: ["alignLeft", "alignCenter", "alignRight"], - toolbar: [ - "imageTextAlternative", - "toggleImageCaption", - "|", - "imageStyle:alignLeft", - "imageStyle:alignCenter", - "imageStyle:alignRight", - "|", - "linkImage", - ], + styles: ['alignLeft', 'alignCenter', 'alignRight'], + toolbar: ['imageTextAlternative', 'toggleImageCaption', '|', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight', '|', 'linkImage'], }, mediaEmbed: { previewsInData: true, providers: [ { - name: "mp4", + name: 'mp4', url: /\.(mp4|avi|mov|flv|wmv|mkv)$/i, - html: match => { - const url = match["input"]; - return ('') + html: (match) => { + const url = match['input'] + return '' }, }, ], @@ -314,59 +286,57 @@ const editorConfig = computed(() => ({ style: { definitions: [ { - name: "Article category", - element: "h3", - classes: ["category"], + name: 'Article category', + element: 'h3', + classes: ['category'], }, { - name: "Info box", - element: "p", - classes: ["info-box"], + name: 'Info box', + element: 'p', + classes: ['info-box'], }, ], }, upload: { - uploadUrl: proxy?.$API?.common?.upload?.url || "", + uploadUrl: proxy?.$API?.common?.upload?.url || '', withCredentials: false, - extendData: { type: "images" }, + extendData: { type: 'images' }, headers: { - Authorization: "Bearer " + proxy?.$TOOL?.data?.get("TOKEN"), + Authorization: 'Bearer ' + proxy?.$TOOL?.data?.get('TOKEN'), }, }, -})); +})) // 监听 modelValue 变化 watch( () => props.modelValue, (newVal) => { - editorData.value = newVal ?? ""; + editorData.value = newVal ?? '' }, - { immediate: true } -); + { immediate: true }, +) // 监听 height 变化 watch( () => props.height, (newVal) => { - editorHeight.value = newVal; - } -); + editorHeight.value = newVal + }, +) // 移除图片宽高的正则替换函数 const stripImageDimensions = (html) => { return html.replace(/]*>/gi, (match) => { - return match - .replace(/width="[^"]*"/gi, "") - .replace(/height="[^"]*"/gi, ""); - }); -}; + return match.replace(/width="[^"]*"/gi, '').replace(/height="[^"]*"/gi, '') + }) +} // 失去焦点事件 - 移除图片的固定宽高,避免响应式布局问题 const onBlur = () => { - const cleanedData = stripImageDimensions(editorData.value); - editorData.value = cleanedData; - emit("update:modelValue", cleanedData); -}; + const cleanedData = stripImageDimensions(editorData.value) + editorData.value = cleanedData + emit('update:modelValue', cleanedData) +}