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',
|
||||
downloadWaiting = false;
|
||||
|
||||
function initialize(onStarted = null) {
|
||||
function initialize(onStarted = null, onChanged = null) {
|
||||
const notifyChanged = (item) => {
|
||||
if (typeof onChanged === 'function') {
|
||||
onChanged(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 下载配置
|
||||
electronDl({
|
||||
showBadge: false,
|
||||
@ -25,10 +31,12 @@ function initialize(onStarted = null) {
|
||||
if (typeof onStarted === 'function') {
|
||||
onStarted(item)
|
||||
}
|
||||
notifyChanged(item)
|
||||
},
|
||||
onCancel: (item) => {
|
||||
downloadManager.refresh(item.getSavePath())
|
||||
syncDownloadItems();
|
||||
notifyChanged(item)
|
||||
},
|
||||
onInterrupted: (item) => {
|
||||
downloadManager.refresh(item.getSavePath());
|
||||
@ -41,6 +49,7 @@ function initialize(onStarted = null) {
|
||||
syncDownloadItems();
|
||||
}
|
||||
});
|
||||
notifyChanged(item)
|
||||
},
|
||||
onProgress: (item) => {
|
||||
downloadManager.refresh(item.path);
|
||||
@ -49,11 +58,12 @@ function initialize(onStarted = null) {
|
||||
onCompleted: (item) => {
|
||||
downloadManager.refresh(item.path);
|
||||
syncDownloadItems();
|
||||
notifyChanged(item)
|
||||
}
|
||||
});
|
||||
|
||||
// IPC
|
||||
ipcMain.handle('downloadManager', async (event, {action, path}) => {
|
||||
ipcMain.handle('downloadManager', async (event, {action, path, msgId}) => {
|
||||
switch (action) {
|
||||
case "get": {
|
||||
return {
|
||||
@ -106,6 +116,20 @@ function initialize(onStarted = null) {
|
||||
shell.showItemInFolder(path);
|
||||
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) {
|
||||
ipcRenderer.on(action, function (event, args) {
|
||||
const listener = function (event, args) {
|
||||
callback(args);
|
||||
});
|
||||
};
|
||||
ipcRenderer.on(action, listener);
|
||||
return function () {
|
||||
ipcRenderer.removeListener(action, listener);
|
||||
};
|
||||
},
|
||||
listenOnce: function (action, callback) {
|
||||
ipcRenderer.once(action, function (event, args) {
|
||||
|
||||
6
electron/electron.js
vendored
6
electron/electron.js
vendored
@ -101,6 +101,12 @@ electronDown.initialize(() => {
|
||||
if (mainWindow) {
|
||||
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 fs = require("fs");
|
||||
const loger = require("electron-log");
|
||||
const Store = require('electron-store');
|
||||
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
|
||||
|
||||
@ -698,6 +698,7 @@ MAC地址
|
||||
Pro版
|
||||
上传
|
||||
下载
|
||||
在文件夹中显示
|
||||
个人
|
||||
今天
|
||||
任务
|
||||
|
||||
@ -39562,5 +39562,17 @@
|
||||
"fr": "Voulez-vous vraiment supprimer cette priorite ?",
|
||||
"id": "Yakin ingin menghapus prioritas ini?",
|
||||
"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 上传配置 )
|
||||
- 不支持文件夹直接拖入,需要逐个或压缩后上传
|
||||
- 不支持已删除文件 ID 复用(通过 sendfileid 发送已删除文件会失败)
|
||||
last_verified: v1.7.90
|
||||
last_verified: v1.8.89
|
||||
---
|
||||
|
||||
# 发送文件消息
|
||||
@ -56,6 +56,16 @@ last_verified: v1.7.90
|
||||
- 其他 → file(普通文件)
|
||||
- mp4 / mov / mkv 等视频会单独处理
|
||||
|
||||
## 桌面端下载与本地定位
|
||||
|
||||
Electron 桌面客户端的普通文件卡片右下角提供快捷图标:
|
||||
|
||||
- 下载图标:文件没有有效的本地下载记录,点击后立即下载,不再弹确认框
|
||||
- 加载图标:文件正在下载,完成前不能重复点击
|
||||
- 文件夹图标:文件已下载且本地文件仍存在,点击后在资源管理器或 Finder 中定位并选中文件
|
||||
|
||||
文件卡片主体仍保留原有的确认下载操作。清除下载历史,或移动、改名、删除本地文件后,文件夹图标会恢复为下载图标。图片和视频消息继续使用原有预览,不显示该快捷图标;浏览器端和移动端也不显示。
|
||||
|
||||
## 通过已有文件 ID 转发
|
||||
|
||||
走 sendfileid 可以把「我的文件」里现存文件以分享链接形式发到会话,不重复上传。
|
||||
|
||||
@ -19,6 +19,18 @@
|
||||
<div class="file-name">{{ msg.name }}</div>
|
||||
<div class="file-size">{{ $A.bytesToSize(msg.size) }}</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 v-if="msg.percentage" class="file-percentage">
|
||||
<span :style="fileStyle(msg.percentage)"></span>
|
||||
@ -30,8 +42,42 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
msgId: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
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: {
|
||||
viewFile() {
|
||||
this.$emit('viewFile');
|
||||
@ -40,6 +86,47 @@ export default {
|
||||
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) {
|
||||
if (percentage) {
|
||||
return {
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
<!--长文本-->
|
||||
<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"/>
|
||||
<!--位置-->
|
||||
|
||||
@ -1111,6 +1111,7 @@
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 10px 14px;
|
||||
border-radius: 3px;
|
||||
width: 220px;
|
||||
@ -1123,6 +1124,8 @@
|
||||
|
||||
.file-info {
|
||||
margin-left: 12px;
|
||||
min-width: 0;
|
||||
padding-right: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@ -1145,6 +1148,41 @@
|
||||
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