将输入区域提取为组件

This commit is contained in:
mcallzbl 2025-04-02 18:41:17 +08:00
parent e8c034937e
commit 7b056d476d
13 changed files with 925 additions and 643 deletions

View File

@ -3,7 +3,7 @@ const dotenv = require('dotenv')
const packageJson = require('../../package.json')
const baseConfig = {
productName: packageJson.name,
productName: '文案助手',
appId: packageJson.appId,
asar: true,
extends: null,

View File

@ -1,27 +1,28 @@
{
"name": "lingtropy",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lingtropy",
"version": "1.0.0",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"@mdi/font": "^7.4.47",
"axios": "^1.8.3",
"electron-store": "^10.0.1",
"lingtropy": "file:",
"lingtropy2": "file:",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^11.1.1",
"vue-router": "^4.5.0",
"vuetify": "^3.7.14",
"vutron": "file:"
"vuetify": "^3.7.14"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/electron-store": "^3.2.2",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-vue": "^5.2.1",
@ -41,7 +42,7 @@
"playwright": "^1.50.1",
"prettier": "^3.5.2",
"tree-kill": "^1.2.2",
"typescript": "5.7.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
@ -1321,6 +1322,17 @@
"@types/ms": "*"
}
},
"node_modules/@types/electron-store": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/electron-store/-/electron-store-3.2.2.tgz",
"integrity": "sha512-N3X45mnsfnwmeZoXSZmeE7/Tne8kdbIKO1vQdbbEV04TzrMbWIeDVJJjnX2n5GH9O61zI612tet4s2jCZ55DXw==",
"deprecated": "This is a stub types definition. electron-store provides its own type definitions, so you do not need this installed.",
"dev": true,
"license": "MIT",
"dependencies": {
"electron-store": "*"
}
},
"node_modules/@types/eslint": {
"version": "8.56.11",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
@ -6333,6 +6345,10 @@
"resolved": "",
"link": true
},
"node_modules/lingtropy2": {
"resolved": "",
"link": true
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -8697,9 +8713,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@ -9151,10 +9167,6 @@
}
}
},
"node_modules/vutron": {
"resolved": "",
"link": true
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@ -1,7 +1,7 @@
{
"name": "lingtropy",
"appId": "com.lingnite.lingtropy",
"version": "1.0.0",
"version": "1.0.1",
"description": "文案助手",
"homepage": "https://www.lingnite.com",
"author": "沈阳泠启网络科技有限公司",
@ -45,6 +45,8 @@
"@mdi/font": "^7.4.47",
"axios": "^1.8.3",
"electron-store": "^10.0.1",
"lingtropy": "file:",
"lingtropy2": "file:",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^11.1.1",
@ -53,6 +55,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/electron-store": "^3.2.2",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-vue": "^5.2.1",
@ -72,7 +75,7 @@
"playwright": "^1.50.1",
"prettier": "^3.5.2",
"tree-kill": "^1.2.2",
"typescript": "5.7.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",

View File

@ -3,7 +3,7 @@ import Constants from './utils/Constants'
import Store from 'electron-store'
import axios from 'axios'
import OpenAI from 'openai'
import { HttpsProxyAgent } from 'https-proxy-agent'
const store = new Store()
/*
* IPC Communications
@ -20,6 +20,7 @@ const store = new Store()
// }
// }
// const baseUrl = ""
export default class IPCs {
static initialize(): void {
// Get application version

View File

@ -10,13 +10,14 @@ import { createTray, hideWindow, showWindow } from './tray.ts'
const options = {
width: Constants.IS_DEV_ENV ? 1500 : 1250,
height: 850,
height: 900,
tray: {
// all optional values from DEFAULT_TRAY_OPTIONS can de defined here
enabled: true,
menu: false, // true, to use a tray menu ; false to toggle visibility on click on tray icon
trayWindow: false // true, to use a tray floating window attached to top try icon
}
},
title: '文案助手'
}
const exitApp = (mainWindow: BrowserWindow): void => {

View File

@ -19,6 +19,7 @@ app.on('ready', async () => {
*/
mainWindow = await createMainWindow()
// mainWindow.setTile("23")
})
app.on('activate', async () => {

View File

@ -15,7 +15,7 @@ export interface TrayOptions {
export default class Constants {
// Display app name (uppercase first letter)
static APP_NAME = name.charAt(0).toUpperCase() + name.slice(1)
static APP_NAME = '文案助手' //name.charAt(0).toUpperCase() + name.slice(1)
static APP_VERSION = version

View File

@ -0,0 +1,193 @@
<template>
<!-- 免责声明弹窗 -->
<div v-if="showDisclaimer" class="disclaimer-modal">
<div class="disclaimer-content">
<h2 class="disclaimer-title">免责声明</h2>
<div class="disclaimer-text">
<p>您正在使用本软件请您仔细阅读以下内容并确认</p>
<ol>
<li
>根据小红书平台的相关要求通过反复发布重复近似交易笔记或发布大量发布工业化低质量创作的交易笔记等方式获取流量的行为系违规行为</li
>
<li>本软件充分尊重原创者的著作权等知识产权仅为用户提供参考素材与灵感</li>
<li
>本软件郑重提醒用户请各用户遵守知识产权相关规定秉持真诚分享精神自行对在小红书平台及其他社交媒体平台发布的内容进行判断负责不要直接搬运原创作品严禁用于辅助违规后端操作引流</li
>
<li>对用户违反本声明所产生的后果炬梦私域与泠启科技不承担任何责任</li>
<li>本软件使用的ai仅供内部测试严禁生成违背各种法律法规或公序良俗的内容</li>
</ol>
<p>继续使用本工具即表示您已阅读并同意上述声明</p>
</div>
<div class="disclaimer-actions">
<label class="disclaimer-checkbox">
<input type="checkbox" v-model="doNotShowAgain" />
<span>不再显示</span>
</label>
<button @click="acceptDisclaimer" class="disclaimer-button">我已阅读并同意</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
//
const showDisclaimer = ref(false)
const doNotShowAgain = ref(false)
//
const checkDisclaimerStatus = async () => {
try {
const hideDisclaimer = await window.mainApi.invoke('store-get', 'hideDisclaimer')
showDisclaimer.value = !hideDisclaimer
// showDisclaimer.value = true
// console.log(':', showDisclaimer.value)
} catch (err) {
// console.error(':', err)
showDisclaimer.value = true //
}
}
//
const acceptDisclaimer = async () => {
if (doNotShowAgain.value) {
try {
await window.mainApi.invoke('store-set', 'hideDisclaimer', true)
} catch (err) {
console.error('保存免责声明偏好失败:', err)
}
}
showDisclaimer.value = false
}
onMounted(() => {
checkDisclaimerStatus()
})
</script>
<style scoped>
.disclaimer-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 90;
animation: fadeIn 0.3s ease;
}
.disclaimer-content {
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
padding: 30px;
position: relative;
}
.disclaimer-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 20px 0;
color: #2ecc71;
text-align: center;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 15px;
}
.disclaimer-text {
margin-bottom: 25px;
line-height: 1.6;
color: #333;
}
.disclaimer-text p {
margin-bottom: 15px;
}
.disclaimer-text ol {
padding-left: 20px;
margin-bottom: 15px;
}
.disclaimer-text li {
margin-bottom: 10px;
}
.disclaimer-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
border-top: 1px solid #f0f0f0;
padding-top: 20px;
}
.disclaimer-checkbox {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
}
.disclaimer-checkbox input {
width: 18px;
height: 18px;
}
.disclaimer-button {
background: linear-gradient(135deg, #2ecc71, #27ae60);
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
font-size: 16px;
font-weight: 600;
padding: 12px 24px;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
}
.disclaimer-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(46, 204, 113, 0.4);
}
@media (max-width: 1200px) {
.disclaimer-content {
width: 95%;
padding: 20px;
}
}
@media (max-width: 768px) {
.disclaimer-actions {
flex-direction: column;
gap: 15px;
}
.disclaimer-checkbox {
margin-bottom: 10px;
}
.disclaimer-button {
width: 100%;
}
}
@media (max-width: 480px) {
.disclaimer-content {
padding: 15px;
}
.disclaimer-title {
font-size: 20px;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<div class="company-footer">
<div class="company-footer-content">
<p>炬梦私域X泠启科技</p>
</div>
</div>
</template>
<script setup>
//
</script>
<style scoped>
.company-footer {
position: fixed;
margin-top: 10px;
bottom: 0;
left: 0;
right: 0;
width: 100%;
padding: 15px 0;
text-align: center;
z-index: 100;
}
.company-footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.company-footer-content p {
margin: 0;
font-size: 24px;
font-weight: 800;
letter-spacing: 2px;
position: relative;
display: inline-block;
}
@media (max-width: 768px) {
.company-footer {
padding: 10px 0;
}
.company-footer-content p {
font-size: 17px;
}
}
</style>

View File

@ -12,7 +12,7 @@
<div class="logo-icon">AI</div>
</div>
<div class="header-content">
<h1 class="title">文案生成工具</h1>
<h1 class="title">文案助手</h1>
<p class="subtitle">智能改写提升文案质量</p>
</div>
<button @click="toggleSettings" class="settings-button">
@ -46,7 +46,7 @@
</div>
</div>
<div class="modal-footer">
<div class="copyright"> © 2025 沈阳泠启网络科技有限公司版权所有 </div>
<div class="copyright"> 仅供测试开发使用严禁对外销售 </div>
<div class="button-group">
<button @click="toggleSettings" class="cancel-button">取消</button>
<button @click="saveSettings" class="save-button">保存设置</button>

View File

@ -0,0 +1,212 @@
<template>
<div class="input-section">
<HeaderComponent v-model:tokenvalue="currentKey" />
<SelectModel v-model:modelvalue="currentModel" />
<div class="rewrite-mode-section">
<h2 class="section-title">
<span class="section-icon"></span>
改写模式
</h2>
<div class="mode-tabs">
<button
:class="{ active: mode === 'single' }"
@click="switchMode('single')"
class="mode-tab"
>
<span class="mode-icon">🔄</span>
单次改写
</button>
<button
:class="{ active: mode === 'batch' }"
@click="switchMode('batch')"
class="mode-tab"
>
<span class="mode-icon">🔀</span>
批量改写
</button>
</div>
<transition name="fade">
<div v-if="mode === 'batch'" class="generation-count">
<label for="count-input">生成数量:</label>
<div class="count-control">
<button
:disabled="generationCount <= 1"
@click="decrementCount"
class="count-button"
>-</button>
<input
id="count-input"
v-model="generationCount"
@input="validateInput"
type="number"
class="count-input"
/>
<button
:disabled="generationCount >= ARTICLE_MAX_COUNT"
@click="incrementCount"
class="count-button"
>+</button>
</div>
</div>
</transition>
</div>
<div class="original-text-section">
<h2 class="section-title">
<span class="section-icon">📝</span>
原始文案
<span v-if="originalText" class="char-count">{{ originalText.length }} </span>
</h2>
<textarea
v-model="originalText"
class="text-input"
placeholder="请在此输入需要改写的文本..."
></textarea>
</div>
<button :disabled="isGenerating" @click="handleRewrite" class="rewrite-button">
<span v-if="isGenerating" class="loading-spinner"></span>
<span v-else>{{ mode === 'single' ? '生成改写' : '批量生成' }}</span>
</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ARTICLE_MAX_COUNT } from '../constants/constants'
import HeaderComponent from './HeaderComponent.vue'
import SelectModel from './SelectModelComponent.vue'
const { isGenerating } = defineProps({
isGenerating: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['rewrite'])
const mode = ref('single')
const originalText = ref('')
const generationCount = ref(3)
const currentModel = ref('gpt-4o')
const currentKey = ref('')
onMounted(async () => {
//
const count = await window.mainApi.invoke('store-get', 'generationCount')
if (count) {
generationCount.value = count
}
})
const validateInput = () => {
if (generationCount.value < 1) {
setGenerationCount(1)
} else if (generationCount.value > ARTICLE_MAX_COUNT) {
setGenerationCount(ARTICLE_MAX_COUNT)
}
}
const setGenerationCount = async (value) => {
generationCount.value = value
await window.mainApi.invoke('store-set', 'generationCount', value)
}
const switchMode = (newMode) => {
if (mode.value !== newMode) {
mode.value = newMode
}
}
const incrementCount = () => {
if (generationCount.value < ARTICLE_MAX_COUNT) {
setGenerationCount(generationCount.value + 1)
}
}
const decrementCount = () => {
if (generationCount.value > 1) {
setGenerationCount(generationCount.value - 1)
}
}
const handleRewrite = () => {
if (!originalText.value.trim()) {
// emit
return
}
emit('rewrite', {
mode: mode.value,
text: originalText.value,
count: mode.value === 'single' ? 1 : generationCount.value,
model: currentModel.value,
key: currentKey.value
})
}
</script>
<style scoped>
.input-section {
flex: 0 0 40%;
max-width: 600px;
min-width: 320px;
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 28px;
display: flex;
flex-direction: column;
overflow-y: auto;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.95);
}
/* 其他样式从 TextRewritingTool.vue 复制相关的部分 */
.rewrite-mode-section {
margin-bottom: 24px;
}
.mode-tabs {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
/* ... 复制其他相关样式 ... */
@media (max-width: 1200px) {
.input-section {
flex: none;
width: 100%;
max-width: 100%;
}
}
@media (max-width: 768px) {
.input-section {
padding: 20px;
border-radius: 12px;
}
.mode-tabs {
flex-direction: column;
}
.generation-count {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
@media (max-width: 480px) {
.input-section {
padding: 16px;
}
}
</style>

View File

@ -1,82 +1,11 @@
<template>
<div class="text-rewriter-container">
<div class="text-rewriter-layout">
<!-- 左侧输入区域 -->
<div class="input-section">
<HeaderComponent v-model:tokenvalue="currentKey" />
<SelectModel v-model:modelvalue="currentModel" />
<div class="rewrite-mode-section">
<h2 class="section-title">
<span class="section-icon"></span>
改写模式
</h2>
<div class="mode-tabs">
<button
:class="{ active: mode === 'single' }"
@click="switchMode('single')"
class="mode-tab"
>
<span class="mode-icon">🔄</span>
单次改写
</button>
<button
:class="{ active: mode === 'batch' }"
@click="switchMode('batch')"
class="mode-tab"
>
<span class="mode-icon">🔀</span>
批量改写
</button>
</div>
<transition name="fade">
<div v-if="mode === 'batch'" class="generation-count">
<label for="count-input">生成数量:</label>
<div class="count-control">
<button
:disabled="generationCount <= 1"
@click="decrementCount"
class="count-button"
>-</button
>
<input
id="count-input"
v-model="generationCount"
@input="validateInput"
type="number"
class="count-input"
/>
<button
:disabled="generationCount >= ARTICLE_MAX_COUNT"
@click="incrementCount"
class="count-button"
>+</button
>
</div>
</div>
</transition>
</div>
<div class="original-text-section">
<h2 class="section-title">
<span class="section-icon">📝</span>
原始文案
<span v-if="originalText" class="char-count">{{ originalText.length }} </span>
</h2>
<textarea
v-model="originalText"
class="text-input"
placeholder="请在此输入需要改写的文本..."
></textarea>
</div>
<button :disabled="isGenerating" @click="rewriteText" class="rewrite-button">
<span v-if="isGenerating" class="loading-spinner"></span>
<span v-else>{{ mode === 'single' ? '生成改写' : '批量生成' }}</span>
</button>
</div>
<DisclaimerComponent />
<InputSection
:is-generating="isGenerating"
@rewrite="handleRewrite"
/>
<!-- 右侧结果区域 -->
<transition name="fade">
<div v-if="rewrittenText.length > 0 || isGenerating" class="result-section">
@ -104,7 +33,6 @@
</div>
<span class="model-badge">{{ currentModel }}</span>
<span class="count-badge">{{ rewrittenText.length }} 个结果</span>
</div>
</div>
@ -160,58 +88,23 @@
</div>
</transition>
</div>
<FooterComponent />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ARTICLE_MAX_COUNT } from '../constants/constants'
import HeaderComponent from '../components/HeaderComponent.vue'
import SelectModel from '../components/SelectModelComponent.vue'
import DisclaimerComponent from '../components/DisclaimerComponent.vue'
import InputSection from '../components/InputSection.vue'
import FooterComponent from '../components/FooterComponent.vue'
//
const mode = ref('single')
const originalText = ref('')
const rewrittenText = ref([])
const generationCount = ref(3)
const expandedResults = reactive({})
const isGenerating = ref(false)
const copiedIndex = ref(null)
const baseUrl = ref('https://www.apimini2.org/v1/')
const currentModel = ref('gpt-4o')
const currentKey = ref('')
const layoutMode = ref('list')
//
const validateInput = () => {
if (generationCount.value < 1) {
setGenerationCount(1)
} else if (generationCount.value > ARTICLE_MAX_COUNT) {
setGenerationCount(ARTICLE_MAX_COUNT)
}
}
const setGenerationCount = async (value) => {
generationCount.value = value
await window.mainApi.invoke('store-set', 'generationCount', value)
}
//
const toggleLayout = (newLayout) => {
layoutMode.value = newLayout
//
window.mainApi.invoke('store-set', 'layoutMode', newLayout).catch((err) => {
console.error('保存布局偏好失败:', err)
})
}
onMounted(async () => {
const count = await window.mainApi.invoke('store-get', 'generationCount')
generationCount.value = count || 3
console.log('generationCount', generationCount.value)
//
try {
const savedLayout = await window.mainApi.invoke('store-get', 'layoutMode')
if (savedLayout) {
@ -220,91 +113,48 @@ onMounted(async () => {
} catch (err) {
console.error('获取布局偏好失败:', err)
}
baseUrl.value = await window.mainApi.invoke('getBaseUrl')
console.log('baseUrl', baseUrl.value)
// currentKey.value =
// console.log("currentKey", currentKey.value)
})
//
const switchMode = (newMode) => {
if (mode.value !== newMode) {
mode.value = newMode
}
}
const incrementCount = () => {
if (generationCount.value < ARTICLE_MAX_COUNT) {
// generationCount.value++
setGenerationCount(generationCount.value + 1)
}
}
const decrementCount = () => {
if (generationCount.value > 1) {
setGenerationCount(generationCount.value - 1)
// generationCount.value--
}
}
function callOpenAI(apiKey, model, count, rawArticle) {
window.mainApi.send('call-openai', baseUrl, apiKey, model, count, rawArticle)
}
const rewriteText = () => {
if (!originalText.value.trim()) {
showToast('请输入需要改写的文本')
return
}
const handleRewrite = async ({ mode, text, count, model, key }) => {
//
Object.keys(expandedResults).forEach((key) => {
delete expandedResults[key]
})
console.log(`userToekn:${currentKey.value}`)
isGenerating.value = true
rewrittenText.value = []
// OpenAI API
callOpenAI(
currentKey.value, // API
currentModel.value, // 使
mode.value === 'single' ? 1 : generationCount.value, //
originalText.value //
)
try {
// baseUrl
const baseUrl = await window.mainApi.invoke('getBaseUrl')
//
window.mainApi.on('openai-partial-response', (_, { index, content }) => {
if (!rewrittenText.value[index]) {
rewrittenText.value[index] = ''
}
rewrittenText.value[index] += content
})
//
window.mainApi.on('openai-partial-response', (_, { index, content }) => {
// console.log(` ${index + 1} :`, content);
//
if (!rewrittenText.value[index]) {
rewrittenText.value[index] = ''
}
rewrittenText.value[index] += content
})
//
window.mainApi
.invoke(
// API
const results = await window.mainApi.invoke(
'call-openai',
baseUrl.value,
currentKey.value,
currentModel.value,
mode.value === 'single' ? 1 : generationCount.value,
originalText.value
baseUrl, // 使 baseUrl
key,
model,
count,
text
)
.then((results) => {
console.log('所有请求的最终结果:', results)
//
results.forEach(({ index, response }) => {
rewrittenText.value[index] = response
})
isGenerating.value = false
})
.catch((error) => {
console.error('调用 API 失败:', error)
showToast('调用 API 失败,请稍后重试')
isGenerating.value = false
results.forEach(({ index, response }) => {
rewrittenText.value[index] = response
})
} catch (error) {
console.error('调用 API 失败:', error)
showToast('调用 API 失败,请稍后重试')
} finally {
isGenerating.value = false
}
}
const copyText = (text, index) => {
@ -342,6 +192,15 @@ const showToast = (message) => {
}, 2000)
}, 10)
}
//
const toggleLayout = (newLayout) => {
layoutMode.value = newLayout
//
window.mainApi.invoke('store-set', 'layoutMode', newLayout).catch((err) => {
console.error('保存布局偏好失败:', err)
})
}
</script>
<style scoped>
@ -354,7 +213,8 @@ const showToast = (message) => {
align-items: flex-start;
padding: 20px;
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
/* background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); */
background: linear-gradient(135deg, #f0f4fd 0%, #d4e0f7 100%);
position: fixed;
top: 0;
left: 0;
@ -434,26 +294,11 @@ const showToast = (message) => {
gap: 24px;
width: 100%;
max-width: 1800px;
height: calc(100vh - 40px);
height: calc(100vh - 80px); /* Reduced height to make room for footer */
position: relative;
z-index: 1;
}
/* 左侧输入区域 */
.input-section {
flex: 0 0 40%;
max-width: 600px;
min-width: 320px;
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 28px;
display: flex;
flex-direction: column;
overflow-y: auto;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.95);
margin-bottom: 10px; /* Add margin to avoid content being hidden by footer */
/* padding-bottom: 10px; */
}
/* 右侧结果区域 */
@ -754,6 +599,12 @@ const showToast = (message) => {
/* 新增:网格布局中的项目样式 */
.result-block.grid-item {
height: 250px;
transition: height 0.5s ease;
}
.result-block.grid-item.expanded-result {
height: auto;
min-height: 250px;
}
.result-block:hover {
@ -1030,12 +881,6 @@ const showToast = (message) => {
flex-direction: column;
}
.input-section {
flex: none;
width: 100%;
max-width: 100%;
}
.result-section,
.empty-result-section {
width: 100%;
@ -1052,16 +897,10 @@ const showToast = (message) => {
padding: 12px;
}
/* 网格布局在平板上调整 */
.results-list.grid-layout {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.text-rewriter-layout {
gap: 16px;
}
.input-section,
.result-section,
.empty-result-section {
padding: 20px;
@ -1077,17 +916,6 @@ const showToast = (message) => {
align-items: flex-start;
gap: 12px;
}
/* .header-section {
flex-direction: column;
text-align: center;
}
.settings-button {
position: absolute;
top: 0;
right: 0;
} */
}
@media (max-width: 480px) {
@ -1095,7 +923,6 @@ const showToast = (message) => {
padding: 8px;
}
.input-section,
.result-section,
.empty-result-section {
padding: 16px;

File diff suppressed because it is too large Load Diff