mirror of
https://github.com/kuaifan/dootask.git
synced 2026-09-10 14:08:57 +00:00
feat(messenger): add local file shortcut to chat cards
This commit is contained in:
parent
098bea3d5b
commit
2a7d635729
28
electron/electron-down.js
vendored
28
electron/electron-down.js
vendored
@ -12,7 +12,13 @@ let downloadWindow = null,
|
|||||||
downloadLanguageCode = 'zh',
|
downloadLanguageCode = 'zh',
|
||||||
downloadWaiting = false;
|
downloadWaiting = false;
|
||||||
|
|
||||||
function initialize(onStarted = null) {
|
function initialize(onStarted = null, onChanged = null) {
|
||||||
|
const notifyChanged = (item) => {
|
||||||
|
if (typeof onChanged === 'function') {
|
||||||
|
onChanged(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 下载配置
|
// 下载配置
|
||||||
electronDl({
|
electronDl({
|
||||||
showBadge: false,
|
showBadge: false,
|
||||||
@ -25,10 +31,12 @@ function initialize(onStarted = null) {
|
|||||||
if (typeof onStarted === 'function') {
|
if (typeof onStarted === 'function') {
|
||||||
onStarted(item)
|
onStarted(item)
|
||||||
}
|
}
|
||||||
|
notifyChanged(item)
|
||||||
},
|
},
|
||||||
onCancel: (item) => {
|
onCancel: (item) => {
|
||||||
downloadManager.refresh(item.getSavePath())
|
downloadManager.refresh(item.getSavePath())
|
||||||
syncDownloadItems();
|
syncDownloadItems();
|
||||||
|
notifyChanged(item)
|
||||||
},
|
},
|
||||||
onInterrupted: (item) => {
|
onInterrupted: (item) => {
|
||||||
downloadManager.refresh(item.getSavePath());
|
downloadManager.refresh(item.getSavePath());
|
||||||
@ -41,6 +49,7 @@ function initialize(onStarted = null) {
|
|||||||
syncDownloadItems();
|
syncDownloadItems();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
notifyChanged(item)
|
||||||
},
|
},
|
||||||
onProgress: (item) => {
|
onProgress: (item) => {
|
||||||
downloadManager.refresh(item.path);
|
downloadManager.refresh(item.path);
|
||||||
@ -49,11 +58,12 @@ function initialize(onStarted = null) {
|
|||||||
onCompleted: (item) => {
|
onCompleted: (item) => {
|
||||||
downloadManager.refresh(item.path);
|
downloadManager.refresh(item.path);
|
||||||
syncDownloadItems();
|
syncDownloadItems();
|
||||||
|
notifyChanged(item)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// IPC
|
// IPC
|
||||||
ipcMain.handle('downloadManager', async (event, {action, path}) => {
|
ipcMain.handle('downloadManager', async (event, {action, path, msgId}) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "get": {
|
case "get": {
|
||||||
return {
|
return {
|
||||||
@ -106,6 +116,20 @@ function initialize(onStarted = null) {
|
|||||||
shell.showItemInFolder(path);
|
shell.showItemInFolder(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "messageFileStatus": {
|
||||||
|
const {status} = downloadManager.getMessageFileStatus(msgId);
|
||||||
|
return {status};
|
||||||
|
}
|
||||||
|
|
||||||
|
case "showMessageFile": {
|
||||||
|
const file = downloadManager.getMessageFileStatus(msgId);
|
||||||
|
if (file.status !== 'available') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shell.showItemInFolder(file.path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
8
electron/electron-preload.js
vendored
8
electron/electron-preload.js
vendored
@ -54,9 +54,13 @@ contextBridge.exposeInMainWorld(
|
|||||||
},
|
},
|
||||||
|
|
||||||
listener: function (action, callback) {
|
listener: function (action, callback) {
|
||||||
ipcRenderer.on(action, function (event, args) {
|
const listener = function (event, args) {
|
||||||
callback(args);
|
callback(args);
|
||||||
});
|
};
|
||||||
|
ipcRenderer.on(action, listener);
|
||||||
|
return function () {
|
||||||
|
ipcRenderer.removeListener(action, listener);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
listenOnce: function (action, callback) {
|
listenOnce: function (action, callback) {
|
||||||
ipcRenderer.once(action, function (event, args) {
|
ipcRenderer.once(action, function (event, args) {
|
||||||
|
|||||||
6
electron/electron.js
vendored
6
electron/electron.js
vendored
@ -101,6 +101,12 @@ electronDown.initialize(() => {
|
|||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.webContents.send("openDownloadWindow", {})
|
mainWindow.webContents.send("openDownloadWindow", {})
|
||||||
}
|
}
|
||||||
|
}, () => {
|
||||||
|
BrowserWindow.getAllWindows().forEach(window => {
|
||||||
|
if (!window.isDestroyed()) {
|
||||||
|
window.webContents.send("downloadItemsChanged", {})
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
47
electron/lib/download-manager.js
vendored
47
electron/lib/download-manager.js
vendored
@ -1,4 +1,5 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
const loger = require("electron-log");
|
const loger = require("electron-log");
|
||||||
const Store = require('electron-store');
|
const Store = require('electron-store');
|
||||||
const utils = require("./utils");
|
const utils = require("./utils");
|
||||||
@ -95,6 +96,52 @@ class DownloadManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取聊天文件消息对应的下载状态。
|
||||||
|
*
|
||||||
|
* @param {number|string} msgId
|
||||||
|
* @returns {{status: 'available'|'downloading'|'missing', path?: string}}
|
||||||
|
*/
|
||||||
|
getMessageFileStatus(msgId) {
|
||||||
|
const targetId = parseInt(msgId, 10);
|
||||||
|
if (!targetId) {
|
||||||
|
return {status: 'missing'};
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = this.downloadHistory.filter(item => this.getMessageFileId(item) === targetId);
|
||||||
|
const downloading = items.some(item => item.state === 'progressing' && !item.paused);
|
||||||
|
if (downloading) {
|
||||||
|
return {status: 'downloading'};
|
||||||
|
}
|
||||||
|
|
||||||
|
const available = items.find(item => item.state === 'completed' && item.path && fs.existsSync(item.path));
|
||||||
|
return available ? {status: 'available', path: available.path} : {status: 'missing'};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从下载地址中识别聊天文件消息 ID。
|
||||||
|
*
|
||||||
|
* @param {Object} item
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getMessageFileId(item) {
|
||||||
|
const urls = [...(Array.isArray(item.urls) ? item.urls : []), item.url].filter(Boolean);
|
||||||
|
for (const value of urls) {
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
if (url.pathname.endsWith('/api/dialog/msg/download')) {
|
||||||
|
const msgId = parseInt(url.searchParams.get('msg_id'), 10);
|
||||||
|
if (msgId > 0) {
|
||||||
|
return msgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore malformed history URLs.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新下载项
|
* 更新下载项
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
|
|||||||
@ -698,6 +698,7 @@ MAC地址
|
|||||||
Pro版
|
Pro版
|
||||||
上传
|
上传
|
||||||
下载
|
下载
|
||||||
|
在文件夹中显示
|
||||||
个人
|
个人
|
||||||
今天
|
今天
|
||||||
任务
|
任务
|
||||||
|
|||||||
@ -39562,5 +39562,17 @@
|
|||||||
"fr": "Voulez-vous vraiment supprimer cette priorite ?",
|
"fr": "Voulez-vous vraiment supprimer cette priorite ?",
|
||||||
"id": "Yakin ingin menghapus prioritas ini?",
|
"id": "Yakin ingin menghapus prioritas ini?",
|
||||||
"ru": "Удалить этот приоритет?"
|
"ru": "Удалить этот приоритет?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "在文件夹中显示",
|
||||||
|
"zh": "",
|
||||||
|
"zh-CHT": "在資料夾中顯示",
|
||||||
|
"en": "Show in Folder",
|
||||||
|
"ko": "폴더에서 보기",
|
||||||
|
"ja": "フォルダーに表示",
|
||||||
|
"de": "Im Ordner anzeigen",
|
||||||
|
"fr": "Afficher dans le dossier",
|
||||||
|
"id": "Tampilkan di Folder",
|
||||||
|
"ru": "Показать в папке"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
2
public/language/web/de.js
vendored
2
public/language/web/de.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/en.js
vendored
2
public/language/web/en.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/fr.js
vendored
2
public/language/web/fr.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/id.js
vendored
2
public/language/web/id.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/ja.js
vendored
2
public/language/web/ja.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/key.js
vendored
2
public/language/web/key.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/ko.js
vendored
2
public/language/web/ko.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/ru.js
vendored
2
public/language/web/ru.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/zh-CHT.js
vendored
2
public/language/web/zh-CHT.js
vendored
File diff suppressed because one or more lines are too long
2
public/language/web/zh.js
vendored
2
public/language/web/zh.js
vendored
File diff suppressed because one or more lines are too long
@ -19,7 +19,7 @@ negative:
|
|||||||
- 单个文件大小受系统设置 file_upload_limit 限制(默认无单独限制,按 PHP 上传配置 )
|
- 单个文件大小受系统设置 file_upload_limit 限制(默认无单独限制,按 PHP 上传配置 )
|
||||||
- 不支持文件夹直接拖入,需要逐个或压缩后上传
|
- 不支持文件夹直接拖入,需要逐个或压缩后上传
|
||||||
- 不支持已删除文件 ID 复用(通过 sendfileid 发送已删除文件会失败)
|
- 不支持已删除文件 ID 复用(通过 sendfileid 发送已删除文件会失败)
|
||||||
last_verified: v1.7.90
|
last_verified: v1.8.89
|
||||||
---
|
---
|
||||||
|
|
||||||
# 发送文件消息
|
# 发送文件消息
|
||||||
@ -56,6 +56,16 @@ last_verified: v1.7.90
|
|||||||
- 其他 → file(普通文件)
|
- 其他 → file(普通文件)
|
||||||
- mp4 / mov / mkv 等视频会单独处理
|
- mp4 / mov / mkv 等视频会单独处理
|
||||||
|
|
||||||
|
## 桌面端下载与本地定位
|
||||||
|
|
||||||
|
Electron 桌面客户端的普通文件卡片右下角提供快捷图标:
|
||||||
|
|
||||||
|
- 下载图标:文件没有有效的本地下载记录,点击后立即下载,不再弹确认框
|
||||||
|
- 加载图标:文件正在下载,完成前不能重复点击
|
||||||
|
- 文件夹图标:文件已下载且本地文件仍存在,点击后在资源管理器或 Finder 中定位并选中文件
|
||||||
|
|
||||||
|
文件卡片主体仍保留原有的确认下载操作。清除下载历史,或移动、改名、删除本地文件后,文件夹图标会恢复为下载图标。图片和视频消息继续使用原有预览,不显示该快捷图标;浏览器端和移动端也不显示。
|
||||||
|
|
||||||
## 通过已有文件 ID 转发
|
## 通过已有文件 ID 转发
|
||||||
|
|
||||||
走 sendfileid 可以把「我的文件」里现存文件以分享链接形式发到会话,不重复上传。
|
走 sendfileid 可以把「我的文件」里现存文件以分享链接形式发到会话,不重复上传。
|
||||||
|
|||||||
@ -19,6 +19,18 @@
|
|||||||
<div class="file-name">{{ msg.name }}</div>
|
<div class="file-name">{{ msg.name }}</div>
|
||||||
<div class="file-size">{{ $A.bytesToSize(msg.size) }}</div>
|
<div class="file-size">{{ $A.bytesToSize(msg.size) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="$Electron"
|
||||||
|
class="file-local-action"
|
||||||
|
type="button"
|
||||||
|
:title="localActionTitle"
|
||||||
|
:aria-label="localActionTitle"
|
||||||
|
:disabled="localFileStatus === 'downloading'"
|
||||||
|
@click.stop="handleLocalAction">
|
||||||
|
<Loading v-if="localFileStatus === 'downloading'"/>
|
||||||
|
<Icon v-else-if="localFileStatus === 'available'" type="ios-folder-open-outline"/>
|
||||||
|
<Icon v-else type="md-download"/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="msg.percentage" class="file-percentage">
|
<div v-if="msg.percentage" class="file-percentage">
|
||||||
<span :style="fileStyle(msg.percentage)"></span>
|
<span :style="fileStyle(msg.percentage)"></span>
|
||||||
@ -30,8 +42,42 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
msgId: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
msg: Object,
|
msg: Object,
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localFileStatus: 'missing',
|
||||||
|
removeDownloadListener: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
localActionTitle() {
|
||||||
|
return this.localFileStatus === 'available' ? this.$L('在文件夹中显示') : this.$L('下载');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
msgId() {
|
||||||
|
this.refreshLocalFileStatus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.$Electron) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.refreshLocalFileStatus();
|
||||||
|
this.removeDownloadListener = $A.Electron.listener('downloadItemsChanged', () => {
|
||||||
|
this.refreshLocalFileStatus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (typeof this.removeDownloadListener === 'function') {
|
||||||
|
this.removeDownloadListener();
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
viewFile() {
|
viewFile() {
|
||||||
this.$emit('viewFile');
|
this.$emit('viewFile');
|
||||||
@ -40,6 +86,47 @@ export default {
|
|||||||
this.$emit('downFile');
|
this.$emit('downFile');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async refreshLocalFileStatus() {
|
||||||
|
if (!this.$Electron || !this.msgId) {
|
||||||
|
this.localFileStatus = 'missing';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await $A.Electron.sendAsync('downloadManager', {
|
||||||
|
action: 'messageFileStatus',
|
||||||
|
msgId: this.msgId,
|
||||||
|
});
|
||||||
|
this.localFileStatus = result?.status || 'missing';
|
||||||
|
} catch {
|
||||||
|
this.localFileStatus = 'missing';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleLocalAction() {
|
||||||
|
if (this.localFileStatus === 'downloading') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.localFileStatus === 'available') {
|
||||||
|
try {
|
||||||
|
const shown = await $A.Electron.sendAsync('downloadManager', {
|
||||||
|
action: 'showMessageFile',
|
||||||
|
msgId: this.msgId,
|
||||||
|
});
|
||||||
|
if (shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Refresh the action when the local file disappears or cannot be revealed.
|
||||||
|
}
|
||||||
|
this.localFileStatus = 'missing';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.localFileStatus = 'downloading';
|
||||||
|
this.$store.dispatch('downUrl', $A.apiUrl(`dialog/msg/download?msg_id=${this.msgId}`));
|
||||||
|
setTimeout(() => this.refreshLocalFileStatus(), 500);
|
||||||
|
},
|
||||||
|
|
||||||
fileStyle(percentage) {
|
fileStyle(percentage) {
|
||||||
if (percentage) {
|
if (percentage) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
<!--长文本-->
|
<!--长文本-->
|
||||||
<LongTextMsg v-else-if="msgData.type === 'longtext'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText" @downFile="downFile"/>
|
<LongTextMsg v-else-if="msgData.type === 'longtext'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText" @downFile="downFile"/>
|
||||||
<!--文件-->
|
<!--文件-->
|
||||||
<FileMsg v-else-if="msgData.type === 'file'" :msg="msgData.msg" @viewFile="viewFile" @downFile="downFile"/>
|
<FileMsg v-else-if="msgData.type === 'file'" :msg-id="msgData.id" :msg="msgData.msg" @viewFile="viewFile" @downFile="downFile"/>
|
||||||
<!--录音-->
|
<!--录音-->
|
||||||
<RecordMsg v-else-if="msgData.type === 'record'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText" @playRecord="playRecord"/>
|
<RecordMsg v-else-if="msgData.type === 'record'" :msgId="msgData.id" :msg="msgData.msg" @viewText="viewText" @playRecord="playRecord"/>
|
||||||
<!--位置-->
|
<!--位置-->
|
||||||
|
|||||||
@ -1111,6 +1111,7 @@
|
|||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
@ -1123,6 +1124,8 @@
|
|||||||
|
|
||||||
.file-info {
|
.file-info {
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -1145,6 +1148,41 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-local-action {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
bottom: 8px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: $primary-text-color;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $flow-status-end-color;
|
||||||
|
background-color: rgba($flow-status-end-color, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-loading {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user