mirror of
https://github.com/ZiuChen/ClipboardManager.git
synced 2025-06-07 22:04:06 +08:00
feat: 添加多选功能 支持合并文本/图片/文件
This commit is contained in:
parent
9b172f684c
commit
cfe567b9fb
@ -15,7 +15,8 @@ const dbName = '_utools_clipboard_manager_storage'
|
||||
|
||||
const isMacOs = utools.isMacOs()
|
||||
const isWindows = utools.isWindows()
|
||||
const DBPath = `${isMacOs ? userDataPath : homePath}${isWindows ? '\\' : '/'}${dbName}`
|
||||
const sep = isWindows ? '\\' : '/'
|
||||
const DBPath = `${isMacOs ? userDataPath : homePath}${sep}${dbName}`
|
||||
|
||||
let globalImageOversize = false
|
||||
|
||||
@ -45,7 +46,7 @@ class DB {
|
||||
)
|
||||
this.updateDataBaseLocal()
|
||||
} catch (err) {
|
||||
utools.showNotification('读取剪切板出错' + err)
|
||||
utools.showNotification('读取剪切板出错: ' + err)
|
||||
return
|
||||
}
|
||||
return
|
||||
@ -66,7 +67,7 @@ class DB {
|
||||
// 更新文件数据
|
||||
fs.writeFileSync(this.path, JSON.stringify(dataBase || this.dataBase), (err) => {
|
||||
if (err) {
|
||||
utools.showNotification('写入剪切板出错' + err)
|
||||
utools.showNotification('写入剪切板出错: ' + err)
|
||||
return
|
||||
}
|
||||
})
|
||||
@ -186,6 +187,30 @@ const paste = () => {
|
||||
else utools.simulateKeyboardTap('v', 'ctrl')
|
||||
}
|
||||
|
||||
const createFile = (item) => {
|
||||
const tempPath = utools.getPath('temp')
|
||||
const folderPath = tempPath + sep + 'utools-clipboard-manager'
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
try {
|
||||
fs.mkdirSync(folderPath)
|
||||
} catch (err) {
|
||||
utools.showNotification('创建临时文件夹出错: ' + err)
|
||||
}
|
||||
}
|
||||
const { type } = item
|
||||
if (type === 'image') {
|
||||
const base64Data = item.data.replace(/^data:image\/\w+;base64,/, '') // remove the prefix
|
||||
const buffer = Buffer.from(base64Data, 'base64') // to Buffer
|
||||
const filePath = folderPath + sep + new Date().valueOf() + '.png'
|
||||
fs.writeFileSync(filePath, buffer)
|
||||
return filePath
|
||||
} else if (type === 'text') {
|
||||
const filePath = folderPath + sep + new Date().valueOf() + '.txt'
|
||||
fs.writeFileSync(filePath, item.data)
|
||||
return filePath
|
||||
}
|
||||
}
|
||||
|
||||
const db = new DB(DBPath)
|
||||
db.init()
|
||||
|
||||
@ -231,6 +256,7 @@ window.db = db
|
||||
window.copy = copy
|
||||
window.paste = paste
|
||||
window.remove = remove
|
||||
window.createFile = createFile
|
||||
window.openFile = utools.shellOpenPath
|
||||
window.openFileFolder = utools.shellShowItemInFolder
|
||||
window.getIcon = utools.getFileIcon
|
||||
|
@ -7,7 +7,7 @@
|
||||
@click.left="handleItemClick($event, item)"
|
||||
@click.right="handleItemClick($event, item)"
|
||||
@mouseover="handleMouseOver(index)"
|
||||
:class="{ active: index === activeIndex }"
|
||||
:class="{ active: index === activeIndex, select: selectItemList.indexOf(item) !== -1 }"
|
||||
>
|
||||
<div class="clip-info">
|
||||
<div class="clip-time">
|
||||
@ -29,7 +29,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clip-operate" v-show="activeIndex === index">
|
||||
<div class="clip-operate" v-show="activeIndex === index && !isMultiple">
|
||||
<template v-for="{ id, title, icon } of operation">
|
||||
<div
|
||||
v-if="
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="clip-count" v-show="activeIndex !== index">
|
||||
<div class="clip-count" v-show="isMultiple || activeIndex !== index">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
@ -66,9 +66,13 @@ const props = defineProps({
|
||||
fullData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isMultiple: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['onDataChange', 'onDataRemove'])
|
||||
const emit = defineEmits(['onDataChange', 'onDataRemove', 'onSelectItemAdd'])
|
||||
const isOverSizedContent = (item) => {
|
||||
const { type, data } = item
|
||||
if (type === 'text') {
|
||||
@ -77,18 +81,42 @@ const isOverSizedContent = (item) => {
|
||||
return JSON.parse(item.data).length >= 6
|
||||
}
|
||||
}
|
||||
const selectItemList = ref([])
|
||||
const emptySelectItemList = () => (selectItemList.value = [])
|
||||
defineExpose({
|
||||
selectItemList, // 暴露给 Main/Switch中的操作按钮以执行复制
|
||||
emptySelectItemList
|
||||
})
|
||||
watch(
|
||||
() => props.isMultiple,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
emptySelectItemList() // 退出多选状态 清空列表
|
||||
}
|
||||
}
|
||||
)
|
||||
const handleItemClick = (ev, item) => {
|
||||
const { button } = ev
|
||||
if (button === 0) {
|
||||
// 左键 复制后粘贴
|
||||
window.copy(item)
|
||||
window.paste()
|
||||
} else if (button === 2) {
|
||||
// 右键 仅复制
|
||||
window.copy(item)
|
||||
if (props.isMultiple === true) {
|
||||
const index = selectItemList.value.indexOf(item)
|
||||
console.log(index)
|
||||
if (index !== -1) {
|
||||
selectItemList.value.splice(index, 1) // 已经存在 点击移除
|
||||
} else {
|
||||
selectItemList.value.push(item) // 添加到已选列表中
|
||||
}
|
||||
emit('onSelectItemAdd')
|
||||
} else {
|
||||
const { button } = ev
|
||||
if (button === 0) {
|
||||
// 左键 复制后粘贴
|
||||
window.copy(item)
|
||||
window.paste()
|
||||
} else if (button === 2) {
|
||||
// 右键 仅复制
|
||||
window.copy(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleContentExpand = (item) => emit('onDataChange', item)
|
||||
const activeIndex = ref(0)
|
||||
const handleMouseOver = (index) => (activeIndex.value = index)
|
||||
const operation = [
|
||||
@ -127,10 +155,7 @@ const handleOperateClick = ({ id, item }) => {
|
||||
}
|
||||
}
|
||||
// 父组件中改变了引用类型的地址 故要用 getter返回
|
||||
watch(
|
||||
() => props.showList,
|
||||
() => (activeIndex.value = 0)
|
||||
)
|
||||
watch(props.showList, () => (activeIndex.value = 0))
|
||||
onMounted(() => {
|
||||
// 监听键盘事件
|
||||
document.addEventListener('keydown', (e) => {
|
||||
|
@ -16,6 +16,9 @@
|
||||
background-color: @text-bg-color-lighter;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
&.select {
|
||||
background-color: @text-bg-color-lighter;
|
||||
}
|
||||
.clip-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -34,10 +34,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.clip-switch-btn {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
.clip-switch-btn-list {
|
||||
margin-right: 5px;
|
||||
.clip-switch-btn {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 5px 2px;
|
||||
cursor: pointer;
|
||||
color: @text-color;
|
||||
background-color: @nav-bg-color;
|
||||
&:hover {
|
||||
background-color: @nav-hover-bg-color;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
&.clip-select-count {
|
||||
background-color: @primary-color;
|
||||
color: @bg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,26 @@
|
||||
></ClipFullData>
|
||||
<ClipSwitch ref="ClipSwitchRef" @onNavClick="handleNavClick">
|
||||
<template #SidePanel>
|
||||
<div v-show="!isSearchPanelExpand">
|
||||
<span class="clip-switch-btn"> 👆 </span>
|
||||
<span class="clip-switch-btn clip-search-btn" @click="handleSearchBtnClick"> 🔍 </span>
|
||||
<div class="clip-switch-btn-list" v-show="!isSearchPanelExpand">
|
||||
<span class="clip-switch-btn clip-select-count" v-show="isMultiple">
|
||||
{{ selectCount }}
|
||||
</span>
|
||||
<span class="clip-switch-btn" v-show="isMultiple" @click="handleMultiCopyBtnClick(false)"
|
||||
>📄 复制</span
|
||||
>
|
||||
<span class="clip-switch-btn" v-show="isMultiple" @click="handleMultiCopyBtnClick(true)"
|
||||
>📑 粘贴</span
|
||||
>
|
||||
<span class="clip-switch-btn" @click="handleMultiBtnClick">{{
|
||||
isMultiple ? '❌ 退出多选' : '👆'
|
||||
}}</span>
|
||||
<span
|
||||
class="clip-switch-btn clip-search-btn"
|
||||
v-show="!isMultiple"
|
||||
@click="handleSearchBtnClick"
|
||||
>
|
||||
🔍
|
||||
</span>
|
||||
</div>
|
||||
<ClipSearch
|
||||
v-show="isSearchPanelExpand"
|
||||
@ -23,8 +40,11 @@
|
||||
<div class="clip-break"></div>
|
||||
<div class="clip-empty-status" v-if="showList.length === 0">📪 无记录</div>
|
||||
<ClipItemList
|
||||
ref="ClipItemListRef"
|
||||
:showList="showList"
|
||||
:fullData="fullData"
|
||||
:isMultiple="isMultiple"
|
||||
@onSelectItemAdd="handleSelectItemAdd"
|
||||
@onDataChange="toggleFullData"
|
||||
@onDataRemove="handleDataRemove"
|
||||
>
|
||||
@ -42,13 +62,66 @@ import ClipFloatBtn from '../cpns/ClipFloatBtn.vue'
|
||||
|
||||
const isMultiple = ref(false)
|
||||
|
||||
const handleMultiBtnClick = () => {
|
||||
isMultiple.value = !isMultiple.value
|
||||
}
|
||||
|
||||
const isSearchPanelExpand = ref(false)
|
||||
|
||||
const handleSearchBtnClick = () => {
|
||||
// 展开搜索框
|
||||
isSearchPanelExpand.value = true
|
||||
nextTick(() => window.focus())
|
||||
}
|
||||
|
||||
const ClipItemListRef = ref(null)
|
||||
const selectCount = ref(0)
|
||||
|
||||
const handleSelectItemAdd = () => {
|
||||
// 每次添加选择的 item都将 count更新
|
||||
selectCount.value = ClipItemListRef.value.selectItemList.length
|
||||
}
|
||||
const handleMultiCopyBtnClick = (isPaste) => {
|
||||
const itemList = ClipItemListRef.value.selectItemList
|
||||
// 如果包含了图片/文件 则转为文件合并 否则仅合并文本
|
||||
const isMergeFile =
|
||||
itemList.filter((item) => item.type === 'image' || item.type === 'file').length !== 0
|
||||
if (isMergeFile) {
|
||||
const filePathArray = []
|
||||
itemList.map((item) => {
|
||||
const { type } = item
|
||||
if (type === 'text') {
|
||||
const textFile = window.createFile(item)
|
||||
filePathArray.push({
|
||||
path: textFile
|
||||
})
|
||||
} else if (type === 'image') {
|
||||
const imageFile = window.createFile(item)
|
||||
filePathArray.push({
|
||||
path: imageFile
|
||||
})
|
||||
} else {
|
||||
// file
|
||||
const files = JSON.parse(item.data)
|
||||
filePathArray.push(...files)
|
||||
}
|
||||
})
|
||||
window.copy({
|
||||
type: 'file',
|
||||
data: JSON.stringify(filePathArray)
|
||||
})
|
||||
} else {
|
||||
const result = itemList.map((item) => item.data).join('\n')
|
||||
window.copy({
|
||||
type: 'text',
|
||||
data: result
|
||||
})
|
||||
}
|
||||
isPaste && window.paste()
|
||||
ClipItemListRef.value.emptySelectItemList()
|
||||
isMultiple.value = false
|
||||
}
|
||||
|
||||
const GAP = 15 // 懒加载 每次添加的条数
|
||||
const offset = ref(0) // 懒加载 偏移量
|
||||
const filterText = ref('') // 搜索框绑定值
|
||||
|
Loading…
x
Reference in New Issue
Block a user