V 3.0.1 LaiTool V3.0.1-preview.3
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-spin style="z-index: 100;" :show="softwareStore.spin.spinning">
|
||||
<n-spin style="z-index: 1000;" :show="softwareStore.spin.spinning">
|
||||
<RouterView></RouterView>
|
||||
<template #description> {{ softwareStore.spin.tip }} </template>
|
||||
</n-spin>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M8 9H4a2 2 0 0 0-2 2v12h2v-5h4v5h2V11a2 2 0 0 0-2-2zm-4 7v-5h4v5z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path d="M22 11h3v10h-3v2h8v-2h-3V11h3V9h-8v2z" fill="currentColor"></path>
|
||||
<path
|
||||
d="M14 23h-2V9h6a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-4zm0-7h4v-5h-4z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
|
||||
setup() {
|
||||
onMounted(async () => {})
|
||||
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div style="width: 100%; height: 100%">
|
||||
<webview style="width: 100%; height: 100%" src="https://api.laitool.cc" partition="persist:your-session-name"></webview>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
|
||||
setup() {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -74,7 +74,12 @@
|
||||
>
|
||||
</n-form-item>
|
||||
<n-form-item label="背景音乐" path="srtPath">
|
||||
<n-select v-model:value="reverseManageStore.selectBook.backgroundMusic" :options="backgroundMusicOptions" placeholder="请选择背景音乐"> </n-select>
|
||||
<n-select
|
||||
v-model:value="reverseManageStore.selectBook.backgroundMusic"
|
||||
:options="backgroundMusicOptions"
|
||||
placeholder="请选择背景音乐"
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
<n-form-item label="小说项目文件夹" path="bookFolderPath">
|
||||
<n-input
|
||||
@@ -99,7 +104,7 @@
|
||||
>
|
||||
</n-form-item>
|
||||
<div style="margin-left: 10px; display: flex; justify-content: flex-end">
|
||||
<n-button type="success" @click="AddOrModifyBook">{{
|
||||
<n-button :loading="loading" type="success" @click="AddOrModifyBook">{{
|
||||
type == 'add' ? '添加' : '保存'
|
||||
}}</n-button>
|
||||
</div>
|
||||
@@ -113,6 +118,7 @@ import { useMessage, NButton, NForm, NFormItem, NInput, NSelect, NIcon } from 'n
|
||||
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
|
||||
import { CloseSharp } from '@vicons/ionicons5'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { useSoftwareStore } from '../../../../../stores/software'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -128,7 +134,9 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let softwareStore = useSoftwareStore()
|
||||
let bookRef = ref(null)
|
||||
let loading = ref(false)
|
||||
let backgroundMusicOptions = ref([])
|
||||
// let data = ref(reverseManageStore.selectBook)
|
||||
let type = ref(props.type ? props.type : 'add')
|
||||
@@ -220,7 +228,9 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
debugger
|
||||
loading.value = true
|
||||
let res = await reverseManageStore.SaveSelectBook(cloneDeep(reverseManageStore.selectBook))
|
||||
loading.value = false
|
||||
// 判断当前的数据是不是有ID
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
@@ -279,8 +289,9 @@ export default defineComponent({
|
||||
SelectMP4File,
|
||||
AddOrModifyBook,
|
||||
bookRef,
|
||||
softwareStore,
|
||||
OpenFolder,
|
||||
backgroundMusicOptions
|
||||
backgroundMusicOptions,loading
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</n-dropdown>
|
||||
<n-divider vertical style="height: 30px" />
|
||||
<n-dropdown trigger="hover" :options="frameOptions" @select="handleSelect">
|
||||
<n-button :color="softwareStore.SoftColor.ORANGE" @click=""> 开始分镜 </n-button>
|
||||
<n-button :color="softwareStore.SoftColor.ORANGE" @click="ActionFrame"> 开始分镜 </n-button>
|
||||
</n-dropdown>
|
||||
<n-dropdown trigger="hover" :options="copywritingOptions" @select="handleSelect">
|
||||
<n-button
|
||||
@@ -102,18 +102,20 @@ import VideoCanvas from '../../VideoSubtitle/VideoCanvas.vue'
|
||||
import { SubtitleSavePositionType } from '../../../../../define/enum/waterMarkAndSubtitle'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
import Setting from './Setting.vue'
|
||||
import { BookType } from '../../../../../define/enum/bookEnum'
|
||||
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
|
||||
import TranslateSetting from '../../Setting/TranslateSetting.vue'
|
||||
import { TranslateType } from '../../../../../define/enum/translate'
|
||||
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
|
||||
import ImportWordAndSrt from '../../Original/Components/ImportWordAndSrt.vue'
|
||||
import GetWaterMaskRectangle from '../../Watermark/GetWaterMaskRectangle.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDropdown,
|
||||
NDivider
|
||||
NDivider,
|
||||
GetWaterMaskRectangle
|
||||
},
|
||||
|
||||
setup() {
|
||||
@@ -268,7 +270,10 @@ export default defineComponent({
|
||||
})
|
||||
return
|
||||
}
|
||||
let res_frame = await window.book.RemoveWatermark(reverseManageStore.selectBook.id)
|
||||
let res_frame = await window.book.RemoveWatermark(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
window.api.showGlobalMessageDialog(res_frame)
|
||||
}
|
||||
@@ -292,21 +297,45 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印位置
|
||||
*/
|
||||
async function GetWatermarkPosition() {
|
||||
// 开始获取水印位置
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.7
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () => h(GetWaterMaskRectangle, { width: dialogWidth, height: dialogHeight }),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
// 一些选择方式
|
||||
async function handleSelect(key) {
|
||||
switch (key) {
|
||||
case 'compute_frame':
|
||||
case 'compute_frame': // 计算分镜
|
||||
await ComputeStoryboard()
|
||||
break
|
||||
case 'framing':
|
||||
case 'framing': // 开始分镜
|
||||
await Framing()
|
||||
break
|
||||
case 'recognizing_setting':
|
||||
case 'recognizing_setting': // 文案位置设置
|
||||
await GetCopywritingSetting()
|
||||
break
|
||||
case 'mj_reverse':
|
||||
case 'watermark_position': // 水印位置设置
|
||||
await GetWatermarkPosition()
|
||||
break
|
||||
case 'mj_reverse': // MJ反推
|
||||
await ImageReversePrompt(BookType.MJ_REVERSE)
|
||||
break
|
||||
case 'sd_reverse':
|
||||
case 'sd_reverse': // SD反推
|
||||
await ImageReversePrompt(BookType.SD_REVERSE)
|
||||
break
|
||||
case 'remove_reverse':
|
||||
@@ -570,12 +599,17 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
async function ActionFrame() {
|
||||
message.info('请使用下面菜单中的按钮进行对应的操作')
|
||||
}
|
||||
|
||||
return {
|
||||
RetuenMain,
|
||||
AutoAction,
|
||||
ImportWordAndSrtFunc,
|
||||
reverseManageStore,
|
||||
softwareStore,
|
||||
ActionFrame,
|
||||
switchLogger,
|
||||
ComputeStoryboard,
|
||||
OpenSettingDialog,
|
||||
@@ -606,7 +640,7 @@ export default defineComponent({
|
||||
],
|
||||
copywritingOptions: [
|
||||
{
|
||||
label: '提取文案设置',
|
||||
label: '提取文案位置',
|
||||
key: 'recognizing_setting'
|
||||
},
|
||||
{
|
||||
@@ -616,8 +650,8 @@ export default defineComponent({
|
||||
],
|
||||
watermarkOptions: [
|
||||
{
|
||||
label: '去除水印设置',
|
||||
key: 'watermark_setting'
|
||||
label: '选择水印位置',
|
||||
key: 'watermark_position'
|
||||
},
|
||||
{
|
||||
label: '停止去除',
|
||||
|
||||
@@ -52,14 +52,14 @@ import {
|
||||
DuplicateOutline,
|
||||
GridOutline,
|
||||
RadioOutline,
|
||||
BookOutline
|
||||
BookOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
import CheckMachineId from '../Components/CheckMachineId.vue'
|
||||
import axios from 'axios'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import ShowMessage from './ShowMessage.vue'
|
||||
import { MD5 } from 'crypto-js'
|
||||
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
|
||||
import APIIcon from '../APIService/APIIcon.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -71,7 +71,8 @@ export default defineComponent({
|
||||
NIcon,
|
||||
ShowMessage,
|
||||
NSwitch,
|
||||
NButton
|
||||
NButton,
|
||||
APIIcon
|
||||
},
|
||||
setup() {
|
||||
let collapsed = ref(false)
|
||||
@@ -89,6 +90,7 @@ export default defineComponent({
|
||||
if (option.key == 'setting') return h(NIcon, null, { default: () => h(SettingsOutline) })
|
||||
if (option.key == 'gptCopywriting') return h(NIcon, null, { default: () => h(BookOutline) })
|
||||
if (option.key == 'book_management') return h(NIcon, null, { default: () => h(GridOutline) })
|
||||
if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) })
|
||||
if (option.key == 'backward_matrix')
|
||||
return h(NIcon, null, { default: () => h(DuplicateOutline) })
|
||||
if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) })
|
||||
@@ -452,21 +454,21 @@ export default defineComponent({
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// label: () =>
|
||||
// h(
|
||||
// RouterLink,
|
||||
// {
|
||||
// to: {
|
||||
// name: 'test_options'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// default: () => '测试操作'
|
||||
// }
|
||||
// ),
|
||||
// key: 'test_options'
|
||||
// },
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'lai_api'
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => 'API服务'
|
||||
}
|
||||
),
|
||||
key: 'lai_api'
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
|
||||
@@ -3,16 +3,26 @@
|
||||
<div style="width: 500px; position: relative">
|
||||
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
|
||||
<n-form-item label="人物名称" path="label">
|
||||
<n-input style="width: 400px;" v-model:value="characterData.label" placeholder="请输入人物名称" />
|
||||
<n-input
|
||||
style="width: 400px"
|
||||
v-model:value="characterData.label"
|
||||
placeholder="请输入人物名称"
|
||||
/>
|
||||
<n-checkbox style="margin-left: 20px" v-model:checked="characterData.isShow"
|
||||
>显示</n-checkbox
|
||||
>
|
||||
<n-image
|
||||
style="position: absolute; left: 520px; top: 0"
|
||||
width="120"
|
||||
height="120"
|
||||
:src="png_base64 ? png_base64 : characterData.show_image"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column"
|
||||
>
|
||||
<n-image
|
||||
width="120"
|
||||
height="120"
|
||||
:src="png_base64 ? png_base64 : characterData.show_image"
|
||||
/>
|
||||
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
|
||||
>本地上传图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="人物别名(可多个)">
|
||||
<n-dynamic-tags type="success" v-model:value="alias_tags" />
|
||||
@@ -36,7 +46,7 @@
|
||||
style="margin-top: 10px"
|
||||
@click="GenerateCharacterImage"
|
||||
:loading="imageLoading"
|
||||
>生成图片</n-button
|
||||
>SD生成图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
@@ -188,7 +198,6 @@ export default defineComponent({
|
||||
await window.mj.SaveTagPropertyData(
|
||||
[JSON.stringify(characterData.value), 'character_tags'],
|
||||
(value) => {
|
||||
|
||||
console.log(value)
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
@@ -280,7 +289,6 @@ export default defineComponent({
|
||||
}
|
||||
imageLoading.value = true
|
||||
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
|
||||
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
imageLoading.value = false
|
||||
@@ -298,8 +306,40 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择本地图片
|
||||
*/
|
||||
async function SelecImage() {
|
||||
await window.api.SelectFile(
|
||||
[
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'png',
|
||||
'gif',
|
||||
'bmp',
|
||||
'tiff',
|
||||
'tif',
|
||||
'webp',
|
||||
'svg',
|
||||
'raw',
|
||||
'cr2',
|
||||
'nef',
|
||||
'arw'
|
||||
],
|
||||
(value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
png_base64.value = null
|
||||
characterData.value.show_image = value.value
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
characterData,
|
||||
SelecImage,
|
||||
alias_tags,
|
||||
SaveCharacterTag,
|
||||
TranslatePrompt,
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<n-form label-placement="top" ref="formRef" :rules="rules" :model="styleData">
|
||||
<n-form-item label="风格名称" path="label">
|
||||
<n-input v-model:value="styleData.label" placeholder="请输入风格名称" />
|
||||
<n-image
|
||||
style="position: absolute; left: 520px; top: 0"
|
||||
width="120"
|
||||
height="120"
|
||||
:src="png_base64 ? png_base64 : styleData.show_image"
|
||||
/>
|
||||
<div style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column">
|
||||
<n-image width="120" height="120" :src="png_base64 ? png_base64 : styleData.show_image" />
|
||||
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
|
||||
>本地上传图片</n-button
|
||||
>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="风格提示词描述(中文)">
|
||||
<n-input
|
||||
@@ -29,7 +29,7 @@
|
||||
style="margin-top: 10px"
|
||||
@click="GenerateStyleImage"
|
||||
:loading="imageLoading"
|
||||
>生成图片</n-button
|
||||
>SD生成图片</n-button
|
||||
>
|
||||
</template>
|
||||
使用SD出图模式生成风格图片,提示词为1gril加上风格提示词
|
||||
@@ -149,6 +149,9 @@ export default defineComponent({
|
||||
return
|
||||
}
|
||||
styleData.value['type'] = 'style_main'
|
||||
styleData.value['show_image'] = styleData.value.show_image
|
||||
? styleData.value.show_image.split('?t')[0]
|
||||
: null
|
||||
// 直接保存
|
||||
// 开始保存
|
||||
await window.mj.SaveTagPropertyData(
|
||||
@@ -244,6 +247,7 @@ export default defineComponent({
|
||||
}
|
||||
imageLoading.value = true
|
||||
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
|
||||
debugger
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
imageLoading.value = false
|
||||
@@ -251,7 +255,8 @@ export default defineComponent({
|
||||
}
|
||||
if (value.data && value.data.length > 0) {
|
||||
let d = value.data[0]
|
||||
styleData.value.show_image = d.image_path.replaceAll('\\', '/')
|
||||
styleData.value.show_image =
|
||||
d.image_path.replaceAll('\\', '/') + '?t=' + new Date().getTime()
|
||||
png_base64.value = 'data:image/png;base64,' + d.base64
|
||||
console.log(styleData.value.show_image)
|
||||
imageLoading.value = false
|
||||
@@ -261,8 +266,52 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择本地的图片应用到风格图片
|
||||
*/
|
||||
async function SelecImage() {
|
||||
await window.api.SelectFile(
|
||||
[
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'png',
|
||||
'gif',
|
||||
'bmp',
|
||||
'tiff',
|
||||
'tif',
|
||||
'webp',
|
||||
'svg',
|
||||
'raw',
|
||||
'cr2',
|
||||
'nef',
|
||||
'arw'
|
||||
],
|
||||
(value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
// 读取文件并转换为Base64
|
||||
fetch(value.value)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
png_base64.value = reader.result
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
// 更新显示的图片
|
||||
|
||||
png_base64.value = null
|
||||
styleData.value.show_image = value.value + '?t=' + new Date().getTime()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
styleData,
|
||||
SelecImage,
|
||||
SaveStyleTag,
|
||||
loading,
|
||||
imageLoading,
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
fit="cover"
|
||||
width="120"
|
||||
height="120"
|
||||
lazy
|
||||
/>
|
||||
<n-button
|
||||
text
|
||||
|
||||
@@ -760,7 +760,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 筛选下面被锁定的数据
|
||||
tempData = tempData.filter((item) => item.imageLock == false)
|
||||
tempData = tempData.filter((item) => item.imageLock == null || item.imageLock == false)
|
||||
|
||||
if (tempData.length <= 0) {
|
||||
message.error('下面的数据都被锁定,无法生图,请检查')
|
||||
|
||||
@@ -220,10 +220,10 @@ export default defineComponent({
|
||||
* 生成所有的图片
|
||||
*/
|
||||
async function GenerateImageAll() {
|
||||
debugger
|
||||
let tmpData = cloneDeep(toRaw(data.value))
|
||||
tmpData = tmpData.filter((item) => {
|
||||
// return item.outImagePath == null || item.outImagePath == ''
|
||||
return !item.imageLoak
|
||||
return item.imageLock == null || item.imageLock == false
|
||||
})
|
||||
|
||||
if (tmpData.length == 0) {
|
||||
@@ -280,12 +280,24 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
// 重置GPT提示词
|
||||
async function ResetGPTPrompt() {
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
data.value[i].gpt_prompt = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 图片数据重置
|
||||
async function ResetImage() {
|
||||
// 循环data删除数据
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
data.value[i].outImagePath = null
|
||||
data.value[i].subImagePath = []
|
||||
data.value[i].imageLock = false
|
||||
// 判断是不是有mj_message,有的话删除
|
||||
if (data.value[i].mj_message) {
|
||||
data.value[i].mj_message = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +345,9 @@ export default defineComponent({
|
||||
await ResetPrompt()
|
||||
} else if (value == 'resetImage') {
|
||||
await ResetImage()
|
||||
} else if (value == 'resetGPTPrompt') {
|
||||
// 重置GPT提示词
|
||||
await ResetGPTPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +355,7 @@ export default defineComponent({
|
||||
async function resetALLData() {
|
||||
await ResetPrompt()
|
||||
await ResetImage()
|
||||
await ResetGPTPrompt()
|
||||
}
|
||||
|
||||
// 导入提示词
|
||||
@@ -408,6 +424,7 @@ export default defineComponent({
|
||||
GptButtonOptions: [{ label: '停止推理任务', key: 'stopGptPrompt' }],
|
||||
GenerateImageOptions: [{ label: '停止生成图片任务', key: 'stopGenerateImage' }],
|
||||
ResetDataOptions: [
|
||||
{ label: '重置GPT提示词', key: 'resetGPTPrompt' },
|
||||
{ label: '重置提示词', key: 'resetPrompt' },
|
||||
{ label: '重置图片', key: 'resetImage' }
|
||||
],
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div style="margin-top: 30px">
|
||||
<div
|
||||
style="
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
"
|
||||
id="cnvas_ss"
|
||||
>
|
||||
<canvas ref="imageCanvas" />
|
||||
<canvas
|
||||
ref="tempImageCanvas"
|
||||
@mousemove="draw"
|
||||
@mouseup="stopDrawing"
|
||||
@mousedown="startDrawing"
|
||||
style="position: absolute; top: 1; left: 0"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
|
||||
<n-button style="margin-left: 10px" @click="undo" type="success">回撤</n-button>
|
||||
<n-button style="margin-left: 10px" @click="Check" type="success">查看去水印效果</n-button>
|
||||
<n-button style="margin-left: 10px" @click="SaveMask" type="success">保存蒙板位置</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
useMessage,
|
||||
NInput,
|
||||
NTag,
|
||||
NImage,
|
||||
NCheckbox,
|
||||
NSlider,
|
||||
NPopover,
|
||||
NProgress,
|
||||
useDialog
|
||||
} from 'naive-ui'
|
||||
import ImageFileSelect from './ImageFileSelect.vue'
|
||||
import ProgressDialog from '../Components/ProgressDialog.vue'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import { cloneDeep, isEmpty } from 'lodash'
|
||||
import { useReverseManageStore } from '../../../../stores/reverseManage'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NInput,
|
||||
NTag,
|
||||
ImageFileSelect,
|
||||
NImage,
|
||||
NCheckbox,
|
||||
NSlider,
|
||||
NPopover,
|
||||
NProgress
|
||||
},
|
||||
props: ['width', 'height'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let dialog = useDialog()
|
||||
const imageCanvas = ref(null)
|
||||
let tempImageCanvas = ref(null)
|
||||
let imageContext = ref(null)
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
|
||||
let height = ref(props.height)
|
||||
let width = ref(props.width)
|
||||
let rectPos = ref(null)
|
||||
let drawing = ref(false)
|
||||
let moving = ref(false)
|
||||
let startPos = ref(null)
|
||||
let offset = ref({ x: 0, y: 0 })
|
||||
|
||||
let isDrawing = false
|
||||
let radius = ref(20) // 圆的半径
|
||||
let canvasHistory = [] // 用于保存画布的历史记录(存放位置信息)
|
||||
let step = 0
|
||||
let mask_setting = ref({
|
||||
isRemote: true,
|
||||
localUrl: null,
|
||||
mask_path: null
|
||||
})
|
||||
let progressDialogRef = ref(null)
|
||||
let total = ref(0)
|
||||
let current = ref(0)
|
||||
let dp
|
||||
let image = ref(null)
|
||||
let initImage = null
|
||||
|
||||
onMounted(async () => {
|
||||
if (imageCanvas.value) {
|
||||
const ctx = imageCanvas.value.getContext('2d', { willReadFrequently: true })
|
||||
if (ctx) {
|
||||
imageContext.value = ctx
|
||||
}
|
||||
}
|
||||
|
||||
let div = document.getElementById('cnvas_ss')
|
||||
div.style.height = height.value - 120 + 'px'
|
||||
|
||||
await window.api.GetDefineConfigJsonByProperty(
|
||||
JSON.stringify(['img_base', 'mask_setting', false, null]),
|
||||
(value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
// 判断是不是有数据
|
||||
if (value.data) {
|
||||
mask_setting.value = Object.assign(mask_setting.value, value.data)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
window.api.setEventListen([DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
|
||||
total.value = value.data.total
|
||||
current.value = value.data.current
|
||||
if (progressDialogRef.value == null) {
|
||||
return
|
||||
}
|
||||
progressDialogRef.value?.modifyTotal(total.value)
|
||||
progressDialogRef.value?.modifyCurrent(current.value)
|
||||
|
||||
if (value.data.total == value.data.current) {
|
||||
// 销毁进度组件
|
||||
setTimeout(() => {
|
||||
if (dp) {
|
||||
dp.destroy()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载图片
|
||||
await loadFile(reverseManageStore.selectBookTaskDetail[0].oldImage)
|
||||
})
|
||||
|
||||
let hasImage = false
|
||||
|
||||
/**
|
||||
* 获取图片的高度并且计算适应的宽高
|
||||
* @param image
|
||||
*/
|
||||
function getCurrentWidthHeight(image) {
|
||||
let image_width = 512
|
||||
let image_height = 512
|
||||
// 设置图片的大小
|
||||
if (image) {
|
||||
image_width = image.width
|
||||
image_height = image.height
|
||||
}
|
||||
|
||||
// 判断图片大小,获取当前的传入的window,判断当前的宽度和高度是不是超出了
|
||||
// let min_size = Math.min(width.value,height.value);
|
||||
let image_height_scale = (height.value - 120) / image_height
|
||||
let image_width_scale = (width.value - 120) / image_width
|
||||
|
||||
let scale = Math.min(image_height_scale, image_width_scale)
|
||||
return [image_width * scale, image_height * scale]
|
||||
}
|
||||
|
||||
function getImage(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => resolve(img)
|
||||
img.onerror = reject
|
||||
img.src = reader.result
|
||||
}
|
||||
reader.onerror = (error) => reject(error)
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
async function loadFile(fileUrl) {
|
||||
try {
|
||||
const response = await fetch(fileUrl)
|
||||
const blob = await response.blob()
|
||||
const file = new File([blob], 'file.jpg', { type: blob.type })
|
||||
const img = await getImage(file)
|
||||
image.value = img
|
||||
modifyImage(img)
|
||||
} catch (error) {
|
||||
console.error('Error loading file:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片初始化
|
||||
* @param image_d
|
||||
*/
|
||||
function modifyImage(image_d) {
|
||||
let [image_width, image_height] = getCurrentWidthHeight(image_d)
|
||||
imageContext.value.canvas.width = image_width
|
||||
imageContext.value.canvas.height = image_height
|
||||
imageContext.value.drawImage(image_d, 0, 0, image_width, image_height)
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
hasImage = true
|
||||
let w = (width.value - image_width - 56) / 2
|
||||
tempImageCanvas.value.style.left = w + 'px'
|
||||
tempImageCanvas.value.width = imageCanvas.value.width
|
||||
tempImageCanvas.value.height = imageCanvas.value.height
|
||||
tempImageCanvas.value.style.zIndex = 100
|
||||
if (!initImage) {
|
||||
initImage = imageCanvas.value.toDataURL('image/png')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas绘制矩形
|
||||
* @param rect
|
||||
*/
|
||||
const drawRectangle = (canvas, rect, clear = true) => {
|
||||
const ctx = canvas.value.getContext('2d')
|
||||
if (clear) {
|
||||
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height) // 清除之前的绘制
|
||||
}
|
||||
ctx.strokeStyle = 'red' // 设置矩形颜色
|
||||
ctx.lineWidth = 2 // 设置线宽
|
||||
ctx.strokeRect(rect.startX, rect.startY, rect.width, rect.height)
|
||||
}
|
||||
|
||||
const clearCanvas = (canvas) => {
|
||||
const c = canvas.value
|
||||
const context = c.getContext('2d')
|
||||
context.clearRect(0, 0, c.width, c.height)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前鼠标在canvas中的相对位置
|
||||
* @param e
|
||||
*/
|
||||
function getCanvasRelativePosition(e) {
|
||||
const rect = tempImageCanvas.value.getBoundingClientRect()
|
||||
return {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止画
|
||||
*/
|
||||
function stopDrawing(e) {
|
||||
// 获取矩形
|
||||
if (drawing.value) {
|
||||
const pos = getCanvasRelativePosition(e)
|
||||
rectPos.value = {
|
||||
startX: Math.min(startPos.value.x, pos.x),
|
||||
startY: Math.min(startPos.value.y, pos.y),
|
||||
width: Math.abs(startPos.value.x - pos.x),
|
||||
height: Math.abs(startPos.value.y - pos.y),
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
}
|
||||
drawRectangle(tempImageCanvas, rectPos.value)
|
||||
|
||||
// 将当前的矩形画到imageCanvas上
|
||||
drawRectangle(imageCanvas, rectPos.value, false)
|
||||
isDrawing = false
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
canvasHistory.push(rectPos.value)
|
||||
console.log(canvasHistory)
|
||||
step++
|
||||
startPos.value = null
|
||||
drawing.value = false
|
||||
}
|
||||
if (moving.value) {
|
||||
moving.value = false
|
||||
// 将当前的矩形画到imageCanvas上
|
||||
drawRectangle(imageCanvas, rectPos.value, false)
|
||||
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
canvasHistory.push(rectPos.value)
|
||||
}
|
||||
clearCanvas(tempImageCanvas)
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充颜色
|
||||
* @param e
|
||||
*/
|
||||
function draw(e) {
|
||||
if (drawing.value && startPos.value) {
|
||||
const currentPos = getCanvasRelativePosition(e)
|
||||
const rect = {
|
||||
startX: Math.min(startPos.value.x, currentPos.x),
|
||||
startY: Math.min(startPos.value.y, currentPos.y),
|
||||
width: Math.abs(startPos.value.x - currentPos.x),
|
||||
height: Math.abs(startPos.value.y - currentPos.y)
|
||||
}
|
||||
drawRectangle(tempImageCanvas, rect)
|
||||
} else if (moving.value && rectPos.value) {
|
||||
const currentPos = getCanvasRelativePosition(e)
|
||||
const newRectPos = {
|
||||
startX: currentPos.x - offset.value.x,
|
||||
startY: currentPos.y - offset.value.y,
|
||||
width: rectPos.value.width,
|
||||
height: rectPos.value.height,
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
}
|
||||
rectPos.value = newRectPos
|
||||
drawRectangle(tempImageCanvas, newRectPos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始话
|
||||
* @param event
|
||||
*/
|
||||
function startDrawing(e) {
|
||||
// 判断是移动还是绘制
|
||||
const pos = getCanvasRelativePosition(e)
|
||||
if (
|
||||
rectPos.value &&
|
||||
pos.x >= rectPos.value.startX &&
|
||||
pos.x <= rectPos.value.startX + rectPos.value.width &&
|
||||
pos.y >= rectPos.value.startY &&
|
||||
pos.y <= rectPos.value.startY + rectPos.value.height
|
||||
) {
|
||||
moving.value = true
|
||||
offset.value = { x: pos.x - rectPos.value.startX, y: pos.y - rectPos.value.startY }
|
||||
} else {
|
||||
startPos.value = pos
|
||||
drawing.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回撤
|
||||
*/
|
||||
async function undo() {
|
||||
debugger
|
||||
if (step <= 0) {
|
||||
// 如果没有可以撤销的步骤,就清空画布
|
||||
imageContext.value.clearRect(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
canvasHistory = []
|
||||
} else {
|
||||
step--
|
||||
clearCanvas(imageCanvas)
|
||||
modifyImage(image.value)
|
||||
canvasHistory.pop()
|
||||
// 重新绘制矩形
|
||||
for (let i = 0; i < canvasHistory.length; i++) {
|
||||
const element = canvasHistory[i]
|
||||
drawRectangle(imageCanvas, element, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的蒙板
|
||||
*/
|
||||
function getCanvasMaskBase64() {
|
||||
// 处理最后的图片
|
||||
const imageData = imageContext.value.getImageData(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
const newImageData = imageContext.value.createImageData(imageData.width, imageData.height)
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
if (
|
||||
imageData.data[i] === 255 &&
|
||||
imageData.data[i + 1] === 255 &&
|
||||
imageData.data[i + 2] === 0
|
||||
) {
|
||||
// 如果这个像素点的颜色是 rgba(255, 255, 0, 255),就将它设置为白色
|
||||
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255
|
||||
newImageData.data[i + 3] = 255
|
||||
} else {
|
||||
// 否则,就将它设置为黑色
|
||||
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0
|
||||
newImageData.data[i + 3] = 255
|
||||
}
|
||||
}
|
||||
|
||||
// // 创建一个新的 Canvas 对象
|
||||
const newCanvas = document.createElement('canvas')
|
||||
newCanvas.width = imageData.width
|
||||
newCanvas.height = imageData.height
|
||||
const newContext = newCanvas.getContext('2d')
|
||||
|
||||
// 将新的 ImageData 对象绘制到新的 Canvas 对象上
|
||||
newContext.putImageData(newImageData, 0, 0)
|
||||
|
||||
// 将新的 Canvas 对象转换为一个 base64 编码的图片 URL
|
||||
const base64 = newCanvas.toDataURL('image/png')
|
||||
return base64
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看效果
|
||||
*/
|
||||
async function Check() {
|
||||
debugger
|
||||
// 判断当前是不是有蒙板
|
||||
if (canvasHistory.length == 0) {
|
||||
message.error('请先选择去水印的区域')
|
||||
return
|
||||
}
|
||||
|
||||
let res = await window.img.ProcessImageCheck(
|
||||
initImage,
|
||||
cloneDeep(canvasHistory),
|
||||
reverseManageStore.selectBook.id
|
||||
)
|
||||
const arrayBuffer = res.data
|
||||
const blob = new Blob([arrayBuffer], { type: 'image/png' })
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = function () {
|
||||
let img = new Image()
|
||||
img.src = reader.result
|
||||
img.onload = function () {
|
||||
imageContext.value.drawImage(img, 0, 0)
|
||||
canvasHistory.push({
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
imageWidth: tempImageCanvas.value.width,
|
||||
imageHeight: tempImageCanvas.value.height
|
||||
})
|
||||
step++
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
}
|
||||
|
||||
async function SaveMask() {
|
||||
debugger
|
||||
// 判断当前是不是又蒙板
|
||||
if (canvasHistory.length <= 1) {
|
||||
message.error('请先选择去水印的区域')
|
||||
return
|
||||
}
|
||||
let maskPos = canvasHistory.filter((item) => item.width > 0 && item.height > 0)
|
||||
if (maskPos.length == 0) {
|
||||
message.error('请先选择去水印的区域,或者选择的水印区域无效')
|
||||
return
|
||||
}
|
||||
// 开始保存
|
||||
let res = await window.db.UpdateBookData(reverseManageStore.selectBook.id, {
|
||||
watermarkPosition: JSON.stringify(maskPos)
|
||||
})
|
||||
if (res.code == 0) {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success('水印位置保存成功')
|
||||
}
|
||||
|
||||
/**
|
||||
*查看蒙板
|
||||
*/
|
||||
async function ViewMaskImage() {
|
||||
window.system.OpenFile(mask_setting.value.mask_path)
|
||||
}
|
||||
|
||||
function OpenTeach(type) {
|
||||
switch (type) {
|
||||
case 'lama':
|
||||
window.api.OpenUrl(
|
||||
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#QOIRdJQvEouJB7xv0Xqcx3zInyb'
|
||||
)
|
||||
break
|
||||
case 'iopaint':
|
||||
window.api.OpenUrl(
|
||||
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#MLoUdn5cfo6NJTxL0xTcUGyLn43'
|
||||
)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
modifyImage,
|
||||
imageCanvas,
|
||||
tempImageCanvas,
|
||||
height,
|
||||
width,
|
||||
stopDrawing,
|
||||
startDrawing,
|
||||
startPos,
|
||||
draw,
|
||||
undo,
|
||||
Check,
|
||||
offset,
|
||||
SaveMask,
|
||||
mask_setting,
|
||||
ViewMaskImage,
|
||||
radius,
|
||||
progressDialogRef,
|
||||
total,
|
||||
current,
|
||||
OpenTeach,
|
||||
reverseManageStore,
|
||||
image
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.url_class {
|
||||
color: #e18a3b;
|
||||
}
|
||||
|
||||
.url_class:hover {
|
||||
color: brown;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -43,6 +43,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function selectImage(value) {
|
||||
debugger
|
||||
let img = await getBase64(value.file.file)
|
||||
props.modifyImage(img)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ const app = createApp(App)
|
||||
import { createPinia } from 'pinia'
|
||||
const pinia = createPinia()
|
||||
|
||||
// 注册 webview 为自定义元素
|
||||
app.config.compilerOptions.isCustomElement = (tag) => tag === 'webview'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -101,6 +104,11 @@ const routes = [
|
||||
name: 'test_options',
|
||||
component: () => import('./components/VideoSubtitle/VideoCanvas.vue')
|
||||
},
|
||||
{
|
||||
path: '/lai_api',
|
||||
name: 'lai_api',
|
||||
component: () => import('./components/APIService/ApiService.vue')
|
||||
},
|
||||
{
|
||||
path: '/TTS_Services',
|
||||
name: 'TTS_Services',
|
||||
|
||||
Reference in New Issue
Block a user