V 2.2.7 lama iopaint 去水印
This commit is contained in:
@@ -1,190 +1,250 @@
|
||||
<template>
|
||||
<n-tabs animated default-value="inGetFrame">
|
||||
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
|
||||
<n-spin :show="show">
|
||||
<template #description>
|
||||
正在抽帧
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<div style="color: red;">
|
||||
注意:该项是使用剪映内的草稿进行分镜
|
||||
</div>
|
||||
<n-select v-model:value="selectedValue" filterable placeholder="选择草稿" :options="options" />
|
||||
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
|
||||
</n-space>
|
||||
</n-spin>
|
||||
<n-divider />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
|
||||
<div style="color: red; font-size: large;">
|
||||
注意:该功能需要对环境要求较高。(环境需要自行安装,第一次执行需要下载模型需要魔法)<span class="url_class" @click="OpenTeachDoc">环境安装教程</span>
|
||||
<br>
|
||||
分割敏感度:值越小分割越精细,值越大分割越粗糙
|
||||
</div>
|
||||
<n-form style="margin-top: 50px;" ref="formRef" label-placement="left" inline :model="frameValue"
|
||||
:rules="rules" size="medium">
|
||||
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px;">
|
||||
<n-slider v-model:value=frameValue.sensitivity show-tooltip />
|
||||
</n-form-item>
|
||||
<n-form-item label="分镜视频地址" path="video_path">
|
||||
<n-input v-model:value="frameValue.video_path" placeholder="选择或输入要分镜的视频地址" style="width: 300px;" />
|
||||
<n-button type="primary" @click="GetVideoFile">
|
||||
选择文件(MP4)
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
|
||||
开始分镜
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-divider />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
<n-code style="padding: 0; font-size: small;" :code="code" language="js" word-wrap />
|
||||
<n-tabs animated default-value="inGetFrame">
|
||||
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
|
||||
<n-spin :show="show">
|
||||
<template #description> 正在抽帧 </template>
|
||||
<n-space vertical>
|
||||
<div style="color: red">注意:该项是使用剪映内的草稿进行分镜</div>
|
||||
<n-select
|
||||
v-model:value="selectedValue"
|
||||
filterable
|
||||
placeholder="选择草稿"
|
||||
:options="options"
|
||||
/>
|
||||
<div>
|
||||
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
|
||||
<n-button style="margin-left: 10px" round type="primary" @click="openExportFolder"
|
||||
>打开抽帧文件夹</n-button
|
||||
>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-spin>
|
||||
<n-divider />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
|
||||
<div style="color: red; font-size: large">
|
||||
注意:该功能需要对环境要求较高。(环境需要自行安装,第一次执行需要下载模型需要魔法)<span
|
||||
class="url_class"
|
||||
@click="OpenTeachDoc"
|
||||
>环境安装教程</span
|
||||
>
|
||||
<br />
|
||||
分割敏感度:值越小分割越精细,值越大分割越粗糙
|
||||
</div>
|
||||
<n-form
|
||||
style="margin-top: 50px"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
inline
|
||||
:model="frameValue"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
>
|
||||
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px">
|
||||
<n-slider v-model:value="frameValue.sensitivity" show-tooltip />
|
||||
</n-form-item>
|
||||
<n-form-item label="分镜视频地址" path="video_path">
|
||||
<n-input
|
||||
v-model:value="frameValue.video_path"
|
||||
placeholder="选择或输入要分镜的视频地址"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<n-button type="primary" @click="GetVideoFile"> 选择文件(MP4) </n-button>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
|
||||
开始分镜
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="info" @click="openExportFolder" :loading="storyLoading">
|
||||
打开抽帧文件夹
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-divider />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
<n-code style="padding: 0; font-size: small" :code="code" language="js" word-wrap />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, toRaw } from "vue";
|
||||
import { NSelect, NButton, useMessage, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput } from "naive-ui"
|
||||
import { DEFINE_STRING } from "../../../../define/define_string.js"
|
||||
import { defineComponent, ref, onMounted, toRaw } from 'vue'
|
||||
import {
|
||||
NSelect,
|
||||
NButton,
|
||||
useMessage,
|
||||
NSpin,
|
||||
NDivider,
|
||||
NCode,
|
||||
NTabs,
|
||||
NTabPane,
|
||||
NSpace,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NSlider,
|
||||
NInput
|
||||
} from 'naive-ui'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string.js'
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NSelect, NButton, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput
|
||||
},
|
||||
setup() {
|
||||
let options = [];
|
||||
let out_dir = ref(null);
|
||||
let selectedValue = ref(null);
|
||||
let show = ref(false);
|
||||
let code = ref("");
|
||||
const message = useMessage();
|
||||
let storyLoading = ref(false);
|
||||
components: {
|
||||
NSelect,
|
||||
NButton,
|
||||
NSpin,
|
||||
NDivider,
|
||||
NCode,
|
||||
NTabs,
|
||||
NTabPane,
|
||||
NSpace,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NSlider,
|
||||
NInput
|
||||
},
|
||||
setup() {
|
||||
let options = []
|
||||
let out_dir = ref(null)
|
||||
let selectedValue = ref(null)
|
||||
let show = ref(false)
|
||||
let code = ref('')
|
||||
const message = useMessage()
|
||||
let storyLoading = ref(false)
|
||||
|
||||
let frameValue = ref({
|
||||
sensitivity: 30,
|
||||
video_path: null
|
||||
let frameValue = ref({
|
||||
sensitivity: 30,
|
||||
video_path: null
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.api.getDraftFileList((value) => {
|
||||
value.forEach((element) => {
|
||||
let obj = {
|
||||
label: element,
|
||||
value: element
|
||||
}
|
||||
options.push(obj)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.api.getDraftFileList((value) => {
|
||||
value.forEach(element => {
|
||||
let obj = {
|
||||
label: element,
|
||||
value: element
|
||||
}
|
||||
options.push(obj)
|
||||
});
|
||||
})
|
||||
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
code.value = code.value + "\n" + value.data;
|
||||
return;
|
||||
}
|
||||
// 完成
|
||||
if (value.type == 0) {
|
||||
storyLoading.value = false;
|
||||
message.success("分镜抽帧完成");
|
||||
}
|
||||
code.value = code.value + "\n" + value.data;
|
||||
})
|
||||
})
|
||||
|
||||
async function getFrameFunc(e) {
|
||||
out_dir = window.config.project_path
|
||||
if (selectedValue.value == null || selectedValue.value == undefined) {
|
||||
message.error("请选择剪映草稿和输出草稿")
|
||||
return;
|
||||
}
|
||||
show.value = true
|
||||
// 抽帧
|
||||
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error("抽帧失败");
|
||||
code.value = value.message;
|
||||
return;
|
||||
}
|
||||
message.success("抽帧成功");
|
||||
code.value = value.data;
|
||||
show.value = false;
|
||||
})
|
||||
})
|
||||
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
code.value = code.value + '\n' + value.data
|
||||
return
|
||||
}
|
||||
|
||||
function selectExportFolder(e) {
|
||||
window.api.selectFolder(null, (value) => {
|
||||
if (value.length <= 0) {
|
||||
message.error("必须选择输出文件夹位置");
|
||||
return;
|
||||
}
|
||||
out_dir.value = value[0]
|
||||
})
|
||||
// 完成
|
||||
if (value.type == 0) {
|
||||
storyLoading.value = false
|
||||
message.success('分镜抽帧完成')
|
||||
}
|
||||
code.value = code.value + '\n' + value.data
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 选择指定的视频文件
|
||||
*/
|
||||
async function GetVideoFile() {
|
||||
await window.api.SelectFile(['mp4'], (value) => {
|
||||
debugger
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
frameValue.value.video_path = value.value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始分镜执行分镜任务
|
||||
*/
|
||||
async function StartStoryboarding() {
|
||||
storyLoading.value = true;
|
||||
if (frameValue.value.video_path == null) {
|
||||
message.error("选择分镜的视频地址");
|
||||
return;
|
||||
}
|
||||
debugger
|
||||
if (toRaw(frameValue.value).video_path.split('.')[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != "MP4") {
|
||||
message.error("目前只支持MP4格式");
|
||||
return;
|
||||
}
|
||||
|
||||
await window.api.StartStoryboarding(toRaw(frameValue.value))
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开环境安装网站
|
||||
*/
|
||||
function OpenTeachDoc() {
|
||||
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink");
|
||||
}
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
options,
|
||||
out_dir,
|
||||
getFrameFunc,
|
||||
selectExportFolder,
|
||||
show,
|
||||
code,
|
||||
frameValue,
|
||||
GetVideoFile,
|
||||
StartStoryboarding,
|
||||
OpenTeachDoc,
|
||||
storyLoading
|
||||
async function getFrameFunc(e) {
|
||||
out_dir = window.config.project_path
|
||||
if (selectedValue.value == null || selectedValue.value == undefined) {
|
||||
message.error('请选择剪映草稿和输出草稿')
|
||||
return
|
||||
}
|
||||
show.value = true
|
||||
// 抽帧
|
||||
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error('抽帧失败')
|
||||
code.value = value.message
|
||||
return
|
||||
}
|
||||
message.success('抽帧成功')
|
||||
code.value = value.data
|
||||
show.value = false
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
function selectExportFolder(e) {
|
||||
window.api.selectFolder(null, (value) => {
|
||||
if (value.length <= 0) {
|
||||
message.error('必须选择输出文件夹位置')
|
||||
return
|
||||
}
|
||||
out_dir.value = value[0]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择指定的视频文件
|
||||
*/
|
||||
async function GetVideoFile() {
|
||||
await window.api.SelectFile(['mp4'], (value) => {
|
||||
debugger
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
frameValue.value.video_path = value.value
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始分镜执行分镜任务
|
||||
*/
|
||||
async function StartStoryboarding() {
|
||||
storyLoading.value = true
|
||||
if (frameValue.value.video_path == null) {
|
||||
message.error('选择分镜的视频地址')
|
||||
return
|
||||
}
|
||||
debugger
|
||||
if (
|
||||
toRaw(frameValue.value)
|
||||
.video_path.split('.')
|
||||
[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != 'MP4'
|
||||
) {
|
||||
message.error('目前只支持MP4格式')
|
||||
return
|
||||
}
|
||||
|
||||
await window.api.StartStoryboarding(toRaw(frameValue.value))
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开环境安装网站
|
||||
*/
|
||||
function OpenTeachDoc() {
|
||||
window.api.OpenUrl(
|
||||
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
|
||||
)
|
||||
}
|
||||
|
||||
function openExportFolder() {
|
||||
window.api.OpenFolder("input_crop")
|
||||
}
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
options,
|
||||
out_dir,
|
||||
getFrameFunc,
|
||||
selectExportFolder,
|
||||
show,
|
||||
code,
|
||||
frameValue,
|
||||
GetVideoFile,
|
||||
StartStoryboarding,
|
||||
OpenTeachDoc,
|
||||
storyLoading,
|
||||
openExportFolder
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.url_class {
|
||||
color: blue;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.url_class:hover {
|
||||
color: brown;
|
||||
cursor: pointer;
|
||||
color: brown;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,99 +1,123 @@
|
||||
<template>
|
||||
<n-spin :show="show">
|
||||
<template #description>
|
||||
正在反推
|
||||
</template>
|
||||
<n-button type="info" @click="PushBackPrompt"> 开始反推 </n-button>
|
||||
<n-button type="error" style="margin-left: 50px;" @click="DeleteBadPrompt"> 一键去除所有不想要的值 </n-button>
|
||||
<n-button type="warning" style="margin-left: 50px;" @click="ManagePrompt"> 管理不想要的值 </n-button>
|
||||
<n-divider />
|
||||
</n-spin>
|
||||
<n-code style="padding: 0;" :code="code" language="js" word-wrap />
|
||||
<n-spin :show="show">
|
||||
<template #description> 正在反推 </template>
|
||||
<n-button type="info" @click="RemoveWather"> 去水印 </n-button>
|
||||
<n-button type="info" style="margin-left: 20px" @click="PushBackPrompt"> 开始反推 </n-button>
|
||||
<n-button type="error" style="margin-left: 20px" @click="DeleteBadPrompt">
|
||||
一键去除所有不想要的值
|
||||
</n-button>
|
||||
<n-button type="warning" style="margin-left: 20px" @click="ManagePrompt">
|
||||
管理不想要的值
|
||||
</n-button>
|
||||
<n-divider />
|
||||
</n-spin>
|
||||
<n-code style="padding: 0" :code="code" language="js" word-wrap />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, h, toRaw } from "vue";
|
||||
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from "naive-ui"
|
||||
import { DEFINE_STRING } from "../../../../define/define_string";
|
||||
import ManageBadPrompt from "../Components/ManageBadPrompt.vue"
|
||||
import { defineComponent, ref, onMounted, h, toRaw } from 'vue'
|
||||
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from 'naive-ui'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import ManageBadPrompt from '../Components/ManageBadPrompt.vue'
|
||||
import GetWaterMask from '../Watermark/GetWaterMask.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton, NDivider, NCode, NSpin
|
||||
},
|
||||
setup() {
|
||||
let dialog = useDialog();
|
||||
let code = ref("");
|
||||
let show = ref(false);
|
||||
let promprRef = ref();
|
||||
let message = useMessage();
|
||||
components: {
|
||||
NButton,
|
||||
NDivider,
|
||||
NCode,
|
||||
NSpin
|
||||
},
|
||||
setup() {
|
||||
let dialog = useDialog()
|
||||
let code = ref('')
|
||||
let show = ref(false)
|
||||
let promprRef = ref()
|
||||
let message = useMessage()
|
||||
|
||||
// 开始反推
|
||||
async function PushBackPrompt() {
|
||||
show.value = true;
|
||||
await window.api.PushBackPrompt((value) => {
|
||||
if (value.code == 0) {
|
||||
message.error("反推报错");
|
||||
code.value = value.message;
|
||||
return;
|
||||
}
|
||||
message.success("反推成功");
|
||||
code.value = value.data;
|
||||
show.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理不想要的值
|
||||
*/
|
||||
async function ManagePrompt() {
|
||||
// 弹窗,管理不想要的值
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: "管理不想要的值",
|
||||
content: () => h(ManageBadPrompt, { ref: promprRef }),
|
||||
style: `width : 650px;`,
|
||||
maskClosable: false,
|
||||
positiveText: "确定",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: async () => {
|
||||
console.log(toRaw(promprRef.value.tags));
|
||||
// 将数据写入
|
||||
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
|
||||
console.log(value)
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return false;
|
||||
}
|
||||
message.success("添加成功");
|
||||
})
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键删除不想要的值
|
||||
*/
|
||||
async function DeleteBadPrompt() {
|
||||
await window.api.DeleteBadPrompt((value) => {
|
||||
debugger
|
||||
console.log(value);
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
message.success("去除成功");
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
code,
|
||||
show,
|
||||
PushBackPrompt,
|
||||
ManagePrompt,
|
||||
DeleteBadPrompt
|
||||
// 开始反推
|
||||
async function PushBackPrompt() {
|
||||
show.value = true
|
||||
await window.api.PushBackPrompt((value) => {
|
||||
if (value.code == 0) {
|
||||
message.error('反推报错')
|
||||
code.value = value.message
|
||||
return
|
||||
}
|
||||
message.success('反推成功')
|
||||
code.value = value.data
|
||||
show.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理不想要的值
|
||||
*/
|
||||
async function ManagePrompt() {
|
||||
// 弹窗,管理不想要的值
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
title: '管理不想要的值',
|
||||
content: () => h(ManageBadPrompt, { ref: promprRef }),
|
||||
style: `width : 650px;`,
|
||||
maskClosable: false,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
console.log(toRaw(promprRef.value.tags))
|
||||
// 将数据写入
|
||||
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
|
||||
console.log(value)
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return false
|
||||
}
|
||||
message.success('添加成功')
|
||||
})
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键删除不想要的值
|
||||
*/
|
||||
async function DeleteBadPrompt() {
|
||||
await window.api.DeleteBadPrompt((value) => {
|
||||
debugger
|
||||
console.log(value)
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success('去除成功')
|
||||
})
|
||||
}
|
||||
|
||||
async function RemoveWather() {
|
||||
// 判断当前数据是不是存在
|
||||
// 处理数据。获取当前的所有的数据
|
||||
let dialogWidth = window.innerWidth * 0.7
|
||||
let dialogHeight = window.innerHeight * 0.9
|
||||
// ImportWordAndSrt
|
||||
dialog.create({
|
||||
showIcon: false,
|
||||
closeOnEsc: false,
|
||||
content: () => h(GetWaterMask, { width: dialogWidth, height: dialogHeight }),
|
||||
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
|
||||
maskClosable: false,
|
||||
onClose: () => {}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
code,
|
||||
show,
|
||||
PushBackPrompt,
|
||||
ManagePrompt,
|
||||
DeleteBadPrompt,
|
||||
RemoveWather
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<n-progress type="circle" :percentage="percentage">
|
||||
<span style="text-align: center; font-size: 20px">{{ text }}</span>
|
||||
</n-progress>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
|
||||
import { NProgress, useMessage } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NProgress
|
||||
},
|
||||
props: ['tatol', 'current'],
|
||||
setup(props) {
|
||||
let text = ref('等待中')
|
||||
let percentage = ref(0)
|
||||
let tatol = ref(props.tatol ? props.tatol : 1)
|
||||
let current = ref(props.current ? props.current : 0)
|
||||
|
||||
// 更新数据
|
||||
function modifyTotal(value) {
|
||||
tatol.value = value
|
||||
if (!value || value == 0) {
|
||||
text.value = '等待中'
|
||||
}
|
||||
}
|
||||
|
||||
function modifyCurrent(value) {
|
||||
current.value = value ? value : 0
|
||||
percentage.value = Math.round((current.value / (tatol.value ? tatol.value : 1)) * 100)
|
||||
if (tatol.value && tatol.value > 0) {
|
||||
text.value = percentage.value + '%'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {})
|
||||
|
||||
return {
|
||||
text,
|
||||
percentage,
|
||||
tatol,
|
||||
current,
|
||||
modifyTotal,
|
||||
modifyCurrent
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -63,7 +63,7 @@ export default defineComponent({
|
||||
let data = ref(props.initData)
|
||||
let AnalyzeCharacter = ref(props.Character)
|
||||
let tagTreeData = ref(props.treeData)
|
||||
let auto_save = ref(window.config.auto_save ? window.config.auto_save : false)
|
||||
let auto_save = ref(window.config?.auto_save ? window.config.auto_save : false)
|
||||
|
||||
watch(
|
||||
() => props.initData,
|
||||
|
||||
@@ -1,274 +1,318 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-tabs class="card-tabs" default-value="signin" size="large" animated pane-wrapper-style="margin: 0 -4px"
|
||||
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
|
||||
<n-tab-pane name="signin" tab="GPT服务商">
|
||||
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
|
||||
<n-form-item label="选择自定义的GPT服务商">
|
||||
<n-select style="width: 300px;" :options="gpt_options"
|
||||
v-model:value="gpt_select_key"></n-select>
|
||||
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditOption">编辑</n-button>
|
||||
<n-button color="#984f31" style="margin-left: 10px;" @click="AddOption">添加</n-button>
|
||||
<n-button type="error" style="margin-left: 10px;" @click="DeleteOption">删除</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item path="label" label="GPT 名称">
|
||||
<n-input v-model:value="gpt_select_value.label"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="value" label="GPT请求网址(写全)">
|
||||
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
|
||||
</n-form-item>
|
||||
<div style="text-align: right;">
|
||||
<n-button type="primary" @click="SaveGptOption">保存</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="signup" tab="GPT模型">
|
||||
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
|
||||
<n-form-item label="选择自定义的GPT模型">
|
||||
<n-select style="width: 300px;" :options="gpt_model_options"
|
||||
v-model:value="gpt_model_select_key"></n-select>
|
||||
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditModelOption">编辑</n-button>
|
||||
<n-button color="#984f31" style="margin-left: 10px;" @click="AddModelOption">添加</n-button>
|
||||
<n-button type="error" style="margin-left: 10px;" @click="DeleteModelOption">删除</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item path="label" label="GPT 模型名称">
|
||||
<n-input v-model:value="gpt_model_select_value.label"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="value" label="GPT 模型值">
|
||||
<n-input v-model:value="gpt_model_select_value.value"></n-input>
|
||||
</n-form-item>
|
||||
<div style="text-align: right;">
|
||||
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
<div>
|
||||
<n-tabs
|
||||
class="card-tabs"
|
||||
default-value="signin"
|
||||
size="large"
|
||||
animated
|
||||
pane-wrapper-style="margin: 0 -4px"
|
||||
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
|
||||
>
|
||||
<n-tab-pane name="signin" tab="GPT服务商">
|
||||
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
|
||||
<n-form-item label="选择自定义的GPT服务商">
|
||||
<n-select
|
||||
style="width: 300px"
|
||||
:options="gpt_options"
|
||||
v-model:value="gpt_select_key"
|
||||
></n-select>
|
||||
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditOption">编辑</n-button>
|
||||
<n-button color="#984f31" style="margin-left: 10px" @click="AddOption">添加</n-button>
|
||||
<n-button type="error" style="margin-left: 10px" @click="DeleteOption">删除</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item path="label" label="GPT 名称">
|
||||
<n-input v-model:value="gpt_select_value.label"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="value" label="GPT请求网址(写全)">
|
||||
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
|
||||
</n-form-item>
|
||||
<div style="text-align: right">
|
||||
<n-button type="primary" @click="SaveGptOption">保存</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="signup" tab="GPT模型">
|
||||
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
|
||||
<n-form-item label="选择自定义的GPT模型">
|
||||
<n-select
|
||||
style="width: 300px"
|
||||
:options="gpt_model_options"
|
||||
v-model:value="gpt_model_select_key"
|
||||
></n-select>
|
||||
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditModelOption"
|
||||
>编辑</n-button
|
||||
>
|
||||
<n-button color="#984f31" style="margin-left: 10px" @click="AddModelOption"
|
||||
>添加</n-button
|
||||
>
|
||||
<n-button type="error" style="margin-left: 10px" @click="DeleteModelOption"
|
||||
>删除</n-button
|
||||
>
|
||||
</n-form-item>
|
||||
<n-form-item path="label" label="GPT 模型名称">
|
||||
<n-input v-model:value="gpt_model_select_value.label"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="value" label="GPT 模型值">
|
||||
<n-input v-model:value="gpt_model_select_value.value"></n-input>
|
||||
</n-form-item>
|
||||
<div style="text-align: right">
|
||||
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, toRaw, onMounted, defineComponent } from "vue"
|
||||
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, useMessage, NSelect, NTabs, NTabPane } from "naive-ui"
|
||||
import { cloneDeep } from "lodash";
|
||||
import { ref, toRaw, onMounted, defineComponent } from 'vue'
|
||||
import {
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
useDialog,
|
||||
NButton,
|
||||
useMessage,
|
||||
NSelect,
|
||||
NTabs,
|
||||
NTabPane
|
||||
} from 'naive-ui'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NTabs, NTabPane
|
||||
},
|
||||
setup() {
|
||||
let message = useMessage();
|
||||
let gpt_model_options = ref([]);
|
||||
let gpt_options = ref([]);
|
||||
let gpt_select_value = ref({});
|
||||
let gpt_select_key = ref("");
|
||||
let gpt_model_select_value = ref({});
|
||||
let gpt_model_select_key = ref("");
|
||||
let formRef = ref(null);
|
||||
let modelFormRef = ref(null);
|
||||
components: {
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NButton,
|
||||
NSelect,
|
||||
NTabs,
|
||||
NTabPane
|
||||
},
|
||||
setup() {
|
||||
let message = useMessage()
|
||||
let gpt_model_options = ref([])
|
||||
let gpt_options = ref([])
|
||||
let gpt_select_value = ref({})
|
||||
let gpt_select_key = ref('')
|
||||
let gpt_model_select_value = ref({})
|
||||
let gpt_model_select_key = ref('')
|
||||
let formRef = ref(null)
|
||||
let modelFormRef = ref(null)
|
||||
|
||||
/**
|
||||
* 加载GPT的配置信息
|
||||
*/
|
||||
async function InitGptOptions() {
|
||||
await window.api.getGptBusinessOption("dynamic", (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
if (value.data.length == 0) {
|
||||
message.error("请先添加自定义GPT服务商");
|
||||
return;
|
||||
}
|
||||
gpt_options.value = value.data;
|
||||
gpt_select_key.value = value.data[0].value;
|
||||
gpt_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
};
|
||||
})
|
||||
|
||||
await window.api.getGptModelOption("dynamic", (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
if (value.data.length == 0) {
|
||||
message.error("请先添加自定义GPT模型");
|
||||
return;
|
||||
}
|
||||
gpt_model_options.value = value.data;
|
||||
gpt_model_select_key.value = value.data[0].value;
|
||||
gpt_model_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 加载GPT的配置信息
|
||||
*/
|
||||
async function InitGptOptions() {
|
||||
await window.api.getGptBusinessOption('dynamic', (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
onMounted(async () => {
|
||||
await InitGptOptions();
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存当前的GPT配置信息
|
||||
*/
|
||||
async function SaveGptOption(e) {
|
||||
e.preventDefault();
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
message.error("请检查必填字段");
|
||||
return
|
||||
}
|
||||
// 保存逻辑
|
||||
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_select_value.value)), "gpt_options"], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
message.success("添加成功");
|
||||
await InitGptOptions();
|
||||
})
|
||||
});
|
||||
if (value.data.length == 0) {
|
||||
message.error('请先添加自定义GPT服务商')
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前选中的GPT配置信息,写入下面已编辑
|
||||
*/
|
||||
async function EditOption() {
|
||||
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
|
||||
gpt_select_value.value = cloneDeep(temp);
|
||||
gpt_options.value = value.data
|
||||
gpt_select_key.value = value.data[0].value
|
||||
gpt_select_value.value = {
|
||||
label: null,
|
||||
value: null,
|
||||
gpt_url: null
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 将当前选中的GPT模型配置信息,写入下面已编辑
|
||||
*/
|
||||
async function EditModelOption() {
|
||||
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
|
||||
gpt_model_select_value.value = cloneDeep(temp);
|
||||
await window.api.getGptModelOption('dynamic', (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前选中的GPT服务商信息
|
||||
*/
|
||||
async function DeleteOption() {
|
||||
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
|
||||
let id = temp.id;
|
||||
await window.api.DeleteDynamicGptOption([id, "gpt_options"], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
message.success("删除成功");
|
||||
await InitGptOptions();
|
||||
})
|
||||
if (value.data.length == 0) {
|
||||
message.error('请先添加自定义GPT模型')
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前选中的GPT服务商信息
|
||||
*/
|
||||
async function DeleteModelOption() {
|
||||
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
|
||||
let id = temp.id;
|
||||
await window.api.DeleteDynamicGptOption([id, "gpt_model_options"], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
message.success("删除成功");
|
||||
await InitGptOptions();
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前的GPT配置信息
|
||||
*/
|
||||
async function SaveGptModelOption(e) {
|
||||
e.preventDefault();
|
||||
modelFormRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
message.error("请检查必填字段");
|
||||
return
|
||||
}
|
||||
// 保存逻辑
|
||||
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_model_select_value.value)), "gpt_model_options"], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message);
|
||||
return;
|
||||
}
|
||||
message.success("添加成功");
|
||||
await InitGptOptions();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个GPT服务商,只是清楚当前的数据
|
||||
*/
|
||||
async function AddOption() {
|
||||
gpt_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个GPT服务商,只是清楚当前的数据
|
||||
*/
|
||||
async function AddModelOption() {
|
||||
gpt_model_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
};
|
||||
}
|
||||
|
||||
let ruleObj = (errorMessage) => {
|
||||
return [{
|
||||
required: true,
|
||||
validator(rule, value) {
|
||||
if (value == null || value == "")
|
||||
return new Error(errorMessage);
|
||||
return true;
|
||||
},
|
||||
trigger: ["input", "blur"]
|
||||
}]
|
||||
}
|
||||
|
||||
let rules = {
|
||||
label: ruleObj("必填GPT名称"),
|
||||
gpt_url: ruleObj("必填GPT请求网址"),
|
||||
};
|
||||
|
||||
let modelRules = {
|
||||
label: ruleObj("必填GPT名称"),
|
||||
value: ruleObj("必填GPT请求网址"),
|
||||
};
|
||||
|
||||
return {
|
||||
gpt_model_options,
|
||||
gpt_options,
|
||||
gpt_select_value,
|
||||
gpt_model_select_value,
|
||||
gpt_select_key,
|
||||
gpt_model_select_key,
|
||||
SaveGptOption,
|
||||
EditOption,
|
||||
DeleteOption,
|
||||
AddOption,
|
||||
SaveGptModelOption,
|
||||
rules,
|
||||
modelRules,
|
||||
formRef,
|
||||
modelFormRef,
|
||||
EditModelOption,
|
||||
AddModelOption,
|
||||
DeleteModelOption
|
||||
gpt_model_options.value = value.data
|
||||
gpt_model_select_key.value = value.data[0].value
|
||||
gpt_model_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
onMounted(async () => {
|
||||
await InitGptOptions()
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存当前的GPT配置信息
|
||||
*/
|
||||
async function SaveGptOption(e) {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
message.error('请检查必填字段')
|
||||
return
|
||||
}
|
||||
// 保存逻辑
|
||||
await window.api.SaveDynamicGptOption(
|
||||
[JSON.stringify(toRaw(gpt_select_value.value)), 'gpt_options'],
|
||||
async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success('添加成功')
|
||||
await InitGptOptions()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前选中的GPT配置信息,写入下面已编辑
|
||||
*/
|
||||
async function EditOption() {
|
||||
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
|
||||
gpt_select_value.value = cloneDeep(temp)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前选中的GPT模型配置信息,写入下面已编辑
|
||||
*/
|
||||
async function EditModelOption() {
|
||||
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
|
||||
gpt_model_select_value.value = cloneDeep(temp)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前选中的GPT服务商信息
|
||||
*/
|
||||
async function DeleteOption() {
|
||||
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
|
||||
let id = temp.id
|
||||
await window.api.DeleteDynamicGptOption([id, 'gpt_options'], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success('删除成功')
|
||||
await InitGptOptions()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前选中的GPT服务商信息
|
||||
*/
|
||||
async function DeleteModelOption() {
|
||||
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
|
||||
let id = temp.id
|
||||
await window.api.DeleteDynamicGptOption([id, 'gpt_model_options'], async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success('删除成功')
|
||||
await InitGptOptions()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前的GPT配置信息
|
||||
*/
|
||||
async function SaveGptModelOption(e) {
|
||||
e.preventDefault()
|
||||
modelFormRef.value?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
message.error('请检查必填字段')
|
||||
return
|
||||
}
|
||||
// 保存逻辑
|
||||
await window.api.SaveDynamicGptOption(
|
||||
[JSON.stringify(toRaw(gpt_model_select_value.value)), 'gpt_model_options'],
|
||||
async (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success('添加成功')
|
||||
await InitGptOptions()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个GPT服务商,只是清楚当前的数据
|
||||
*/
|
||||
async function AddOption() {
|
||||
gpt_select_value.value = {
|
||||
label: null,
|
||||
value: null,
|
||||
gpt_url: null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个GPT服务商,只是清楚当前的数据
|
||||
*/
|
||||
async function AddModelOption() {
|
||||
gpt_model_select_value.value = {
|
||||
label: null,
|
||||
value: null
|
||||
}
|
||||
}
|
||||
|
||||
let ruleObj = (errorMessage) => {
|
||||
return [
|
||||
{
|
||||
required: true,
|
||||
validator(rule, value) {
|
||||
if (value == null || value == '') return new Error(errorMessage)
|
||||
return true
|
||||
},
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let rules = {
|
||||
label: ruleObj('必填GPT名称'),
|
||||
gpt_url: ruleObj('必填GPT请求网址')
|
||||
}
|
||||
|
||||
let modelRules = {
|
||||
label: ruleObj('必填GPT名称'),
|
||||
value: ruleObj('必填GPT请求网址')
|
||||
}
|
||||
|
||||
return {
|
||||
gpt_model_options,
|
||||
gpt_options,
|
||||
gpt_select_value,
|
||||
gpt_model_select_value,
|
||||
gpt_select_key,
|
||||
gpt_model_select_key,
|
||||
SaveGptOption,
|
||||
EditOption,
|
||||
DeleteOption,
|
||||
AddOption,
|
||||
SaveGptModelOption,
|
||||
rules,
|
||||
modelRules,
|
||||
formRef,
|
||||
modelFormRef,
|
||||
EditModelOption,
|
||||
AddModelOption,
|
||||
DeleteModelOption
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-tabs .n-tabs-nav--bar-type {
|
||||
padding-left: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</n-form-item>
|
||||
<n-form-item label="出图方式(文生图、图生图)" path="type">
|
||||
<n-select
|
||||
style="width: 250px"
|
||||
style="width: 250px"
|
||||
v-model:value="formValue.type"
|
||||
:options="modelOption"
|
||||
placeholder="选择出图方式"
|
||||
|
||||
@@ -0,0 +1,555 @@
|
||||
<template>
|
||||
<div style="margin-top: 30px">
|
||||
<div style="display: flex; margin-right: 10px">
|
||||
<ImageFileSelect :modifyImage="modifyImage"></ImageFileSelect>
|
||||
<div style="width: 250px; margin-left: 10px">
|
||||
<n-checkbox size="small" v-model:checked="mask_setting.isRemote"> 使用iopaint </n-checkbox>
|
||||
<!-- <div>
|
||||
<n-checkbox v-model:checked="mask_setting.isRemote"> 软件内去除 </n-checkbox>
|
||||
</div> -->
|
||||
<div style="display: flex; justify-content: flex-end; align-items: flex-end">
|
||||
<n-input
|
||||
size="tiny"
|
||||
style="margin-top: 5px"
|
||||
v-model:value="mask_setting.localUrl"
|
||||
placeholder="请输入本地iopaint地址"
|
||||
></n-input>
|
||||
<n-button type="success" @click="SaveMaskConfig" size="tiny">保存</n-button>
|
||||
</div>
|
||||
<div style="margin-top: 5px">
|
||||
<span class="url_class" @click="OpenTeach('lama')">lama安装教程</span>
|
||||
<span class="url_class" @click="OpenTeach('iopaint')" style="margin-left: 5px"
|
||||
>iopaint安装教程</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="margin-top: 10px; display: flex; justify-content: center; align-items: center"
|
||||
id="cnvas_ss"
|
||||
>
|
||||
<!-- <canvas ref="imageCanvas" /> -->
|
||||
<canvas
|
||||
ref="imageCanvas"
|
||||
:style="{
|
||||
cursor: 'grab',
|
||||
clipPath: `inset(0 ${sliderPos}% 0 0)`,
|
||||
transition: `clip-path 300ms`
|
||||
}"
|
||||
@mousemove="draw"
|
||||
@mouseup="stopDrawing"
|
||||
@mousedown="startDrawing"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
|
||||
<n-slider v-model:value="radius" :step="1" style="width: 120px" />
|
||||
<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>
|
||||
<n-button style="margin-left: 10px" @click="ViewMaskImage" type="success">查看蒙板</n-button>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button style="margin-left: 10px" @click="BatchMask" type="success"
|
||||
>开始批量处理</n-button
|
||||
>
|
||||
</template>
|
||||
<span>批量处理的图片的尺寸要和上面的蒙板一致</span>
|
||||
</n-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
|
||||
import {
|
||||
NButton,
|
||||
useMessage,
|
||||
NInput,
|
||||
NTag,
|
||||
NImage,
|
||||
NCheckbox,
|
||||
NSlider,
|
||||
NPopover,
|
||||
NProgress,
|
||||
useDialog,
|
||||
useDialogReactiveList
|
||||
} from 'naive-ui'
|
||||
import ImageFileSelect from './ImageFileSelect.vue'
|
||||
import ProgressDialog from '../Components/ProgressDialog.vue'
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
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 imageContext = ref(null)
|
||||
|
||||
let height = ref(props.height)
|
||||
let width = ref(props.width)
|
||||
|
||||
let isDrawing = false
|
||||
let radius = ref(20) // 圆的半径
|
||||
let points = [] // 用于保存鼠标的移动轨迹
|
||||
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
|
||||
|
||||
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 - 220 + '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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let hasImage = false
|
||||
|
||||
/**
|
||||
* 获取图片的高度并且计算适应的宽高
|
||||
* @param image
|
||||
*/
|
||||
function getCurrentWidthHeight(image) {
|
||||
debugger
|
||||
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 - 220) / image_height
|
||||
let image_width_scale = (width.value - 220) / image_width
|
||||
|
||||
let scale = Math.min(image_height_scale, image_width_scale)
|
||||
return [image_width * scale, image_height * scale]
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片初始化
|
||||
* @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 = []
|
||||
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
hasImage = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止画
|
||||
*/
|
||||
function stopDrawing() {
|
||||
debugger
|
||||
isDrawing = false
|
||||
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
|
||||
console.log(canvasHistory)
|
||||
step++
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算点和点直接的所有像素点
|
||||
* @param x0
|
||||
* @param y0
|
||||
* @param x1
|
||||
* @param y1
|
||||
*/
|
||||
function bresenhamLine(x0, y0, x1, y1) {
|
||||
let dx = Math.abs(x1 - x0)
|
||||
let dy = Math.abs(y1 - y0)
|
||||
let sx = x0 < x1 ? 1 : -1
|
||||
let sy = y0 < y1 ? 1 : -1
|
||||
let err = dx - dy
|
||||
|
||||
let points = []
|
||||
while (true) {
|
||||
points.push({ x: x0, y: y0 })
|
||||
|
||||
if (x0 === x1 && y0 === y1) break
|
||||
let e2 = 2 * err
|
||||
if (e2 > -dy) {
|
||||
err -= dy
|
||||
x0 += sx
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx
|
||||
y0 += sy
|
||||
}
|
||||
}
|
||||
|
||||
return points
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充颜色
|
||||
* @param event
|
||||
*/
|
||||
function draw(event) {
|
||||
requestAnimationFrame(() => {
|
||||
const rect = imageContext.value.canvas.getBoundingClientRect()
|
||||
const x = event.clientX - rect.left
|
||||
const y = event.clientY - rect.top
|
||||
|
||||
if (isDrawing && hasImage) {
|
||||
// 获取上一个鼠标位置
|
||||
const lastPoint = points[points.length - 1]
|
||||
// 使用 Bresenham's line algorithm 计算两个点之间的所有像素点
|
||||
const linePoints = bresenhamLine(lastPoint.x, lastPoint.y, x, y)
|
||||
for (const point of linePoints) {
|
||||
// 在每个像素点的周围绘制一个圆
|
||||
imageContext.value.beginPath()
|
||||
imageContext.value.arc(point.x, point.y, radius.value, 0, Math.PI * 2, false)
|
||||
imageContext.value.fillStyle = 'rgba(255, 255, 0)'
|
||||
imageContext.value.fill()
|
||||
}
|
||||
// 将当前鼠标位置添加到 points 数组中
|
||||
points.push({ x, y })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始话
|
||||
* @param event
|
||||
*/
|
||||
function startDrawing(event) {
|
||||
isDrawing = true
|
||||
const rect = imageContext.value.canvas.getBoundingClientRect()
|
||||
points.push({ x: event.clientX - rect.left, y: event.clientY - rect.top })
|
||||
}
|
||||
|
||||
/**
|
||||
* 回撤
|
||||
*/
|
||||
async function undo() {
|
||||
debugger
|
||||
if (step <= 0) {
|
||||
// 如果没有可以撤销的步骤,就清空画布
|
||||
imageContext.value.clearRect(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
canvasHistory = []
|
||||
} else {
|
||||
step--
|
||||
let canvasPic = new Image()
|
||||
canvasHistory.pop()
|
||||
canvasPic.src = canvasHistory[step]
|
||||
canvasPic.onload = function () {
|
||||
imageContext.value.clearRect(
|
||||
0,
|
||||
0,
|
||||
imageContext.value.canvas.width,
|
||||
imageContext.value.canvas.height
|
||||
)
|
||||
imageContext.value.drawImage(canvasPic, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的蒙板
|
||||
*/
|
||||
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() {
|
||||
// 判断当前是不是有蒙板
|
||||
if (canvasHistory.length <= 1) {
|
||||
message.error('请先绘制蒙板')
|
||||
return
|
||||
}
|
||||
let base64 = getCanvasMaskBase64()
|
||||
let baseUrl = mask_setting.value.localUrl
|
||||
if (mask_setting.value.isRemote) {
|
||||
}
|
||||
|
||||
let data = {
|
||||
image: canvasHistory[0],
|
||||
mask: base64,
|
||||
type: 'arrayBuffer'
|
||||
}
|
||||
await window.img.ProcessImage(JSON.stringify(data), (value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
debugger
|
||||
const arrayBuffer = value.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(imageCanvas.value.toDataURL())
|
||||
step++
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}
|
||||
|
||||
async function SaveMask() {
|
||||
debugger
|
||||
// 判断当前是不是又蒙板
|
||||
if (canvasHistory.length <= 1) {
|
||||
message.error('请先绘制蒙板')
|
||||
return
|
||||
}
|
||||
let base64 = getCanvasMaskBase64()
|
||||
// 将base64 保存到本地
|
||||
|
||||
await window.img.Base64ToFile(
|
||||
JSON.stringify([base64, `data/mask/mask_${new Date().getTime()}.png`]),
|
||||
async (value) => {
|
||||
debugger
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
mask_setting.value.mask_path = value.data
|
||||
await SaveMaskConfig()
|
||||
message.success('蒙板保存成功')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存蒙板配置信息
|
||||
*/
|
||||
async function SaveMaskConfig() {
|
||||
// 判断是不是iopaint,是的话没有localUrl就报错
|
||||
if (mask_setting.value.isRemote && isEmpty(mask_setting.value.localUrl)) {
|
||||
message.error('请输入iopaint地址')
|
||||
return
|
||||
}
|
||||
await window.api.SaveDefineConfigJsonByProperty(
|
||||
JSON.stringify(['img_base', 'mask_setting', toRaw(mask_setting.value), false]),
|
||||
(value) => {
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
message.success(value.message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
*查看蒙板
|
||||
*/
|
||||
async function ViewMaskImage() {
|
||||
window.system.OpenFile(mask_setting.value.mask_path)
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始批量处理
|
||||
*/
|
||||
async function BatchMask() {
|
||||
// 判断当前是不是有配置模板
|
||||
if (mask_setting.value.mask_path == null) {
|
||||
message.error('请先绘制蒙板')
|
||||
return
|
||||
}
|
||||
|
||||
// 开始处理
|
||||
// 弹窗,管理不想要的值
|
||||
dp = dialog.create({
|
||||
name: 'progressDialog',
|
||||
showIcon: false,
|
||||
content: () =>
|
||||
h(ProgressDialog, {
|
||||
ref: progressDialogRef,
|
||||
total: total,
|
||||
current: current
|
||||
}),
|
||||
style: `width : 180px; opacity: 0.8;;`,
|
||||
maskClosable: false,
|
||||
closable: false,
|
||||
onPositiveClick: async () => {}
|
||||
})
|
||||
// 提交处理任务,默认处理文件夹下面的所有的PNG图片
|
||||
await window.img.BatchProcessImage('tmp/input_crop', (value) => {
|
||||
debugger
|
||||
// 不管返回什么,这边都关掉
|
||||
setTimeout(() => {
|
||||
if (dp) {
|
||||
dp.destroy()
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
if (value.code == 0) {
|
||||
message.error(value.message)
|
||||
return
|
||||
}
|
||||
window.api.showGlobalMessageDialog(value)
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
height,
|
||||
width,
|
||||
stopDrawing,
|
||||
startDrawing,
|
||||
draw,
|
||||
undo,
|
||||
Check,
|
||||
SaveMask,
|
||||
mask_setting,
|
||||
SaveMaskConfig,
|
||||
ViewMaskImage,
|
||||
radius,
|
||||
BatchMask,
|
||||
progressDialogRef,
|
||||
total,
|
||||
current,
|
||||
OpenTeach
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.url_class {
|
||||
color: #e18a3b;
|
||||
}
|
||||
|
||||
.url_class:hover {
|
||||
color: brown;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<n-upload :show-file-list="false" directory-dnd :default-upload="false" @change="selectImage">
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<archive-icon />
|
||||
</n-icon>
|
||||
</div>
|
||||
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </n-text>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted, h, toRaw, watch } from 'vue'
|
||||
import { NButton, useMessage, NInput, NTag, NUpload, NUploadDragger } from 'naive-ui'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NButton,
|
||||
NInput,
|
||||
NTag,
|
||||
NUpload,
|
||||
NUploadDragger
|
||||
},
|
||||
props: ['modifyImage'],
|
||||
setup(props) {
|
||||
let message = useMessage()
|
||||
let image = ref(props.image)
|
||||
|
||||
function getBase64(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 selectImage(value) {
|
||||
let img = await getBase64(value.file.file)
|
||||
props.modifyImage(img)
|
||||
}
|
||||
return {
|
||||
selectImage,
|
||||
image
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user