847 lines
18 KiB
Vue
847 lines
18 KiB
Vue
<template>
|
||
<transition name="fade">
|
||
<div class="selection-toolbar" v-if="visible">
|
||
<!-- 顶部选区类型工具栏 -->
|
||
<div class="toolbar-section">
|
||
<div class="toolbar-header">
|
||
<div class="header-title">{{ t("选区工具") }}</div>
|
||
<!-- 移除关闭按钮,完全通过工具切换控制显示隐藏 -->
|
||
</div>
|
||
|
||
<div class="tool-types">
|
||
<div
|
||
:class="[
|
||
'tool-btn',
|
||
{ active: selectionType === OperationType.LASSO },
|
||
]"
|
||
@click="setSelectionType(OperationType.LASSO)"
|
||
>
|
||
<svg-icon name="CFree" size="26" />
|
||
<span>{{ $t("手绘") }}</span>
|
||
</div>
|
||
<div
|
||
:class="[
|
||
'tool-btn',
|
||
{ active: selectionType === OperationType.LASSO_RECTANGLE },
|
||
]"
|
||
@click="setSelectionType(OperationType.LASSO_RECTANGLE)"
|
||
>
|
||
<svg-icon name="CRectangle" size="32" />
|
||
<span>{{ $t("矩形") }}</span>
|
||
</div>
|
||
<div
|
||
:class="[
|
||
'tool-btn',
|
||
{ active: selectionType === OperationType.LASSO_ELLIPSE },
|
||
]"
|
||
@click="setSelectionType(OperationType.LASSO_ELLIPSE)"
|
||
>
|
||
<svg-icon name="CEllipse" size="30" />
|
||
<span>{{ $t("椭圆") }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分割线 -->
|
||
<div class="toolbar-divider"></div>
|
||
|
||
<!-- 底部选区操作工具栏 -->
|
||
<div class="tool-actions">
|
||
<div class="action-btn" @click="copySelectionToNewLayer">
|
||
<svg-icon name="CPaste" size="20" />
|
||
<span class="btn-text">{{ $t("创建") }}</span>
|
||
</div>
|
||
<div class="action-btn" @click="cutSelectionToNewLayer">
|
||
<svg-icon name="CCut" size="30" />
|
||
<span class="btn-text">{{ $t("创建并拷贝") }}</span>
|
||
</div>
|
||
<div class="action-btn" @click="clearSelectionContent">
|
||
<svg-icon name="CClear" size="22" />
|
||
<span class="btn-text">{{ $t("清除选择内容") }}</span>
|
||
</div>
|
||
<!-- <button
|
||
class="action-btn"
|
||
@click="addSelection"
|
||
:disabled="!hasSelection"
|
||
title="添加"
|
||
>
|
||
<svg-icon name="plus" />
|
||
<span class="btn-text">{{ $t("添加") }}</span>
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="removeSelection"
|
||
:disabled="!hasSelection"
|
||
title="移除"
|
||
>
|
||
<svg-icon name="minus" />
|
||
<span class="btn-text">{{ $t("移除") }}</span>
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="invertSelection"
|
||
:disabled="!hasSelection"
|
||
title="反转"
|
||
>
|
||
<svg-icon name="flip-horizontal" />
|
||
<span class="btn-text">{{ $t("反转") }}</span>
|
||
</button> -->
|
||
<!-- <button
|
||
class="action-btn"
|
||
@click="copySelectionToNewLayer"
|
||
:disabled="!hasSelection"
|
||
title="拷贝并粘贴"
|
||
>
|
||
<svg-icon name="copy" />
|
||
<span class="btn-text">{{ $t("拷贝并粘贴") }}</span>
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="openFeatherDialog"
|
||
:disabled="!hasSelection"
|
||
title="羽化"
|
||
>
|
||
<svg-icon name="feather" />
|
||
<span class="btn-text">{{ $t("羽化") }}</span>
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="fillSelection"
|
||
:disabled="!hasSelection"
|
||
title="颜色填充"
|
||
>
|
||
<svg-icon name="fill-color" />
|
||
<span class="btn-text">{{ $t("颜色填充") }}</span>
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
@click="clearSelection"
|
||
:disabled="!hasSelection"
|
||
title="清除"
|
||
>
|
||
<svg-icon name="trash" />
|
||
<span class="btn-text">{{ $t("清除") }}</span>
|
||
</button> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 羽化设置弹窗 -->
|
||
<div v-if="showFeatherDialog" class="dialog-overlay">
|
||
<div class="dialog-container">
|
||
<div class="dialog-header">
|
||
<h3>{{ $t("羽化") }}</h3>
|
||
<button class="close-dialog-btn" @click="cancelFeather">×</button>
|
||
</div>
|
||
<div class="dialog-content">
|
||
<div class="feather-control">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="50"
|
||
v-model.number="featherAmount"
|
||
class="slider-control"
|
||
/>
|
||
<div class="feather-value">{{ featherAmount }}px</div>
|
||
</div>
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="cancelFeather">
|
||
{{ $t("取消") }}
|
||
</button>
|
||
<button class="confirm-btn" @click="applyFeather">
|
||
{{ $t("确认") }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 颜色选择器 -->
|
||
<div v-if="showColorPicker" class="dialog-overlay">
|
||
<div class="dialog-container">
|
||
<div class="dialog-header">
|
||
<h3>{{ $t("选择填充颜色") }}</h3>
|
||
<button class="close-dialog-btn" @click="cancelColorPicker">
|
||
×
|
||
</button>
|
||
</div>
|
||
<div class="dialog-content">
|
||
<input type="color" v-model="fillColor" class="color-picker" />
|
||
<div class="dialog-buttons">
|
||
<button class="cancel-btn" @click="cancelColorPicker">
|
||
{{ $t("取消") }}
|
||
</button>
|
||
<button class="confirm-btn" @click="confirmColorPicker">
|
||
{{ $t("确认") }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, watch } from "vue";
|
||
import { useI18n } from "vue-i18n";
|
||
import {
|
||
CreateSelectionCommand,
|
||
InvertSelectionCommand,
|
||
FeatherSelectionCommand,
|
||
FillSelectionCommand,
|
||
// CopySelectionToNewLayerCommand,
|
||
// ClearSelectionContentCommand,
|
||
} from "../commands/SelectionCommands";
|
||
import { ToolCommand } from "../commands/ToolCommands";
|
||
import {
|
||
LassoCutoutCommand,
|
||
ClearSelectionCommand,
|
||
// CutSelectionToNewLayerCommand,
|
||
} from "../commands/LassoCutoutCommand";
|
||
|
||
import { OperationType } from "../utils/layerHelper";
|
||
import { ClearSelectionContentCommand } from "../commands/ClearSelectionContentCommand";
|
||
import { CutSelectionToNewLayerCommand } from "../commands/CutSelectionToNewLayerCommand";
|
||
|
||
const props = defineProps({
|
||
canvas: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
commandManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
selectionManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
layerManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
toolManager: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
activeTool: {
|
||
type: String,
|
||
required: false,
|
||
default: null,
|
||
},
|
||
});
|
||
|
||
// 响应式数据
|
||
const visible = ref(false);
|
||
const selectionType = ref("rectangle");
|
||
const featherAmount = ref(0);
|
||
const fillColor = ref("#000000");
|
||
const hasSelection = ref(false);
|
||
const showFeatherDialog = ref(false);
|
||
const showColorPicker = ref(false);
|
||
|
||
// 国际化
|
||
const { t } = useI18n();
|
||
|
||
onMounted(() => {
|
||
// 为选区管理器添加监听,以便在选区变化时更新状态
|
||
if (props.selectionManager) {
|
||
// 在选区管理器中添加选区变化的监听
|
||
checkSelectionStatus();
|
||
|
||
// 设置选区状态变化的回调
|
||
props.selectionManager.onSelectionChanged = () => {
|
||
checkSelectionStatus();
|
||
};
|
||
}
|
||
});
|
||
|
||
// 监听 activeTool 变化
|
||
watch(
|
||
() => props.activeTool,
|
||
(newTool) => {
|
||
// 当工具为LASSO或AREA类型时显示选区面板
|
||
const selectionTools = [
|
||
OperationType.LASSO,
|
||
OperationType.LASSO_RECTANGLE,
|
||
OperationType.LASSO_ELLIPSE,
|
||
];
|
||
|
||
if (selectionTools.includes(newTool)) {
|
||
show();
|
||
// 根据工具类型设置选区类型
|
||
selectionType.value = newTool;
|
||
|
||
// 更新选区管理器的选区类型
|
||
if (props.selectionManager) {
|
||
props.selectionManager.setSelectionType(selectionType.value);
|
||
props.selectionManager.setupSelectionEvents();
|
||
}
|
||
} else {
|
||
close();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
/**
|
||
* 显示面板
|
||
*/
|
||
function show() {
|
||
visible.value = true;
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 关闭面板
|
||
*/
|
||
function close() {
|
||
visible.value = false;
|
||
}
|
||
|
||
/**
|
||
* 设置选区类型
|
||
*/
|
||
function setSelectionType(type) {
|
||
selectionType.value = type;
|
||
|
||
// 通过 ToolManager 切换工具,这会自动通知 SelectionManager
|
||
if (props.toolManager) {
|
||
props.toolManager.setToolWithCommand(type);
|
||
}
|
||
|
||
// 备用方案:如果没有 toolManager,直接更新 selectionManager
|
||
else if (props.selectionManager) {
|
||
props.selectionManager.setSelectionType(type);
|
||
props.selectionManager.setupSelectionEvents();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查选区状态
|
||
*/
|
||
function checkSelectionStatus() {
|
||
hasSelection.value =
|
||
props.selectionManager &&
|
||
props.selectionManager.getSelectionObject() !== null;
|
||
|
||
// 同步羽化值
|
||
if (hasSelection.value) {
|
||
featherAmount.value = props.selectionManager.getFeatherAmount();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 添加选区
|
||
*/
|
||
function addSelection() {
|
||
// TODO: 实现添加选区功能
|
||
console.log("添加选区功能尚未实现");
|
||
}
|
||
|
||
/**
|
||
* 移除选区
|
||
*/
|
||
function removeSelection() {
|
||
// TODO: 实现移除选区功能
|
||
console.log("移除选区功能尚未实现");
|
||
}
|
||
|
||
/**
|
||
* 反转选区
|
||
*/
|
||
function invertSelection() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new InvertSelectionCommand({
|
||
canvas: props.canvas,
|
||
selectionManager: props.selectionManager,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 清除选区
|
||
*/
|
||
function clearSelection() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new ClearSelectionCommand({
|
||
selectionManager: props.selectionManager,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 应用羽化效果
|
||
*/
|
||
function applyFeather() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new FeatherSelectionCommand({
|
||
selectionManager: props.selectionManager,
|
||
featherAmount: featherAmount.value,
|
||
})
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 填充选区
|
||
*/
|
||
function fillSelection() {
|
||
if (!hasSelection.value) return;
|
||
showColorPicker.value = true;
|
||
}
|
||
|
||
/**
|
||
* 套索抠图到新图层
|
||
*/
|
||
function copySelectionToNewLayer() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new LassoCutoutCommand({
|
||
canvas: props.canvas,
|
||
layerManager: props.layerManager,
|
||
selectionManager: props.selectionManager,
|
||
toolManager: props.toolManager,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 剪切选区到新图层
|
||
*/
|
||
function cutSelectionToNewLayer() {
|
||
if (!hasSelection.value) return;
|
||
props.commandManager.execute(
|
||
new CutSelectionToNewLayerCommand({
|
||
canvas: props.canvas,
|
||
layerManager: props.layerManager,
|
||
selectionManager: props.selectionManager,
|
||
toolManager: props.toolManager,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 清除选区内容
|
||
*/
|
||
function clearSelectionContent() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new ClearSelectionContentCommand({
|
||
canvas: props.canvas,
|
||
layerManager: props.layerManager,
|
||
selectionManager: props.selectionManager,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
}
|
||
|
||
/**
|
||
* 打开羽化设置弹窗
|
||
*/
|
||
function openFeatherDialog() {
|
||
showFeatherDialog.value = true;
|
||
}
|
||
|
||
/**
|
||
* 取消羽化设置
|
||
*/
|
||
function cancelFeather() {
|
||
showFeatherDialog.value = false;
|
||
}
|
||
|
||
/**
|
||
* 确认羽化设置
|
||
*/
|
||
function confirmFeather() {
|
||
applyFeather();
|
||
showFeatherDialog.value = false;
|
||
}
|
||
|
||
/**
|
||
* 取消颜色选择
|
||
*/
|
||
function cancelColorPicker() {
|
||
showColorPicker.value = false;
|
||
}
|
||
|
||
/**
|
||
* 确认颜色选择
|
||
*/
|
||
function confirmColorPicker() {
|
||
if (!hasSelection.value) return;
|
||
|
||
props.commandManager.execute(
|
||
new FillSelectionCommand({
|
||
canvas: props.canvas,
|
||
layerManager: props.layerManager,
|
||
selectionManager: props.selectionManager,
|
||
color: fillColor.value,
|
||
})
|
||
);
|
||
checkSelectionStatus();
|
||
showColorPicker.value = false;
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.selection-toolbar {
|
||
position: absolute;
|
||
bottom: 22px;
|
||
left: 20px;
|
||
right: 20px;
|
||
max-width: min(90vw, 640px);
|
||
margin: 0 auto;
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(15px);
|
||
-webkit-backdrop-filter: blur(15px);
|
||
border-radius: 8px;
|
||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||
z-index: 1000;
|
||
color: #333;
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
user-select: none;
|
||
}
|
||
|
||
/* 平板和手机适配 */
|
||
@media screen and (max-width: 768px) {
|
||
.selection-toolbar {
|
||
bottom: 15px;
|
||
left: 15px;
|
||
right: 15px;
|
||
max-width: calc(100vw - 30px);
|
||
border-radius: 6px;
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 480px) {
|
||
.selection-toolbar {
|
||
bottom: 10px;
|
||
left: 10px;
|
||
right: 10px;
|
||
max-width: calc(100vw - 20px);
|
||
}
|
||
}
|
||
|
||
.selection-toolbar.is-active {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.toolbar-header {
|
||
// display: flex;
|
||
// justify-content: center;
|
||
// align-items: center;
|
||
padding: 8px 15px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
border-radius: 8px 8px 0 0;
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #333;
|
||
text-align: left;
|
||
}
|
||
.header-btn {
|
||
background: none;
|
||
border: none;
|
||
color: #333;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
padding: 3px 6px;
|
||
border-radius: 3px;
|
||
transition: background-color 0.2s ease;
|
||
min-width: 32px;
|
||
}
|
||
|
||
.header-btn:hover {
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.close-btn {
|
||
color: #666;
|
||
}
|
||
|
||
.toolbar-section {
|
||
padding: 0 0 10px;
|
||
}
|
||
|
||
.tool-types {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
padding: 10px;
|
||
}
|
||
|
||
/* 平板适配 */
|
||
@media screen and (max-width: 768px) {
|
||
.tool-types {
|
||
gap: 6px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.toolbar-header {
|
||
padding: 6px 12px;
|
||
border-radius: 6px 6px 0 0;
|
||
}
|
||
}
|
||
|
||
/* 手机适配 */
|
||
@media screen and (max-width: 480px) {
|
||
.tool-types {
|
||
gap: 4px;
|
||
padding: 6px;
|
||
}
|
||
|
||
.toolbar-header {
|
||
padding: 5px 10px;
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.tool-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 10px 5px;
|
||
color: #333;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.tool-btn span {
|
||
margin-top: 6px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.tool-btn svg {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.tool-btn:hover {
|
||
background-color: rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.tool-btn.active {
|
||
background-color: #007aff;
|
||
color: white;
|
||
}
|
||
|
||
.toolbar-divider {
|
||
height: 1px;
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
margin: 0 10px 10px;
|
||
}
|
||
|
||
.tool-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 5px;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
/* 平板适配 - 每行4个按钮 */
|
||
@media screen and (max-width: 768px) {
|
||
.tool-actions {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px 6px;
|
||
padding: 0 8px;
|
||
}
|
||
}
|
||
|
||
/* 手机适配 - 每行3个按钮 */
|
||
@media screen and (max-width: 480px) {
|
||
.tool-actions {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 6px 4px;
|
||
padding: 0 6px;
|
||
}
|
||
|
||
.header-btn {
|
||
font-size: 11px;
|
||
padding: 2px 4px;
|
||
min-width: 28px;
|
||
}
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background: none;
|
||
border: none;
|
||
color: #333;
|
||
cursor: pointer;
|
||
padding: 8px 2px;
|
||
}
|
||
|
||
.action-btn svg {
|
||
width: 22px;
|
||
height: 22px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.btn-text {
|
||
font-size: 14px;
|
||
text-align: center;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
color: #007aff;
|
||
}
|
||
|
||
.action-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* 对话框样式 */
|
||
.dialog-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
backdrop-filter: blur(5px);
|
||
-webkit-backdrop-filter: blur(5px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2000;
|
||
}
|
||
|
||
.dialog-container {
|
||
background-color: #ffffff;
|
||
border-radius: 12px;
|
||
width: 280px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||
overflow: hidden;
|
||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.dialog-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 15px;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.dialog-header h3 {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.close-dialog-btn {
|
||
background: none;
|
||
border: none;
|
||
color: #666;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.dialog-content {
|
||
padding: 15px;
|
||
}
|
||
|
||
.feather-control {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.slider-control {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: rgba(0, 0, 0, 0.1);
|
||
border-radius: 2px;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.slider-control::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: #007aff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.feather-value {
|
||
font-size: 14px;
|
||
color: #333;
|
||
min-width: 40px;
|
||
text-align: right;
|
||
}
|
||
|
||
.dialog-buttons {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.confirm-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: rgba(0, 0, 0, 0.05);
|
||
color: #333;
|
||
}
|
||
|
||
.confirm-btn {
|
||
background-color: #007aff;
|
||
color: white;
|
||
}
|
||
|
||
.color-picker {
|
||
width: 100%;
|
||
height: 40px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: opacity 0.3s, transform 0.3s;
|
||
}
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(30px);
|
||
}
|
||
</style>
|