mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-01-30 03:03:25 +08:00
feat: add MulInputModal
This commit is contained in:
parent
18ad1332db
commit
3a8110171f
@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "azon-seeker",
|
||||
"displayName": "Azon Seeker",
|
||||
"version": "0.7.1",
|
||||
"displayName": "Azon Seeker v0.7.1.1-beta",
|
||||
"version": "0.7.2",
|
||||
"private": true,
|
||||
"description": "Starter modify by honestfox101",
|
||||
"description": "Starter modify by honestfox101 and PetrichorFun",
|
||||
"scripts": {
|
||||
"dev": "npm run clear && cross-env NODE_ENV=development run-p dev:prepare dev:web dev:background dev:js",
|
||||
"dev-firefox": "npm run clear-firefox && cross-env NODE_ENV=development EXTENSION=firefox run-p dev:prepare dev:web dev:background dev:js",
|
||||
|
||||
77
plans/MulInputModal_modification.md
Normal file
77
plans/MulInputModal_modification.md
Normal file
@ -0,0 +1,77 @@
|
||||
# MulInputModal.vue 修改计划
|
||||
|
||||
## 目标
|
||||
|
||||
修改 `src/sidepanel/views/components/MulInputModal.vue` 组件,使其支持从 Excel 复制一列值粘贴到输入框,并提供格式验证和确认后的消息通知。
|
||||
|
||||
## 需求详情
|
||||
|
||||
1. **输入格式**:每行一个值(换行分隔),不允许空行。
|
||||
2. **实时验证**:在用户输入时检查格式是否正确,并给出提示。
|
||||
3. **确认通知**:点击确认按钮后,显示 n-message 通知成功或失败。
|
||||
|
||||
## 当前组件分析
|
||||
|
||||
当前组件是一个简单的模态对话框,包含一个文本区域输入框。它通过 `v-model:show-modal` 控制显示,但没有处理输入值或验证逻辑。
|
||||
|
||||
## 设计修改
|
||||
|
||||
### 1. 模板修改
|
||||
|
||||
- 保留现有的 `n-modal` 和 `n-dialog` 结构。
|
||||
- 修改 `n-dialog` 属性:
|
||||
- 移除 `content` 属性(因为其内容与功能不符)。
|
||||
- 动态设置确认按钮的文本和状态:格式正确时显示“确认”,格式错误时显示“格式错误”并禁用按钮。
|
||||
- 增强 `n-input`:
|
||||
- 添加 `placeholder` 提示用户粘贴格式。
|
||||
- 设置 `autosize` 适应多行内容。
|
||||
- 绑定 `v-model:value` 到本地输入文本。
|
||||
- 添加验证提示区域:
|
||||
- 显示验证状态消息(成功/错误)。
|
||||
- 显示行数统计。
|
||||
|
||||
### 2. 脚本修改
|
||||
|
||||
- 导入必要的 Vue 和 Naive UI API。
|
||||
- 定义模型:
|
||||
- `showModal`:控制模态框显示。
|
||||
- `modelValue`:可选,用于双向绑定关键词数组(字符串数组)。
|
||||
- 定义本地响应式数据:
|
||||
- `inputText`:文本区域的绑定值。
|
||||
- 计算属性 `validation`:
|
||||
- 根据输入文本实时验证格式。
|
||||
- 返回 `valid`、`lines`(非空行数组)、`message`。
|
||||
- 验证规则:
|
||||
1. 输入不能为空(去除首尾空格后)。
|
||||
2. 不允许空行(任何一行 trim 后为空即无效)。
|
||||
3. 检测制表符(警告用户可能粘贴了多列)。
|
||||
- 确认处理函数 `handleConfirm`:
|
||||
- 检查验证结果,如果无效则显示错误消息并返回。
|
||||
- 如果有效,更新 `modelValue`,发出 `confirm` 事件,显示成功消息,关闭模态框。
|
||||
- 初始化:当 `modelValue` 变化时,将数组转换为换行分隔的文本并填充到输入框。
|
||||
|
||||
### 3. 样式修改
|
||||
|
||||
- 添加验证提示区域的样式,根据状态改变颜色。
|
||||
- 调整布局间距。
|
||||
|
||||
## 代码草案
|
||||
|
||||
详见 [MulInputModal.vue 草案](草案内容略)
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
- 添加的 `modelValue` 模型为可选,不影响现有使用(父组件可以不传递)。
|
||||
- 现有的 `showModal` 模型保持不变。
|
||||
- 新增的 `confirm` 事件可选,父组件可以监听以获取导入的行。
|
||||
|
||||
## 测试计划
|
||||
|
||||
1. 手动测试从 Excel 复制一列值并粘贴到输入框。
|
||||
2. 验证实时提示是否正确显示。
|
||||
3. 测试确认按钮在格式正确/错误时的行为。
|
||||
4. 测试 n-message 通知是否正常弹出。
|
||||
|
||||
## 下一步
|
||||
|
||||
完成详细设计后,切换到 Code 模式实施修改。
|
||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@ -988,7 +988,6 @@ packages:
|
||||
}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.45.1':
|
||||
resolution:
|
||||
@ -997,7 +996,6 @@ packages:
|
||||
}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1006,7 +1004,6 @@ packages:
|
||||
}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.45.1':
|
||||
resolution:
|
||||
@ -1015,7 +1012,6 @@ packages:
|
||||
}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1024,7 +1020,6 @@ packages:
|
||||
}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1033,7 +1028,6 @@ packages:
|
||||
}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1042,7 +1036,6 @@ packages:
|
||||
}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.45.1':
|
||||
resolution:
|
||||
@ -1051,7 +1044,6 @@ packages:
|
||||
}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1060,7 +1052,6 @@ packages:
|
||||
}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.45.1':
|
||||
resolution:
|
||||
@ -1069,7 +1060,6 @@ packages:
|
||||
}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.45.1':
|
||||
resolution:
|
||||
@ -1078,7 +1068,6 @@ packages:
|
||||
}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.45.1':
|
||||
resolution:
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
const openOptionsPage = async () => {
|
||||
await browser.runtime.openOptionsPage();
|
||||
};
|
||||
const emit = defineEmits<{
|
||||
(e: 'clickMulInput', event: MouseEvent): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -15,10 +18,20 @@ const openOptionsPage = async () => {
|
||||
</template>
|
||||
<template #default>数据</template>
|
||||
</n-button>
|
||||
<n-button class="setting-button" round @click="$emit('clickMulInput', $el)" size="small">
|
||||
<template #icon>
|
||||
<n-icon size="18" color="#0f0f0f">
|
||||
<stash:list-add />
|
||||
</n-icon>
|
||||
</template>
|
||||
<template #default>输入</template>
|
||||
</n-button>
|
||||
</div>
|
||||
<n-space class="app-title">
|
||||
<mdi-cat style="font-size: 60px; color: black" />
|
||||
<h1><slot></slot></h1>
|
||||
<h1>
|
||||
<slot></slot>
|
||||
</h1>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
@ -40,6 +53,7 @@ const openOptionsPage = async () => {
|
||||
.setting-button {
|
||||
margin: 12px 20px 0 0;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@ -14,5 +14,7 @@ export function isForbiddenUrl(url: string): boolean {
|
||||
|
||||
export const isFirefox = navigator.userAgent.includes('Firefox');
|
||||
|
||||
export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '47.251.4.191:8000';
|
||||
// export const remoteHost = __DEV__ ? '127.0.0.1:8000' : '47.251.4.191:8000';
|
||||
export const remoteHost = __DEV__ ? '127.0.0.1:18000' : 'vm8nc3zr-18000.usw2.devtunnels.ms';
|
||||
|
||||
// export const remoteHost = '47.251.4.191:8000';
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import { keywordsList } from '~/storages/amazon';
|
||||
import type { Timeline } from '~/components/ProgressReport.vue';
|
||||
import { usePageWorker } from '~/page-worker';
|
||||
|
||||
import MulInputModal from '~/sidepanel/views/components/MulInputModal.vue';
|
||||
const message = useMessage();
|
||||
const showModal = ref(false);
|
||||
|
||||
//#region Initial Page Worker
|
||||
const worker = usePageWorker('amazon', { objects: ['search'] });
|
||||
@ -72,23 +73,35 @@ const handleInterrupt = () => {
|
||||
worker.stop();
|
||||
message.info('已触发中断,正在等待当前任务完成。', { duration: 2000 });
|
||||
};
|
||||
const clickInputButton = (e: MouseEvent) => {
|
||||
showModal.value = true;
|
||||
};
|
||||
|
||||
const handleModalConfirm = (keys: string[]) => {
|
||||
keywordsList.value = keys;
|
||||
// console.log(keys);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-page-entry">
|
||||
<header-title>Amazon Search</header-title>
|
||||
<header-title @click-mul-input="clickInputButton">Amazon Search</header-title>
|
||||
<div class="interactive-section">
|
||||
<n-dynamic-input
|
||||
:disabled="worker.isRunning.value"
|
||||
v-model:value="keywordsList"
|
||||
:min="1"
|
||||
:max="10"
|
||||
class="search-input-box"
|
||||
autosize
|
||||
size="large"
|
||||
round
|
||||
placeholder="请输入关键词采集信息"
|
||||
/>
|
||||
<n-infinite-scroll style="max-height: 60vh; padding-right: 16px" :distance="10">
|
||||
<n-dynamic-input
|
||||
:disabled="worker.isRunning.value"
|
||||
v-model:value="keywordsList"
|
||||
:min="1"
|
||||
:max="999"
|
||||
class="search-input-box"
|
||||
autosize
|
||||
size="large"
|
||||
round
|
||||
placeholder="请输入关键词采集信息"
|
||||
/>
|
||||
</n-infinite-scroll>
|
||||
<!-- <n-dynamic-input :disabled="worker.isRunning.value" v-model:value="keywordsList" :min="1" :max="999"
|
||||
class="search-input-box" autosize size="large" round placeholder="请输入关键词采集信息" /> -->
|
||||
<n-button
|
||||
v-if="!worker.isRunning.value"
|
||||
type="primary"
|
||||
@ -117,6 +130,7 @@ const handleInterrupt = () => {
|
||||
</div>
|
||||
<progress-report class="progress-report" :timelines="timelines" />
|
||||
</div>
|
||||
<MulInputModal v-model:show-modal="showModal" @confirm="handleModalConfirm"> </MulInputModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
146
src/sidepanel/views/components/MulInputModal.vue
Normal file
146
src/sidepanel/views/components/MulInputModal.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<n-modal
|
||||
style="width: 80vw"
|
||||
title="确认"
|
||||
positive-text="确认"
|
||||
negative-text="算了"
|
||||
v-model:show="showModal"
|
||||
>
|
||||
<n-dialog
|
||||
title="输入关键词"
|
||||
:positive-text="validation.valid ? '确认' : '格式错误'"
|
||||
negative-text="取消"
|
||||
@positive-click="handleConfirm"
|
||||
@negative-click="handleCancel"
|
||||
:positive-button-props="{
|
||||
type: validation.valid ? 'primary' : 'error',
|
||||
disabled: !validation.valid,
|
||||
}"
|
||||
@close="handleCancel"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="inputText"
|
||||
type="textarea"
|
||||
placeholder="请从 Excel 复制一列值粘贴到这里,每行一个关键词,不允许空行"
|
||||
:autosize="{ minRows: 8, maxRows: 20 }"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<div class="validation-hint" :class="{ valid: validation.valid, invalid: !validation.valid }">
|
||||
<n-text :type="validation.valid ? 'success' : 'error'">
|
||||
{{ validation.message }}
|
||||
</n-text>
|
||||
<n-text v-if="validation.valid" depth="3" class="line-count">
|
||||
共 {{ validation.lines.length }} 行
|
||||
</n-text>
|
||||
</div>
|
||||
</n-dialog>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
const showModal = defineModel<boolean>('showModal', { default: false });
|
||||
const props = defineProps<{
|
||||
initialValue?: string[];
|
||||
}>();
|
||||
|
||||
const inputText = ref('');
|
||||
|
||||
// 验证逻辑
|
||||
const validation = computed(() => {
|
||||
const text = inputText.value;
|
||||
const lines = text.split('\n').map((line) => line.trim());
|
||||
const nonEmptyLines = lines.filter((line) => line.length > 0);
|
||||
|
||||
if (text.trim() === '') {
|
||||
return { valid: false, lines: [], message: '输入不能为空' };
|
||||
}
|
||||
|
||||
// 检查是否存在空行(trim 后为空)
|
||||
const hasEmptyLine = lines.some((line) => line.length === 0);
|
||||
if (hasEmptyLine) {
|
||||
return { valid: false, lines: [], message: '存在空行,请删除空行' };
|
||||
}
|
||||
|
||||
// 检查是否包含制表符(可能误粘贴多列)
|
||||
const hasTab = lines.some((line) => line.includes('\t'));
|
||||
if (hasTab) {
|
||||
return { valid: false, lines: [], message: '检测到制表符,请确保每行只有一个值' };
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
lines: nonEmptyLines,
|
||||
message: '格式正确',
|
||||
};
|
||||
});
|
||||
|
||||
const handleInput = () => {
|
||||
// 实时验证,无需额外操作
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!validation.value.valid) {
|
||||
message.error('格式错误:' + validation.value.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = validation.value.lines;
|
||||
// 发出确认事件
|
||||
emit('confirm', lines);
|
||||
// 显示成功消息
|
||||
message.success(`成功导入 ${lines.length} 个关键词`);
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [lines: string[]];
|
||||
}>();
|
||||
|
||||
watch(
|
||||
showModal,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
// 关闭时重置输入
|
||||
inputText.value = '';
|
||||
} else {
|
||||
// 打开时如果有初始值,设置输入文本
|
||||
if (props.initialValue && props.initialValue.length > 0) {
|
||||
inputText.value = props.initialValue.join('\n');
|
||||
} else {
|
||||
inputText.value = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.validation-hint {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.validation-hint.valid {
|
||||
color: var(--n-color-success);
|
||||
}
|
||||
|
||||
.validation-hint.invalid {
|
||||
color: var(--n-color-error);
|
||||
}
|
||||
|
||||
.line-count {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user