1328 lines
38 KiB
Vue
1328 lines
38 KiB
Vue
<template>
|
||
<view class="clone-tool-container">
|
||
<!-- 上方日志展示区域 -->
|
||
<view class="log-section">
|
||
<view class="log-header">
|
||
<text class="log-title">日志输出</text>
|
||
<view class="log-actions">
|
||
<button class="btn-small" @click="clearLog">清空</button>
|
||
<button class="btn-small" @click="exportLog">导出</button>
|
||
</view>
|
||
</view>
|
||
<scroll-view class="log-content" scroll-y scroll-with-animation :scroll-top="scrollTop">
|
||
<view v-for="(log, index) in logs" :key="index" class="log-item">
|
||
<text>{{ log }}</text>
|
||
</view>
|
||
<view v-if="logs.length === 0" class="empty-log">
|
||
<text>暂无日志信息</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 下方控制按钮区域 -->
|
||
<view class="control-section">
|
||
<!-- 状态显示 -->
|
||
<view class="status-bar">
|
||
<text class="status-text">{{ status }}</text>
|
||
</view>
|
||
|
||
<!-- 主要功能按钮 -->
|
||
<view class="main-buttons">
|
||
<button class="function-btn account-btn" @click="showAccountModal = true">
|
||
<text class="btn-label">账号配置</text>
|
||
</button>
|
||
|
||
<button class="function-btn fetch-btn" @click="showFetchModal = true">
|
||
<text class="btn-label">拉取商品</text>
|
||
</button>
|
||
|
||
<button class="function-btn price-btn" @click="showPriceModal = true">
|
||
<text class="btn-label">价格配置</text>
|
||
</button>
|
||
|
||
|
||
|
||
<button class="function-btn process-btn" @click="showProcessModal = true">
|
||
<text class="btn-label">处理控制</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部状态栏 -->
|
||
<view class="status-bar">
|
||
<text>{{ progressText }}</text>
|
||
<text>{{ currentItemText }}</text>
|
||
</view>
|
||
|
||
<!-- 账号配置模态框 -->
|
||
<view v-if="showAccountModal" class="modal-overlay" @click="showAccountModal = false">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">账号配置</text>
|
||
<text class="modal-close" @click="showAccountModal = false">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="label">用户名:</text>
|
||
<input class="input" v-model="config.username" placeholder="请输入用户名" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">密码:</text>
|
||
<input class="input" v-model="config.password" type="password" placeholder="请输入密码" />
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="showAccountModal = false">取消</button>
|
||
<!-- <button class="btn-primary" @click="saveConfig">保存配置</button> -->
|
||
<button class="btn-primary" @click="login">登录</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 拉取商品模态框 -->
|
||
<view v-if="showFetchModal" class="modal-overlay" @click="showFetchModal = false">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">拉取商品设置</text>
|
||
<text class="modal-close" @click="showFetchModal = false">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="label">货号:</text>
|
||
<input class="input" v-model="fetchParams.itemSn" placeholder="请输入货号" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">价格范围:</text>
|
||
<view class="price-range-container">
|
||
<input class="input price-range-input" v-model="fetchParams.priceMin" placeholder="最低价格"
|
||
type="digit" inputmode="decimal" />
|
||
<text class="price-separator">-</text>
|
||
<input class="input price-range-input" v-model="fetchParams.priceMax" placeholder="最高价格"
|
||
type="digit" inputmode="decimal" />
|
||
</view>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">开始日期:</text>
|
||
<view class="date-input-container">
|
||
<picker mode="date" :value="startDateFormatted" @change="onStartDateChange"
|
||
class="date-picker">
|
||
<view class="picker-text">{{ fetchParams.startDate || '请选择开始日期' }}</view>
|
||
</picker>
|
||
<button v-if="fetchParams.startDate" class="clear-btn" @click="clearStartDate">×</button>
|
||
</view>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">结束日期:</text>
|
||
<view class="date-input-container">
|
||
<picker mode="date" :value="endDateFormatted" @change="onEndDateChange" class="date-picker">
|
||
<view class="picker-text">{{ fetchParams.endDate || '请选择结束日期' }}</view>
|
||
</picker>
|
||
<button v-if="fetchParams.endDate" class="clear-btn" @click="clearEndDate">×</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="showFetchModal = false">取消</button>
|
||
<button class="btn-primary" @click="confirmFetchItems" :disabled="fetching">
|
||
{{ fetching ? '拉取中...' : '开始拉取' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 价格配置模态框 -->
|
||
<view v-if="showPriceModal" class="modal-overlay" @click="showPriceModal = false">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">价格配置设置</text>
|
||
<text class="modal-close" @click="showPriceModal = false">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="form-item">
|
||
<text class="label">调整类型:</text>
|
||
<radio-group @change="onPriceTypeChange" class="radio-group">
|
||
<label class="radio-item">
|
||
<radio value="1" :checked="priceConfig.configType === 1" />折扣
|
||
</label>
|
||
<label class="radio-item">
|
||
<radio value="2" :checked="priceConfig.configType === 2" />加减值
|
||
</label>
|
||
<label class="radio-item">
|
||
<radio value="3" :checked="priceConfig.configType === 3" />指定金额
|
||
</label>
|
||
</radio-group>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">调整值:</text>
|
||
<view class="price-input-container">
|
||
<!-- 加减值类型显示正负号选择 -->
|
||
<picker v-if="priceConfig.configType === 2" mode="selector" :range="signOptions"
|
||
:value="signIndex" @change="onSignChange" class="sign-picker">
|
||
<view class="sign-text">{{ signOptions[signIndex] }}</view>
|
||
</picker>
|
||
<input class="input price-input" v-model="priceConfig.value"
|
||
:placeholder="getPlaceholderText()" type="digit" @input="onPriceValueInput"
|
||
@keypress="onKeyPress" inputmode="decimal" />
|
||
</view>
|
||
<!-- 根据配置类型显示不同的提示文字 -->
|
||
<text class="price-hint-text">
|
||
<text v-if="priceConfig.configType === 1">请输入0-100内的折扣数字</text>
|
||
<text v-else-if="priceConfig.configType === 2">选择加减符号并输入相应的数字进行调整值</text>
|
||
<text v-else-if="priceConfig.configType === 3">请输入纯数字</text>
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="showPriceModal = false">取消</button>
|
||
<button class="btn-primary" @click="confirmSavePriceConfig">保存配置</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 处理控制模态框 -->
|
||
<view v-if="showProcessModal" class="modal-overlay" @click="showProcessModal = false">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">处理控制</text>
|
||
<text class="modal-close" @click="showProcessModal = false">×</text>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="process-controls">
|
||
<button class="control-btn start-btn" @click="startProcessing" :disabled="processing">
|
||
<text class="btn-icon">▶️</text>
|
||
<text class="btn-text">开始处理</text>
|
||
</button>
|
||
<button class="control-btn pause-btn" @click="togglePause" :disabled="!processing">
|
||
<text class="btn-icon">{{ paused ? '▶️' : '⏸️' }}</text>
|
||
<text class="btn-text">{{ paused ? '继续' : '暂停' }}</text>
|
||
</button>
|
||
<button class="control-btn stop-btn" @click="stopProcessing" :disabled="!processing">
|
||
<text class="btn-icon">⏹️</text>
|
||
<text class="btn-text">停止</text>
|
||
</button>
|
||
</view>
|
||
<view class="process-info">
|
||
<text class="info-text">当前状态: {{ status }}</text>
|
||
<text class="info-text">处理进度: {{ progressText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="modal-footer">
|
||
<button class="btn-secondary" @click="showProcessModal = false">关闭</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import request from '@/utils/request.js';
|
||
import {
|
||
generateDeviceId,
|
||
validateDate,
|
||
dateToTimestamp,
|
||
adjustPrice,
|
||
formatLogMessage,
|
||
saveToStorage,
|
||
loadFromStorage,
|
||
getNewItemIds,
|
||
clearNewItemIds
|
||
} from '@/utils/clone-tool.js';
|
||
import {
|
||
login as kongfzLogin,
|
||
fetchItems as kongfzFetchItems,
|
||
getItemTplFields as kongfzGetItemTplFields,
|
||
submitItemForm as kongfzSubmitItemForm,
|
||
deleteItem as kongfzDeleteItem,
|
||
batchUpdatePddPlatformId
|
||
} from '@/api/kongfz.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
// 配置信息
|
||
config: {
|
||
username: '',
|
||
password: '',
|
||
token: ''
|
||
},
|
||
// 价格配置
|
||
priceConfig: {
|
||
configType: 1, // 1: 折扣, 2: 加减值, 3: 指定金额
|
||
value: 1.0
|
||
},
|
||
// 正负号选择
|
||
signOptions: ['+', '-'],
|
||
signIndex: 0,
|
||
// 拉取参数
|
||
fetchParams: {
|
||
itemSn: '',
|
||
priceMin: '',
|
||
priceMax: '',
|
||
startDate: '',
|
||
endDate: ''
|
||
},
|
||
// 处理状态
|
||
processing: false,
|
||
paused: false,
|
||
fetching: false,
|
||
status: '就绪',
|
||
// 日志
|
||
logs: [],
|
||
scrollTop: 0,
|
||
// 进度
|
||
progressText: '0/0',
|
||
currentItemText: '当前商品: 无',
|
||
// 商品列表
|
||
itemList: [],
|
||
currentItemIndex: -1,
|
||
// 设备ID
|
||
deviceId: '',
|
||
// 模态框控制
|
||
showAccountModal: false,
|
||
showFetchModal: false,
|
||
showPriceModal: false,
|
||
showProcessModal: false
|
||
};
|
||
},
|
||
onLoad() {
|
||
// 生成设备ID
|
||
this.deviceId = generateDeviceId();
|
||
this.log(`设备ID: ${this.deviceId}`);
|
||
|
||
// 加载配置
|
||
this.config = loadFromStorage('kongfz_config', this.config);
|
||
const savedPriceConfig = loadFromStorage('kongfz_price_config', this.priceConfig);
|
||
this.priceConfig = savedPriceConfig;
|
||
// 加载正负号信息
|
||
if (savedPriceConfig && savedPriceConfig.signIndex !== undefined) {
|
||
this.signIndex = savedPriceConfig.signIndex;
|
||
}
|
||
|
||
// 加载商品列表
|
||
const itemList = loadFromStorage('kongfz_item_list');
|
||
if (itemList) {
|
||
this.itemList = itemList;
|
||
}
|
||
|
||
// 加载新商品ID列表
|
||
this.newItemIds = getNewItemIds();
|
||
|
||
this.log('配置已加载');
|
||
if (this.newItemIds.length > 0) {
|
||
this.log(`已加载 ${this.newItemIds.length} 个新商品ID`);
|
||
}
|
||
},
|
||
computed: {
|
||
// 开始日期格式化为YYYY-MM-DD格式供picker使用
|
||
startDateFormatted() {
|
||
if (!this.fetchParams.startDate) return '';
|
||
const date = this.fetchParams.startDate;
|
||
if (date.length === 8) {
|
||
return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`;
|
||
}
|
||
return date;
|
||
},
|
||
// 结束日期格式化为YYYY-MM-DD格式供picker使用
|
||
endDateFormatted() {
|
||
if (!this.fetchParams.endDate) return '';
|
||
const date = this.fetchParams.endDate;
|
||
if (date.length === 8) {
|
||
return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`;
|
||
}
|
||
return date;
|
||
}
|
||
},
|
||
methods: {
|
||
// 开始日期选择处理
|
||
onStartDateChange(e) {
|
||
const selectedDate = e.detail.value;
|
||
this.fetchParams.startDate = selectedDate.replace(/-/g, '');
|
||
},
|
||
// 结束日期选择处理
|
||
onEndDateChange(e) {
|
||
const selectedDate = e.detail.value;
|
||
this.fetchParams.endDate = selectedDate.replace(/-/g, '');
|
||
},
|
||
// 清空开始日期
|
||
clearStartDate() {
|
||
this.fetchParams.startDate = '';
|
||
},
|
||
// 清空结束日期
|
||
clearEndDate() {
|
||
this.fetchParams.endDate = '';
|
||
},
|
||
// 价格类型选择处理
|
||
onPriceTypeChange(e) {
|
||
this.priceConfig.configType = parseInt(e.detail.value);
|
||
// 重置调整值
|
||
this.priceConfig.value = this.priceConfig.configType === 1 ? 1.0 : 0;
|
||
// 重置正负号为正号
|
||
this.signIndex = 0;
|
||
},
|
||
// 正负号选择处理
|
||
onSignChange(e) {
|
||
this.signIndex = e.detail.value;
|
||
},
|
||
// 获取输入框提示文本
|
||
getPlaceholderText() {
|
||
switch (this.priceConfig.configType) {
|
||
case 1:
|
||
return '请输入0-100的折扣值';
|
||
case 2:
|
||
return '请输入数字';
|
||
case 3:
|
||
return '请输入指定金额';
|
||
default:
|
||
return '请输入调整值';
|
||
}
|
||
},
|
||
// 键盘按键事件处理(阻止非数字字符输入)
|
||
onKeyPress(e) {
|
||
const char = String.fromCharCode(e.which || e.keyCode);
|
||
const currentValue = this.priceConfig.value || '';
|
||
|
||
// 只允许数字和小数点
|
||
if (!/[\d.]/.test(char)) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
|
||
// 如果是小数点,检查是否已经有小数点
|
||
if (char === '.' && currentValue.includes('.')) {
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
},
|
||
|
||
// 价格值输入处理
|
||
onPriceValueInput(e) {
|
||
let value = e.detail.value;
|
||
|
||
// 严格过滤:只允许数字和小数点,移除所有非数字字符(包括汉字)
|
||
value = value.replace(/[^\d.]/g, '');
|
||
|
||
// 防止多个小数点
|
||
const dotCount = (value.match(/\./g) || []).length;
|
||
if (dotCount > 1) {
|
||
const firstDotIndex = value.indexOf('.');
|
||
value = value.substring(0, firstDotIndex + 1) + value.substring(firstDotIndex + 1).replace(/\./g, '');
|
||
}
|
||
|
||
// 防止以小数点开头
|
||
if (value.startsWith('.')) {
|
||
value = '0' + value;
|
||
}
|
||
|
||
// 折扣类型限制0-100
|
||
if (this.priceConfig.configType === 1) {
|
||
const numValue = parseFloat(value);
|
||
if (numValue > 100) {
|
||
value = '100';
|
||
uni.showToast({
|
||
title: '折扣值不能超过100',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
}
|
||
|
||
// 更新值
|
||
this.priceConfig.value = value;
|
||
|
||
// 强制更新输入框显示(防止输入汉字后显示异常)
|
||
this.$nextTick(() => {
|
||
this.priceConfig.value = value;
|
||
});
|
||
},
|
||
|
||
// 保存配置
|
||
saveConfig() {
|
||
if (saveToStorage('kongfz_config', this.config)) {
|
||
this.log('配置已保存');
|
||
this.showAccountModal = false; // 关闭弹框
|
||
} else {
|
||
this.log('保存配置失败');
|
||
}
|
||
},
|
||
|
||
// 加载价格配置
|
||
loadPriceConfig() {
|
||
const savedPriceConfig = loadFromStorage('kongfz_price_config', this.priceConfig);
|
||
this.priceConfig = savedPriceConfig;
|
||
// 加载正负号信息
|
||
if (savedPriceConfig && savedPriceConfig.signIndex !== undefined) {
|
||
this.signIndex = savedPriceConfig.signIndex;
|
||
}
|
||
console.log('已加载价格配置:', this.priceConfig);
|
||
},
|
||
|
||
// 保存价格配置
|
||
savePriceConfig() {
|
||
// 验证价格值是否为数字
|
||
let value = parseFloat(this.priceConfig.value);
|
||
if (isNaN(value)) {
|
||
uni.showToast({
|
||
title: '请输入有效的数值',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 折扣类型验证范围
|
||
if (this.priceConfig.configType === 1 && (value < 0 || value > 100)) {
|
||
uni.showToast({
|
||
title: '折扣值必须在0-100之间',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 加减值类型处理正负号
|
||
if (this.priceConfig.configType === 2 && parseInt(this.signIndex) === 1) {
|
||
value = -Math.abs(value); // 确保是负数
|
||
}
|
||
|
||
// 保存配置时包含正负号信息
|
||
const configToSave = {
|
||
...this.priceConfig,
|
||
value: value,
|
||
signIndex: this.signIndex
|
||
};
|
||
console.log("价格配置", configToSave)
|
||
|
||
if (saveToStorage('kongfz_price_config', configToSave)) {
|
||
this.log('价格配置已保存');
|
||
} else {
|
||
this.log('保存价格配置失败');
|
||
}
|
||
},
|
||
|
||
// 登录
|
||
async login() {
|
||
const {
|
||
username,
|
||
password
|
||
} = this.config;
|
||
|
||
if (!username || !password) {
|
||
uni.showToast({
|
||
title: '请输入用户名和密码',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.log('正在登录...');
|
||
this.status = '登录中...';
|
||
|
||
try {
|
||
// 调用登录API
|
||
const result = await kongfzLogin(username, password);
|
||
|
||
if (result.success) {
|
||
console.log("token", result);
|
||
this.config.token = result.token;
|
||
this.saveConfig();
|
||
this.log(`登录成功! Token: ${result.token}`);
|
||
this.status = '登录成功';
|
||
|
||
// 关闭账号配置模态框
|
||
this.showAccountModal = false;
|
||
|
||
// 显示成功提示
|
||
uni.showToast({
|
||
title: '登录成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
} else {
|
||
// 处理登录失败情况
|
||
this.handleLoginError(result);
|
||
}
|
||
} catch (err) {
|
||
console.error('登录出错:', err);
|
||
this.log(`登录失败: ${err.message}`);
|
||
this.status = '登录失败';
|
||
|
||
// 显示网络错误提示
|
||
uni.showModal({
|
||
title: '登录失败',
|
||
content: '网络异常或服务器错误,请检查网络连接后重试',
|
||
showCancel: false
|
||
});
|
||
}
|
||
},
|
||
|
||
// 处理登录错误
|
||
handleLoginError(result) {
|
||
this.log(result.message || '登录失败');
|
||
this.status = '登录失败';
|
||
|
||
// 根据错误码显示不同的错误信息
|
||
let errorTitle = '登录失败';
|
||
let errorContent = result.message || '未知错误';
|
||
|
||
// 检查是否有具体的错误码
|
||
if (result.responseData) {
|
||
const errCode = result.responseData.errCode;
|
||
const errInfo = result.responseData.errInfo;
|
||
|
||
switch (errCode) {
|
||
case 102:
|
||
errorContent = '用户名不能为空';
|
||
break;
|
||
case 1000:
|
||
errorContent = '授权码错误或已过期';
|
||
break;
|
||
case 1001:
|
||
errorContent = '用户不存在,请检查用户名是否正确';
|
||
break;
|
||
case 1005:
|
||
errorContent = '密码错误,请检查密码是否正确';
|
||
break;
|
||
case 1009:
|
||
errorContent = '调用次数已达上限,请稍后再试';
|
||
break;
|
||
default:
|
||
if (errInfo) {
|
||
errorContent = errInfo;
|
||
}
|
||
break;
|
||
}
|
||
|
||
// 检查是否需要手机验证
|
||
if (result.responseData.extInfo &&
|
||
result.responseData.extInfo.action === "redirect" &&
|
||
result.responseData.extInfo.uri &&
|
||
result.responseData.extInfo.uri.includes("请使用手机号验证登录")) {
|
||
errorTitle = '需要手机验证';
|
||
errorContent = '该账号需要手机验证登录,请使用其他账号或到网页端完成验证';
|
||
}
|
||
}
|
||
|
||
// 显示错误弹框
|
||
uni.showModal({
|
||
title: errorTitle,
|
||
content: errorContent,
|
||
showCancel: false,
|
||
confirmText: '确定'
|
||
});
|
||
},
|
||
|
||
// 确认拉取商品
|
||
confirmFetchItems() {
|
||
this.showFetchModal = false;
|
||
this.startFetchItems();
|
||
},
|
||
|
||
// 确认保存价格配置
|
||
confirmSavePriceConfig() {
|
||
this.showPriceModal = false;
|
||
this.savePriceConfig();
|
||
// 更新当前的价格配置
|
||
this.loadPriceConfig();
|
||
},
|
||
|
||
// 开始拉取商品
|
||
startFetchItems() {
|
||
if (this.fetching) {
|
||
return;
|
||
}
|
||
|
||
// 获取输入参数
|
||
const {
|
||
itemSn,
|
||
priceMin,
|
||
priceMax,
|
||
startDate,
|
||
endDate
|
||
} = this.fetchParams;
|
||
|
||
// 验证日期格式
|
||
if (startDate && !validateDate(startDate)) {
|
||
uni.showToast({
|
||
title: '开始日期格式错误,请输入YYYYMMDD格式的日期',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (endDate && !validateDate(endDate)) {
|
||
uni.showToast({
|
||
title: '结束日期格式错误,请输入YYYYMMDD格式的日期',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 验证时间范围
|
||
if (startDate && endDate) {
|
||
try {
|
||
const startTs = dateToTimestamp(startDate, false);
|
||
const endTs = dateToTimestamp(endDate, true);
|
||
if (endTs <= startTs) {
|
||
uni.showToast({
|
||
title: '结束日期必须大于开始日期',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
this.log(`日期转换错误: ${e.message}`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 准备参数
|
||
const params = {};
|
||
if (itemSn) {
|
||
params.itemSn = itemSn;
|
||
}
|
||
|
||
if (priceMin) {
|
||
try {
|
||
parseFloat(priceMin); // 验证是否为数字
|
||
params.priceMin = priceMin;
|
||
} catch (e) {
|
||
uni.showToast({
|
||
title: '最低价格格式错误,请输入有效数字',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (priceMax) {
|
||
try {
|
||
parseFloat(priceMax); // 验证是否为数字
|
||
params.priceMax = priceMax;
|
||
} catch (e) {
|
||
uni.showToast({
|
||
title: '最高价格格式错误,请输入有效数字',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (startDate) {
|
||
try {
|
||
const startTs = dateToTimestamp(startDate, false);
|
||
params.startCreateTime = startTs.toString();
|
||
} catch (e) {
|
||
this.log(`开始日期转换错误: ${e.message}`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (endDate) {
|
||
try {
|
||
const endTs = dateToTimestamp(endDate, true);
|
||
params.endCreateTime = endTs.toString();
|
||
} catch (e) {
|
||
this.log(`结束日期转换错误: ${e.message}`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
this.log('开始拉取商品...');
|
||
this.status = '拉取商品中...';
|
||
this.fetching = true;
|
||
|
||
// 执行拉取
|
||
this.fetchItems(params);
|
||
},
|
||
|
||
// 拉取商品
|
||
fetchItems(params) {
|
||
const token = this.config.token;
|
||
if (!token) {
|
||
this.log('请先登录获取Token');
|
||
this.fetching = false;
|
||
this.status = '就绪';
|
||
return;
|
||
}
|
||
|
||
// 调用API获取商品列表
|
||
console.log(`开始拉取商品...`);
|
||
this.log(`请求参数:${JSON.stringify(params)}`);
|
||
this.log(`使用Token:${token.substring(0, 4)}...${token.substring(token.length - 4)}`);
|
||
|
||
kongfzFetchItems(token, params, (progressMessage) => {
|
||
// 进度回调
|
||
this.log(progressMessage);
|
||
})
|
||
.then(allItemIds => {
|
||
if (allItemIds.length > 0) {
|
||
this.itemList = allItemIds;
|
||
this.log(`✅ 成功拉取 ${allItemIds.length} 个商品`);
|
||
this.log(
|
||
`📋 商品ID列表: ${allItemIds.slice(0, 5).map(item => item.id).join(', ')}${allItemIds.length > 5 ? '...' : ''}`
|
||
);
|
||
|
||
// 统计商品状态
|
||
const statusCounts = allItemIds.reduce((acc, item) => {
|
||
acc[item.status] = (acc[item.status] || 0) + 1;
|
||
return acc;
|
||
}, {});
|
||
this.log(
|
||
`📊 商品状态统计: ${Object.entries(statusCounts).map(([status, count]) => `${status}=${count}`).join(', ')}`
|
||
);
|
||
console.log("保存数据", allItemIds)
|
||
// 保存到本地存储
|
||
saveToStorage('kongfz_item_list', allItemIds);
|
||
this.log('💾 商品列表已保存到本地存储');
|
||
} else {
|
||
this.log('⚠️ 没有找到符合条件的商品');
|
||
this.log('请检查以下可能的原因:');
|
||
this.log('1. 筛选条件是否过于严格');
|
||
this.log('2. 账号中是否有在售商品');
|
||
this.log('3. 日期范围是否正确');
|
||
}
|
||
|
||
this.fetching = false;
|
||
this.status = '就绪';
|
||
this.log('拉取商品完成');
|
||
})
|
||
.catch(err => {
|
||
this.log(`❌ 拉取商品失败: ${err.message}`);
|
||
this.log('🔍 请检查以下可能的问题:');
|
||
this.log('1. 网络连接是否正常');
|
||
this.log('2. 登录状态是否有效 (Token可能已过期)');
|
||
this.log('3. 孔夫子网站是否可访问');
|
||
this.log('4. 请求参数是否正确');
|
||
|
||
// 建议用户重新登录
|
||
this.log('建议尝试重新登录后再次拉取');
|
||
|
||
this.fetching = false;
|
||
this.status = '拉取失败';
|
||
|
||
// 3秒后恢复状态
|
||
setTimeout(() => {
|
||
if (this.status === '拉取失败') {
|
||
this.status = '就绪';
|
||
}
|
||
}, 3000);
|
||
});
|
||
},
|
||
|
||
// 开始处理商品
|
||
startProcessing() {
|
||
if (this.processing) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否有商品列表
|
||
if (!this.itemList || this.itemList.length === 0) {
|
||
try {
|
||
const itemListStr = uni.getStorageSync('kongfz_item_list');
|
||
if (itemListStr) {
|
||
this.itemList = JSON.parse(itemListStr);
|
||
}
|
||
} catch (e) {
|
||
this.log('读取商品列表失败: ' + e.message);
|
||
}
|
||
|
||
if (!this.itemList || this.itemList.length === 0) {
|
||
uni.showToast({
|
||
title: '没有可处理的商品,请先拉取商品',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 检查是否已登录
|
||
if (!this.config.token) {
|
||
this.log('请先登录获取Token');
|
||
uni.showToast({
|
||
title: '请先登录',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.processing = true;
|
||
this.paused = false;
|
||
this.status = '处理中...';
|
||
this.log('开始处理商品...');
|
||
|
||
// 清除之前的新商品ID存储数据
|
||
try {
|
||
uni.removeStorageSync('kongfz_new_ids');
|
||
this.log('已清除之前的新商品ID记录');
|
||
} catch (e) {
|
||
this.log('清除新商品ID记录失败: ' + e.message);
|
||
}
|
||
|
||
// 重置进度
|
||
this.currentItemIndex = -1;
|
||
const waitingItems = this.itemList.filter(item => item.status === 'wait');
|
||
this.progressText = `0/${waitingItems.length}`;
|
||
|
||
// 保存当前商品列表到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 开始处理
|
||
this.processNextItem();
|
||
},
|
||
|
||
// 处理下一个商品
|
||
processNextItem() {
|
||
if (!this.processing || this.paused) {
|
||
return;
|
||
}
|
||
|
||
// 查找下一个待处理的商品
|
||
let nextItemIndex = -1;
|
||
for (let i = 0; i < this.itemList.length; i++) {
|
||
if (this.itemList[i].status === 'wait') {
|
||
nextItemIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (nextItemIndex === -1) {
|
||
// 所有商品已处理完成
|
||
this.log('所有商品处理完成!');
|
||
this.stopProcessing();
|
||
return;
|
||
}
|
||
|
||
this.currentItemIndex = nextItemIndex;
|
||
const currentItem = this.itemList[nextItemIndex];
|
||
const itemId = currentItem.id;
|
||
|
||
this.currentItemText = `当前商品: ${itemId}`;
|
||
this.log(`处理商品: ${itemId}`);
|
||
|
||
// 获取商品模板字段
|
||
this.log(`获取商品 ${itemId} 的模板字段...`);
|
||
console.log("通过itemId获取模板", itemId)
|
||
this.getItemTplFields(itemId).then(itemData => {
|
||
console.log("itemData", itemData)
|
||
if (itemData) {
|
||
// 保存商品数据
|
||
this.log(`保存商品 ${itemId} 数据...`);
|
||
this.saveItemData(itemId, itemData);
|
||
|
||
// 更新状态为geted
|
||
this.updateItemStatus(nextItemIndex, 'geted');
|
||
|
||
// 更新进度
|
||
const waitingItems = this.itemList.filter(item => item.status === 'wait');
|
||
const processedItems = this.itemList.filter(item => item.status !== 'wait').length;
|
||
this.progressText = `${processedItems}/${processedItems + waitingItems.length}`;
|
||
|
||
// 保存当前状态到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 提交商品表单
|
||
this.log(`提交商品 ${itemId}...`);
|
||
console.log("itemData", itemData)
|
||
this.submitItemForm(itemData).then(success => {
|
||
if (success) {
|
||
this.log(`商品 ${itemId} 提交成功`);
|
||
|
||
// 刷新新商品ID列表
|
||
// this.refreshNewItemIds();
|
||
|
||
// 删除原商品
|
||
this.deleteItem(itemId).then(deleteSuccess => {
|
||
if (deleteSuccess) {
|
||
this.updateItemStatus(nextItemIndex, 'delete');
|
||
this.log(`原商品 ${itemId} 已删除`);
|
||
} else {
|
||
this.updateItemStatus(nextItemIndex, 'delete_error');
|
||
this.log(`警告: 原商品 ${itemId} 删除失败`);
|
||
}
|
||
console.log("保存数据1", this.itemList)
|
||
// 保存当前状态到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 立即处理当前商品的孔更新
|
||
this.log(`开始处理商品 ${itemId} 的平台ID更新...`);
|
||
this.processSinglePddItem(itemId).then(() => {
|
||
this.log(`孔商品 ${itemId} 处理完成`);
|
||
// 延迟5秒后处理下一个
|
||
this.log('等待5秒...');
|
||
setTimeout(() => this.processNextItem(), 5000);
|
||
}).catch(err => {
|
||
this.log(`商品 ${itemId} 孔处理失败: ${err.message}`);
|
||
// 延迟5秒后处理下一个
|
||
this.log('等待5秒...');
|
||
setTimeout(() => this.processNextItem(), 5000);
|
||
});
|
||
});
|
||
} else {
|
||
this.log(`商品 ${itemId} 提交失败`);
|
||
this.updateItemStatus(nextItemIndex, 'clone_error');
|
||
|
||
// 保存当前状态到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 延迟5秒后处理下一个
|
||
this.log('等待5秒...');
|
||
setTimeout(() => this.processNextItem(), 5000);
|
||
}
|
||
});
|
||
} else {
|
||
// 更新状态为error
|
||
this.updateItemStatus(nextItemIndex, 'error');
|
||
this.log(`商品 ${itemId} 处理失败`);
|
||
|
||
// 保存当前状态到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 延迟5秒后处理下一个
|
||
this.log('等待5秒...');
|
||
setTimeout(() => this.processNextItem(), 5000);
|
||
}
|
||
}).catch(err => {
|
||
this.log(`处理商品 ${itemId} 时出错: ${err.message}`);
|
||
this.updateItemStatus(nextItemIndex, 'error');
|
||
|
||
// 保存当前状态到本地存储
|
||
saveToStorage('kongfz_item_list', this.itemList);
|
||
|
||
// 延迟5秒后处理下一个
|
||
this.log('等待5秒...');
|
||
setTimeout(() => this.processNextItem(), 5000);
|
||
});
|
||
},
|
||
|
||
// 暂停/继续处理
|
||
togglePause() {
|
||
this.paused = !this.paused;
|
||
|
||
if (this.paused) {
|
||
this.log('处理已暂停...');
|
||
this.status = '已暂停';
|
||
} else {
|
||
this.log('继续处理...');
|
||
this.status = '处理中...';
|
||
this.processNextItem();
|
||
}
|
||
},
|
||
|
||
// 停止处理
|
||
stopProcessing() {
|
||
this.processing = false;
|
||
this.paused = false;
|
||
this.log('处理已停止');
|
||
this.status = '已停止';
|
||
this.currentItemText = '当前商品: 无';
|
||
},
|
||
|
||
// 获取商品模板字段
|
||
getItemTplFields(itemId) {
|
||
return kongfzGetItemTplFields(this.config.token, itemId);
|
||
},
|
||
|
||
// 保存商品数据
|
||
saveItemData(itemId, data) {
|
||
try {
|
||
// 保存到本地存储
|
||
const key = `kongfz_item_${itemId}`;
|
||
saveToStorage(key, data);
|
||
return true;
|
||
} catch (e) {
|
||
this.log(`保存商品数据失败: ${e.message}`);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 更新商品状态
|
||
updateItemStatus(index, newStatus) {
|
||
if (index >= 0 && index < this.itemList.length) {
|
||
this.itemList[index].status = newStatus;
|
||
// 不在每次状态更新时都保存,而是在processNextItem方法的关键点保存
|
||
}
|
||
},
|
||
|
||
// 提交商品表单
|
||
submitItemForm(itemData) {
|
||
return kongfzSubmitItemForm(this.config.token, itemData, this.priceConfig);
|
||
},
|
||
|
||
// 删除原商品
|
||
deleteItem(itemId) {
|
||
return kongfzDeleteItem(this.config.token, itemId);
|
||
},
|
||
|
||
// 添加日志
|
||
log(message) {
|
||
const logMessage = formatLogMessage(message);
|
||
this.logs.push(logMessage);
|
||
|
||
// 同时输出到控制台
|
||
console.log(logMessage);
|
||
|
||
// 保持日志不超过1000条
|
||
if (this.logs.length > 1000) {
|
||
this.logs.shift();
|
||
}
|
||
|
||
// 自动滚动到底部
|
||
this.$nextTick(() => {
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select('.log-content').fields({
|
||
scrollOffset: true,
|
||
size: true
|
||
}, (data) => {
|
||
if (data && data.scrollHeight > data.height) {
|
||
// 计算需要滚动的距离
|
||
const maxScrollTop = data.scrollHeight - data.height;
|
||
this.scrollTop = maxScrollTop;
|
||
}
|
||
}).exec();
|
||
});
|
||
},
|
||
|
||
// 清空日志
|
||
clearLog() {
|
||
this.logs = [];
|
||
this.log('日志已清空');
|
||
},
|
||
|
||
// 导出日志
|
||
exportLog() {
|
||
if (this.logs.length === 0) {
|
||
uni.showToast({
|
||
title: '暂无日志可导出',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
const logContent = this.logs.join('\n');
|
||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||
const filename = `clone-tool-log-${timestamp}.txt`;
|
||
|
||
// 在小程序环境中,可以使用分享功能
|
||
uni.showActionSheet({
|
||
itemList: ['复制到剪贴板', '分享日志'],
|
||
success: (res) => {
|
||
if (res.tapIndex === 0) {
|
||
uni.setClipboardData({
|
||
data: logContent,
|
||
success: () => {
|
||
uni.showToast({
|
||
title: '日志已复制到剪贴板',
|
||
icon: 'success'
|
||
});
|
||
}
|
||
});
|
||
} else if (res.tapIndex === 1) {
|
||
// 分享功能
|
||
uni.share({
|
||
provider: 'weixin',
|
||
type: 0,
|
||
title: '克隆工具日志',
|
||
summary: `日志内容,共${this.logs.length}条记录`,
|
||
success: () => {
|
||
this.log('日志分享成功');
|
||
},
|
||
fail: (err) => {
|
||
this.log(`日志分享失败: ${err.errMsg}`);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 开始孔商品处理
|
||
startPddProcessing() {
|
||
if (this.pddProcessing) {
|
||
return;
|
||
}
|
||
|
||
// 获取新商品ID列表
|
||
const newItemIds = getNewItemIds();
|
||
if (!newItemIds || newItemIds.length === 0) {
|
||
uni.showToast({
|
||
title: '没有找到新商品ID,请先完成商品处理',
|
||
icon: 'none'
|
||
});
|
||
this.log('孔处理失败: 没有找到新商品ID');
|
||
return;
|
||
}
|
||
|
||
// 检查是否有对应的旧商品ID
|
||
if (!this.itemList || this.itemList.length === 0) {
|
||
uni.showToast({
|
||
title: '没有找到原始商品列表,请先拉取商品',
|
||
icon: 'none'
|
||
});
|
||
this.log('孔处理失败: 没有找到原始商品列表');
|
||
return;
|
||
}
|
||
|
||
this.pddProcessing = true;
|
||
this.pddStatus = '处理中...';
|
||
this.pddProcessedItems = [];
|
||
|
||
this.log('开始孔商品处理...');
|
||
this.log(`找到 ${newItemIds.length} 个新商品ID`);
|
||
this.log(`原始商品列表包含 ${this.itemList.length} 个商品`);
|
||
|
||
// 构建ID对应关系 - 使用存储的映射关系而不是索引匹配
|
||
const idMappings = [];
|
||
|
||
// 遍历新商品ID列表,每个项目现在包含oldId和newId的映射
|
||
for (const item of newItemIds) {
|
||
// 严格校验oldId和newId都不为空
|
||
if (item.oldId && item.newId &&
|
||
String(item.oldId).trim() !== '' &&
|
||
String(item.newId).trim() !== '') {
|
||
idMappings.push({
|
||
oldPlatformId: String(item.oldId),
|
||
newPlatformId: String(item.newId)
|
||
});
|
||
} else {
|
||
// 记录跳过的无效数据
|
||
this.log(`跳过无效数据: oldId=${item.oldId || '空'}, newId=${item.newId || '空'}`);
|
||
}
|
||
}
|
||
|
||
if (idMappings.length === 0) {
|
||
this.pddProcessing = false;
|
||
this.pddStatus = '处理失败';
|
||
uni.showToast({
|
||
title: '没有找到有效的ID对应关系',
|
||
icon: 'none'
|
||
});
|
||
this.log('孔处理失败: 没有找到有效的ID对应关系');
|
||
return;
|
||
}
|
||
|
||
this.pddProgressText = `0/${idMappings.length}`;
|
||
this.log(`准备处理 ${idMappings.length} 个商品ID对应关系`);
|
||
|
||
// 调用批量更新接口
|
||
this.processPddBatch(idMappings);
|
||
},
|
||
|
||
// 处理单个商品的孔平台ID更新
|
||
processSinglePddItem(oldItemId) {
|
||
return new Promise((resolve, reject) => {
|
||
// 获取新商品ID列表
|
||
const newItemIds = getNewItemIds();
|
||
|
||
if (!newItemIds || newItemIds.length === 0) {
|
||
reject(new Error('没有找到新商品ID记录'));
|
||
return;
|
||
}
|
||
|
||
// 查找对应的新商品ID
|
||
const matchedItem = newItemIds.find(item =>
|
||
item.oldId && String(item.oldId).trim() === String(oldItemId).trim()
|
||
);
|
||
|
||
if (!matchedItem || !matchedItem.newId || String(matchedItem.newId).trim() === '') {
|
||
reject(new Error(`未找到商品 ${oldItemId} 对应的新商品ID`));
|
||
return;
|
||
}
|
||
|
||
// 构建单个商品的ID映射
|
||
const idMapping = [{
|
||
oldPlatformId: String(matchedItem.oldId),
|
||
newPlatformId: String(matchedItem.newId)
|
||
}];
|
||
|
||
this.log(`找到商品 ${oldItemId} 对应的新ID: ${matchedItem.newId}`);
|
||
|
||
// 调用批量更新接口(虽然只有一个商品)
|
||
batchUpdatePddPlatformId(idMapping)
|
||
.then(result => {
|
||
if (result.success) {
|
||
this.log(`商品 ${oldItemId} 孔平台ID更新成功`);
|
||
resolve(result);
|
||
} else {
|
||
reject(new Error(result.message || '更新失败'));
|
||
}
|
||
})
|
||
.catch(err => {
|
||
reject(err);
|
||
});
|
||
});
|
||
},
|
||
|
||
// 处理孔批量更新
|
||
processPddBatch(idMappings) {
|
||
this.pddCurrentItemText = `正在批量更新 ${idMappings.length} 个商品...`;
|
||
|
||
batchUpdatePddPlatformId(idMappings)
|
||
.then(result => {
|
||
if (result.success) {
|
||
this.pddStatus = '处理完成';
|
||
this.pddProgressText = `${idMappings.length}/${idMappings.length}`;
|
||
this.pddCurrentItemText = '批量更新完成';
|
||
this.log(`孔商品处理成功: ${result.message}`);
|
||
this.log(`成功更新 ${idMappings.length} 个商品的平台ID`);
|
||
|
||
// 显示处理结果
|
||
uni.showToast({
|
||
title: '孔商品处理完成',
|
||
icon: 'success'
|
||
});
|
||
|
||
// 保存处理记录
|
||
this.pddProcessedItems = idMappings;
|
||
|
||
} else {
|
||
this.pddStatus = '处理失败';
|
||
this.pddCurrentItemText = '批量更新失败';
|
||
this.log(`孔商品处理失败: ${result.message}`);
|
||
|
||
uni.showToast({
|
||
title: `处理失败: ${result.message}`,
|
||
icon: 'none'
|
||
});
|
||
}
|
||
})
|
||
.catch(err => {
|
||
this.pddStatus = '处理失败';
|
||
this.pddCurrentItemText = '批量更新出错';
|
||
this.log(`孔商品处理出错: ${err.message}`);
|
||
|
||
uni.showToast({
|
||
title: `处理出错: ${err.message}`,
|
||
icon: 'none'
|
||
});
|
||
})
|
||
.finally(() => {
|
||
this.pddProcessing = false;
|
||
});
|
||
},
|
||
|
||
|
||
|
||
// 格式化时间
|
||
formatTime(timeStr) {
|
||
try {
|
||
const date = new Date(timeStr);
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
} catch (e) {
|
||
return timeStr;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import "./index.scss";
|
||
</style> |