From efc953c094eba8a90b0ef1b2faaf6f71a85e92f1 Mon Sep 17 00:00:00 2001
From: AN <1983933789@qq.com>
Date: Tue, 8 Jul 2025 17:39:44 +0800
Subject: [PATCH 1/3] =?UTF-8?q?fix(projects):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E7=94=A8=E6=88=B7=E5=AF=BC=E5=85=A5=E7=BB=93=E6=9E=9C=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E6=9C=AA=E6=B8=B2=E6=9F=93=E6=A0=87=E7=AD=BE=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/views/system/user/modules/user-import-modal.vue | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/views/system/user/modules/user-import-modal.vue b/src/views/system/user/modules/user-import-modal.vue
index 22378489..0401b091 100644
--- a/src/views/system/user/modules/user-import-modal.vue
+++ b/src/views/system/user/modules/user-import-modal.vue
@@ -142,8 +142,12 @@ watch(visible, () => {
{{ $t('common.updateExisting') }}
+
- {{ message }}
+
+
+
+
From 90d32ee29a52fee6ffeea68297459adaf3245a02 Mon Sep 17 00:00:00 2001
From: AN <1983933789@qq.com>
Date: Tue, 8 Jul 2025 22:09:45 +0800
Subject: [PATCH 2/3] =?UTF-8?q?fix(utils):=20=E4=BF=AE=E5=A4=8DisNull?=
=?UTF-8?q?=E5=92=8CIsNotNull=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95=E6=BD=9C?=
=?UTF-8?q?=E5=9C=A8=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/hooks/common/form.ts | 3 ++-
src/utils/common.ts | 4 ++--
src/views/system/user/modules/user-operate-drawer.vue | 5 +++--
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/hooks/common/form.ts b/src/hooks/common/form.ts
index f6296a00..fa6ce6f4 100644
--- a/src/hooks/common/form.ts
+++ b/src/hooks/common/form.ts
@@ -2,6 +2,7 @@ import { ref, toValue } from 'vue';
import type { ComputedRef, Ref } from 'vue';
import type { FormInst } from 'naive-ui';
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
+import { isNull } from '@/utils/common';
import { $t } from '@/locales';
export function useFormRules() {
@@ -52,7 +53,7 @@ export function useFormRules() {
required: true,
trigger: ['input', 'blur'],
validator: (_rule: any, value: any) => {
- if (value === null || value === undefined || value === '') {
+ if (isNull(value) || (Array.isArray(value) && value.length === 0)) {
return new Error(message);
}
return true;
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 497a9b9a..1888dd27 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -78,12 +78,12 @@ export function humpToLine(str: string, line: string = '-') {
/** 判断是否为空 */
export function isNotNull(value: any) {
- return value !== undefined && value !== null && value !== '' && value !== 'undefined' && value !== 'null';
+ return value !== undefined && value !== null && value !== '';
}
/** 判断是否为空 */
export function isNull(value: any) {
- return value === undefined || value === null || value === '' || value === 'undefined' || value === 'null';
+ return value === undefined || value === null || value === '';
}
/** 判断是否为图片类型 */
diff --git a/src/views/system/user/modules/user-operate-drawer.vue b/src/views/system/user/modules/user-operate-drawer.vue
index 8bbdc419..68ecfa2c 100644
--- a/src/views/system/user/modules/user-operate-drawer.vue
+++ b/src/views/system/user/modules/user-operate-drawer.vue
@@ -65,14 +65,15 @@ function createDefaultModel(): Model {
};
}
-type RuleKey = Extract;
+type RuleKey = Extract;
const rules: Record = {
userName: [createRequiredRule($t('page.system.user.form.userName.required'))],
nickName: [createRequiredRule($t('page.system.user.form.nickName.required'))],
password: [{ ...patternRules.pwd, required: props.operateType === 'add' }],
phonenumber: [patternRules.phone],
- status: [createRequiredRule($t('page.system.user.form.status.required'))]
+ status: [createRequiredRule($t('page.system.user.form.status.required'))],
+ roleIds: [{ ...createRequiredRule('请选择角色'), type: 'array' }]
};
async function getUserInfo() {
From 566b2c2db8ced2725379ee0d6b79812b7c4c3725 Mon Sep 17 00:00:00 2001
From: xlsea
Date: Tue, 8 Jul 2025 23:05:56 +0800
Subject: [PATCH 3/3] =?UTF-8?q?fix(hooks):=20=E8=A7=A3=E5=86=B3=20streamsa?=
=?UTF-8?q?ver=20=E8=AE=BF=E9=97=AE=E4=B8=8D=E5=88=B0=20Github=20=E8=B5=84?=
=?UTF-8?q?=E6=BA=90=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
public/streamsaver/mitm.html | 179 +++++++++++++++++++++++++++++++++
public/streamsaver/sw.js | 132 ++++++++++++++++++++++++
src/hooks/business/download.ts | 1 +
3 files changed, 312 insertions(+)
create mode 100644 public/streamsaver/mitm.html
create mode 100644 public/streamsaver/sw.js
diff --git a/public/streamsaver/mitm.html b/public/streamsaver/mitm.html
new file mode 100644
index 00000000..f6d94e2f
--- /dev/null
+++ b/public/streamsaver/mitm.html
@@ -0,0 +1,179 @@
+
+
diff --git a/public/streamsaver/sw.js b/public/streamsaver/sw.js
new file mode 100644
index 00000000..f6cb4c3a
--- /dev/null
+++ b/public/streamsaver/sw.js
@@ -0,0 +1,132 @@
+/* eslint-disable */
+/* global self ReadableStream Response */
+
+self.addEventListener('install', () => {
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', event => {
+ event.waitUntil(self.clients.claim());
+});
+
+const map = new Map();
+
+// This should be called once per download
+// Each event has a dataChannel that the data will be piped through
+self.onmessage = event => {
+ // We send a heartbeat every x second to keep the
+ // service worker alive if a transferable stream is not sent
+ if (event.data === 'ping') {
+ return;
+ }
+
+ const data = event.data;
+ const downloadUrl =
+ data.url || `${self.registration.scope + Math.random()}/${typeof data === 'string' ? data : data.filename}`;
+ const port = event.ports[0];
+ const metadata = Array.from({ length: 3 }); // [stream, data, port]
+
+ metadata[1] = data;
+ metadata[2] = port;
+
+ // Note to self:
+ // old streamsaver v1.2.0 might still use `readableStream`...
+ // but v2.0.0 will always transfer the stream through MessageChannel #94
+ if (event.data.readableStream) {
+ metadata[0] = event.data.readableStream;
+ } else if (event.data.transferringReadable) {
+ port.onmessage = evt => {
+ port.onmessage = null;
+ metadata[0] = evt.data.readableStream;
+ };
+ } else {
+ metadata[0] = createStream(port);
+ }
+
+ map.set(downloadUrl, metadata);
+ port.postMessage({ download: downloadUrl });
+};
+
+function createStream(port) {
+ // ReadableStream is only supported by chrome 52
+ return new ReadableStream({
+ start(controller) {
+ // When we receive data on the messageChannel, we write
+ port.onmessage = ({ data }) => {
+ if (data === 'end') {
+ return controller.close();
+ }
+
+ if (data === 'abort') {
+ controller.error('Aborted the download');
+ return;
+ }
+
+ controller.enqueue(data);
+ };
+ },
+ cancel(reason) {
+ console.log('user aborted', reason);
+ port.postMessage({ abort: true });
+ }
+ });
+}
+
+self.onfetch = event => {
+ const url = event.request.url;
+
+ // this only works for Firefox
+ if (url.endsWith('/ping')) {
+ return event.respondWith(new Response('pong'));
+ }
+
+ const hijacke = map.get(url);
+
+ if (!hijacke) return null;
+
+ const [stream, data, port] = hijacke;
+
+ map.delete(url);
+
+ // Not comfortable letting any user control all headers
+ // so we only copy over the length & disposition
+ const responseHeaders = new Headers({
+ 'Content-Type': 'application/octet-stream; charset=utf-8',
+
+ // To be on the safe side, The link can be opened in a iframe.
+ // but octet-stream should stop it.
+ 'Content-Security-Policy': "default-src 'none'",
+ 'X-Content-Security-Policy': "default-src 'none'",
+ 'X-WebKit-CSP': "default-src 'none'",
+ 'X-XSS-Protection': '1; mode=block',
+ 'Cross-Origin-Embedder-Policy': 'require-corp'
+ });
+
+ const headers = new Headers(data.headers || {});
+
+ if (headers.has('Content-Length')) {
+ responseHeaders.set('Content-Length', headers.get('Content-Length'));
+ }
+
+ if (headers.has('Content-Disposition')) {
+ responseHeaders.set('Content-Disposition', headers.get('Content-Disposition'));
+ }
+
+ // data, data.filename and size should not be used anymore
+ if (data.size) {
+ console.warn('Depricated');
+ responseHeaders.set('Content-Length', data.size);
+ }
+
+ let fileName = typeof data === 'string' ? data : data.filename;
+ if (fileName) {
+ console.warn('Depricated');
+ // Make filename RFC5987 compatible
+ fileName = encodeURIComponent(fileName).replace(/['()]/g, escape).replace(/\*/g, '%2A');
+ responseHeaders.set('Content-Disposition', `attachment; filename*=UTF-8''${fileName}`);
+ }
+
+ event.respondWith(new Response(stream, { headers: responseHeaders }));
+
+ port.postMessage({ debug: 'Download started' });
+};
diff --git a/src/hooks/business/download.ts b/src/hooks/business/download.ts
index 210e1253..060b4579 100644
--- a/src/hooks/business/download.ts
+++ b/src/hooks/business/download.ts
@@ -51,6 +51,7 @@ export function useDownload() {
contentLength?: number
): Promise {
window.$loading?.endLoading();
+ StreamSaver.mitm = '/streamsaver/mitm.html?version=2.0.0';
const fileStream = StreamSaver.createWriteStream(filename, { size: contentLength });
if (window.WritableStream && readableStream?.pipeTo) {