mirror of
https://github.com/primedigitaltech/azon_seeker.git
synced 2026-02-07 07:43:12 +08:00
feat: add MulInputModal
This commit is contained in:
parent
18ad1332db
commit
3a8110171f
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "azon-seeker",
|
"name": "azon-seeker",
|
||||||
"displayName": "Azon Seeker",
|
"displayName": "Azon Seeker v0.7.1.1-beta",
|
||||||
"version": "0.7.1",
|
"version": "0.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Starter modify by honestfox101",
|
"description": "Starter modify by honestfox101 and PetrichorFun",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run clear && cross-env NODE_ENV=development run-p dev:prepare dev:web dev:background dev:js",
|
"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",
|
"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]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.45.1':
|
'@rollup/rollup-linux-arm-musleabihf@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -997,7 +996,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.45.1':
|
'@rollup/rollup-linux-arm64-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1006,7 +1004,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.45.1':
|
'@rollup/rollup-linux-arm64-musl@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1015,7 +1012,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loongarch64-gnu@4.45.1':
|
'@rollup/rollup-linux-loongarch64-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1024,7 +1020,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.45.1':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1033,7 +1028,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.45.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1042,7 +1036,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.45.1':
|
'@rollup/rollup-linux-riscv64-musl@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1051,7 +1044,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.45.1':
|
'@rollup/rollup-linux-s390x-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1060,7 +1052,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.45.1':
|
'@rollup/rollup-linux-x64-gnu@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1069,7 +1060,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.45.1':
|
'@rollup/rollup-linux-x64-musl@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
@ -1078,7 +1068,6 @@ packages:
|
|||||||
}
|
}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.45.1':
|
'@rollup/rollup-win32-arm64-msvc@4.45.1':
|
||||||
resolution:
|
resolution:
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
const openOptionsPage = async () => {
|
const openOptionsPage = async () => {
|
||||||
await browser.runtime.openOptionsPage();
|
await browser.runtime.openOptionsPage();
|
||||||
};
|
};
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'clickMulInput', event: MouseEvent): void;
|
||||||
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -15,10 +18,20 @@ const openOptionsPage = async () => {
|
|||||||
</template>
|
</template>
|
||||||
<template #default>数据</template>
|
<template #default>数据</template>
|
||||||
</n-button>
|
</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>
|
</div>
|
||||||
<n-space class="app-title">
|
<n-space class="app-title">
|
||||||
<mdi-cat style="font-size: 60px; color: black" />
|
<mdi-cat style="font-size: 60px; color: black" />
|
||||||
<h1><slot></slot></h1>
|
<h1>
|
||||||
|
<slot></slot>
|
||||||
|
</h1>
|
||||||
</n-space>
|
</n-space>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -40,6 +53,7 @@ const openOptionsPage = async () => {
|
|||||||
.setting-button {
|
.setting-button {
|
||||||
margin: 12px 20px 0 0;
|
margin: 12px 20px 0 0;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,5 +14,7 @@ export function isForbiddenUrl(url: string): boolean {
|
|||||||
|
|
||||||
export const isFirefox = navigator.userAgent.includes('Firefox');
|
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';
|
// export const remoteHost = '47.251.4.191:8000';
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
import { keywordsList } from '~/storages/amazon';
|
import { keywordsList } from '~/storages/amazon';
|
||||||
import type { Timeline } from '~/components/ProgressReport.vue';
|
import type { Timeline } from '~/components/ProgressReport.vue';
|
||||||
import { usePageWorker } from '~/page-worker';
|
import { usePageWorker } from '~/page-worker';
|
||||||
|
import MulInputModal from '~/sidepanel/views/components/MulInputModal.vue';
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
const showModal = ref(false);
|
||||||
|
|
||||||
//#region Initial Page Worker
|
//#region Initial Page Worker
|
||||||
const worker = usePageWorker('amazon', { objects: ['search'] });
|
const worker = usePageWorker('amazon', { objects: ['search'] });
|
||||||
@ -72,23 +73,35 @@ const handleInterrupt = () => {
|
|||||||
worker.stop();
|
worker.stop();
|
||||||
message.info('已触发中断,正在等待当前任务完成。', { duration: 2000 });
|
message.info('已触发中断,正在等待当前任务完成。', { duration: 2000 });
|
||||||
};
|
};
|
||||||
|
const clickInputButton = (e: MouseEvent) => {
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalConfirm = (keys: string[]) => {
|
||||||
|
keywordsList.value = keys;
|
||||||
|
// console.log(keys);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="search-page-entry">
|
<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">
|
<div class="interactive-section">
|
||||||
<n-dynamic-input
|
<n-infinite-scroll style="max-height: 60vh; padding-right: 16px" :distance="10">
|
||||||
:disabled="worker.isRunning.value"
|
<n-dynamic-input
|
||||||
v-model:value="keywordsList"
|
:disabled="worker.isRunning.value"
|
||||||
:min="1"
|
v-model:value="keywordsList"
|
||||||
:max="10"
|
:min="1"
|
||||||
class="search-input-box"
|
:max="999"
|
||||||
autosize
|
class="search-input-box"
|
||||||
size="large"
|
autosize
|
||||||
round
|
size="large"
|
||||||
placeholder="请输入关键词采集信息"
|
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
|
<n-button
|
||||||
v-if="!worker.isRunning.value"
|
v-if="!worker.isRunning.value"
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -117,6 +130,7 @@ const handleInterrupt = () => {
|
|||||||
</div>
|
</div>
|
||||||
<progress-report class="progress-report" :timelines="timelines" />
|
<progress-report class="progress-report" :timelines="timelines" />
|
||||||
</div>
|
</div>
|
||||||
|
<MulInputModal v-model:show-modal="showModal" @confirm="handleModalConfirm"> </MulInputModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<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