Compare commits

7 Commits

Author SHA1 Message Date
6d725b51cb fix(config): 修正开发服务器代理配置
- 将代理目标端口从 18082 修正为 8082
- 更新错误提示信息中的端口号以保持一致
2025-12-26 16:21:32 +08:00
857f46ad17 chore(config): 更新生产环境API配置和代理设置
- 将生产环境API基础URL从绝对路径改为相对路径 /api
- 修改开发环境代理配置,后端目标端口从 8082 改为 18082
- 更新相关注释和错误提示信息
- 移除生产环境配置中的服务器IP地址硬编码
- 添加
2025-12-26 14:11:46 +08:00
e251153d14 chore(config): 更新生产环境配置和构建设置
- 修复 .env.production 文件中的注释编码问题
- 移除路由守卫中的调试日志输出
- 调整错误处理机制,生产环境静默处理错误
- 移除应用挂载时的调试日志和异常处理包装
- 添加构建配置和基础路径设置
2025-12-26 10:54:17 +08:00
3662ee072b feat(config): 添加生产环境配置文件
- 配置生产环境服务器地址 175.178.252.59
- 设置后端 API 地址为 http://175.178.252.59:8082/api
- 配置 PingPong 模式为 sandbox
- 添加生产环境相关环境变量
2025-12-26 10:27:09 +08:00
3bdb2ff5f3 feat(order): 添加订单管理功能
- 创建新的订单管理页面 OrderManage.vue,支持分页和多条件查询
- 添加订单查询API接口,支持复杂的订单筛选条件
- 在路由中添加订单管理页面路由配置
- 更新导航菜单,将订单查询改为订单管理并指向新页面
- 实现订单列表的表格展示,包含订单号、商品信息、金额、客户信息等
- 添加订单状态、支付状态、PayPal状态的标签显示和筛选功能
- 实现查询表单,支持订单号、PayPal订单ID、客户信息等多维度搜索
- 添加分页组件,支持每页显示数量调整和页码切换
- 实现订单详情查看功能和重置查询条件功能
2025-12-25 18:12:23 +08:00
b2bbbf8c44 feat(product): 添加商品管理分页功能并支持完整链接查询
- 添加分页组件支持,包括页码、页面大小切换功能
- 实现从完整URL中自动提取链接码的功能
- 更新查询表单提示文本为"请输入链接码或完整链接"
- 添加分页数据响应处理,显示总记录数和当前页码信息
- 增加分页相关的CSS样式适配移动端显示
- 重置查询时同步重置分页参数到默认值
2025-12-25 17:11:48 +08:00
01bda65010 feat(product): 添加商品下架和多条件查询功能
- 实现了商品下架功能,下架后SKU库存变为0且链接失效
- 添加了支持多条件查询的商品列表功能
- 增加了包含商品名称、链接码、状态、发售地区等查询条件
- 在商品管理页面添加了查询表单界面
- 实现了下架按钮的禁用逻辑和状态显示
- 添加了移动端响应式查询表单适配
- 集成了API接口和错误处理机制
2025-12-25 16:26:11 +08:00
10 changed files with 923 additions and 36 deletions

6
.env.production Normal file
View File

@@ -0,0 +1,6 @@
# 鐢熶骇鐜閰嶇疆
# 浣跨敤鐩稿璺緞锛岄€氳繃Nginx浠悊鍒板悗绔?
# API鍩虹URL锛堢浉瀵硅矾寰勶紝閫氳繃Nginx浠悊锛?# 閲嶈锛氬繀椤讳娇鐢ㄧ浉瀵硅矾寰?/api锛屼笉瑕佷娇鐢ㄥ畬鏁碪RL
VITE_API_BASE_URL=/api
# PingPong妯″紡锛坰andbox/production锛?VITE_PINGPONG_MODE=sandbox

View File

@@ -73,7 +73,7 @@ MTKJPAY-FRONT/
`src/api/request.js` 中配置后端API地址
```javascript
const service = axios.create({
baseURL: 'http://localhost:8082/api',
baseURL: '/api', // 使用相对路径通过Nginx代理
timeout: 10000
})
```

View File

@@ -11,11 +11,11 @@
<h1>MT Pay 管理系统</h1>
<div class="nav-menu">
<router-link
to="/query"
to="/manage/order"
class="nav-item"
:class="{ active: activeIndex === '/query' || activeIndex.startsWith('/query') }"
:class="{ active: activeIndex === '/manage/order' || activeIndex.startsWith('/manage/order') }"
>
订单查询
订单管理
</router-link>
<router-link
to="/manage/product"

View File

@@ -42,3 +42,14 @@ export function calculateCurrencyConversion(data) {
})
}
/**
* 查询订单列表(支持分页和多条件查询)
*/
export function queryOrders(query) {
return request({
url: '/order/query',
method: 'post',
data: query
})
}

View File

@@ -65,3 +65,30 @@ export function uploadProductImage(file) {
// 注意:不设置 Content-Type让浏览器自动设置包含 boundary
})
}
/**
* 下架商品
* 下架后商品所有SKU库存改为0链接失效无法再被访问
*/
export function offShelfProductById(id) {
return request({
url: `/product/${id}/off-shelf`,
method: 'put'
})
}
/**
* 查询商品列表(支持多条件查询)
* @param {Object} query - 查询条件
* @param {string} query.name - 商品名称(模糊查询)
* @param {string} query.linkCode - 商品链接码(精确查询)
* @param {string} query.status - 商品状态ACTIVE-上架INACTIVE-下架)
* @param {string} query.salesRegion - 发售地区货币代码MYR, PHP, THB, VND, SGD, CNY, USD等
*/
export function queryProducts(query) {
return request({
url: '/product/query',
method: 'post',
data: query
})
}

View File

@@ -17,18 +17,14 @@ app.use(router)
app.use(ElementPlus)
app.use(i18n)
// 添加错误处理
// 添加错误处理(生产环境静默处理)
app.config.errorHandler = (err, instance, info) => {
console.error('Vue错误:', err)
console.error('错误信息:', info)
console.error('组件实例:', instance)
// 生产环境可以记录到错误追踪服务
if (import.meta.env.DEV) {
console.error('Vue错误:', err, info)
}
}
// 挂载应用
try {
app.mount('#app')
console.log('Vue应用已成功挂载')
} catch (error) {
console.error('应用挂载失败:', error)
}
app.mount('#app')

View File

@@ -78,6 +78,12 @@ const routes = [
name: 'OrderQuery',
component: OrderQuery
},
{
path: '/manage/order',
name: 'OrderManage',
component: () => import('../views/OrderManage.vue'),
meta: { requiresAuth: true }
},
{
path: '/manage/product',
name: 'ProductManage',
@@ -105,8 +111,6 @@ const router = createRouter({
// 路由守卫
router.beforeEach((to, from, next) => {
console.log('路由导航:', from.path, '->', to.path)
// 检查是否需要认证
if (to.meta.requiresAuth) {
// 检查是否已登录

545
src/views/OrderManage.vue Normal file
View File

@@ -0,0 +1,545 @@
<template>
<div class="order-manage">
<!-- 查询表单 -->
<el-card class="search-card" style="margin-bottom: 20px">
<el-form :model="queryForm" :inline="true" class="search-form">
<el-form-item label="订单号">
<el-input
v-model="queryForm.orderNo"
placeholder="请输入订单号"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="PayPal订单ID">
<el-input
v-model="queryForm.paypalOrderId"
placeholder="请输入PayPal订单ID"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="订单状态">
<el-select
v-model="queryForm.status"
placeholder="请选择状态"
clearable
style="width: 150px"
>
<el-option label="待支付" value="PENDING" />
<el-option label="已支付" value="PAID" />
<el-option label="已发货" value="SHIPPED" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
<el-form-item label="支付状态">
<el-select
v-model="queryForm.paymentStatus"
placeholder="请选择支付状态"
clearable
style="width: 150px"
>
<el-option label="未支付" value="UNPAID" />
<el-option label="已支付" value="PAID" />
<el-option label="支付失败" value="FAILED" />
</el-select>
</el-form-item>
<el-form-item label="PayPal状态">
<el-select
v-model="queryForm.paypalStatus"
placeholder="请选择PayPal状态"
clearable
style="width: 150px"
>
<el-option label="已创建" value="CREATED" />
<el-option label="已批准" value="APPROVED" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="VOIDED" />
</el-select>
</el-form-item>
<el-form-item label="客户姓名">
<el-input
v-model="queryForm.customerName"
placeholder="请输入客户姓名"
clearable
style="width: 150px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="客户电话">
<el-input
v-model="queryForm.customerPhone"
placeholder="请输入客户电话"
clearable
style="width: 150px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="商品名称">
<el-input
v-model="queryForm.productName"
placeholder="请输入商品名称"
clearable
style="width: 150px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 订单列表 -->
<el-card>
<el-table :data="orderList" v-loading="loading" style="width: 100%">
<!-- 订单号 -->
<el-table-column label="订单号" prop="orderNo" width="180" show-overflow-tooltip />
<!-- 商品信息 -->
<el-table-column label="商品信息" min-width="200">
<template #default="{ row }">
<div>
<div class="product-name">{{ row.productName }}</div>
<div class="sku-name">SKU: {{ row.skuName }}</div>
<div class="quantity">数量: {{ row.quantity }}</div>
</div>
</template>
</el-table-column>
<!-- 订单金额 -->
<el-table-column label="订单金额" width="150" align="center">
<template #default="{ row }">
<div v-if="row.paymentCurrency && row.paymentCurrency !== row.originalCurrency">
<div class="original-amount">{{ row.originalAmount }} {{ row.originalCurrency }}</div>
<div class="payment-amount">{{ row.paymentAmount }} {{ row.paymentCurrency }}</div>
</div>
<div v-else>
{{ row.totalAmount }} {{ row.originalCurrency || row.currency }}
</div>
</template>
</el-table-column>
<!-- 客户信息 -->
<el-table-column label="客户信息" min-width="180">
<template #default="{ row }">
<div>
<div>{{ row.customerName }}</div>
<div class="customer-phone">{{ row.customerPhone }}</div>
<div class="customer-email" v-if="row.customerEmail">{{ row.customerEmail }}</div>
</div>
</template>
</el-table-column>
<!-- 收货地址 -->
<el-table-column label="收货地址" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<div>
{{ row.shippingCountry }} {{ row.shippingCity }}
</div>
</template>
</el-table-column>
<!-- 订单状态 -->
<el-table-column label="订单状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)" size="small">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<!-- 支付状态 -->
<el-table-column label="支付状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getPaymentStatusTagType(row.paymentStatus)" size="small">
{{ getPaymentStatusText(row.paymentStatus) }}
</el-tag>
</template>
</el-table-column>
<!-- PayPal信息 -->
<el-table-column label="PayPal信息" min-width="200">
<template #default="{ row }">
<div v-if="row.paypalOrderId">
<div class="paypal-order-id">订单ID: {{ row.paypalOrderId }}</div>
<el-tag v-if="row.paypalStatus" :type="getPaypalStatusTagType(row.paypalStatus)" size="small" style="margin-top: 4px">
{{ row.paypalStatus }}
</el-tag>
<div v-if="row.payerEmail" class="payer-email">{{ row.payerEmail }}</div>
</div>
<span v-else class="no-paypal">未关联PayPal订单</span>
</template>
</el-table-column>
<!-- 创建时间 -->
<el-table-column label="创建时间" width="180" align="center">
<template #default="{ row }">
{{ formatDateTime(row.createTime) }}
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="viewOrderDetail(row.id)">
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-container" v-if="pagination.total > 0">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Search, Refresh } from '@element-plus/icons-vue'
import { queryOrders } from '../api/order'
const router = useRouter()
const loading = ref(false)
const orderList = ref([])
// 查询表单
const queryForm = reactive({
orderNo: '',
paypalOrderId: '',
merchantOrderNo: '',
status: '',
paymentStatus: '',
paypalStatus: '',
paypalPaymentStatus: '',
customerName: '',
customerPhone: '',
customerEmail: '',
productName: '',
payerEmail: '',
payerName: '',
startTime: '',
endTime: ''
})
// 分页信息
const pagination = ref({
pageNum: 1,
pageSize: 10,
total: 0
})
// 查询订单列表
const handleQuery = async () => {
loading.value = true
try {
const query = {}
if (queryForm.orderNo && queryForm.orderNo.trim()) {
query.orderNo = queryForm.orderNo.trim()
}
if (queryForm.paypalOrderId && queryForm.paypalOrderId.trim()) {
query.paypalOrderId = queryForm.paypalOrderId.trim()
}
if (queryForm.merchantOrderNo && queryForm.merchantOrderNo.trim()) {
query.merchantOrderNo = queryForm.merchantOrderNo.trim()
}
if (queryForm.status) {
query.status = queryForm.status
}
if (queryForm.paymentStatus) {
query.paymentStatus = queryForm.paymentStatus
}
if (queryForm.paypalStatus) {
query.paypalStatus = queryForm.paypalStatus
}
if (queryForm.paypalPaymentStatus) {
query.paypalPaymentStatus = queryForm.paypalPaymentStatus
}
if (queryForm.customerName && queryForm.customerName.trim()) {
query.customerName = queryForm.customerName.trim()
}
if (queryForm.customerPhone && queryForm.customerPhone.trim()) {
query.customerPhone = queryForm.customerPhone.trim()
}
if (queryForm.customerEmail && queryForm.customerEmail.trim()) {
query.customerEmail = queryForm.customerEmail.trim()
}
if (queryForm.productName && queryForm.productName.trim()) {
query.productName = queryForm.productName.trim()
}
if (queryForm.payerEmail && queryForm.payerEmail.trim()) {
query.payerEmail = queryForm.payerEmail.trim()
}
if (queryForm.payerName && queryForm.payerName.trim()) {
query.payerName = queryForm.payerName.trim()
}
if (queryForm.startTime && queryForm.startTime.trim()) {
query.startTime = queryForm.startTime.trim()
}
if (queryForm.endTime && queryForm.endTime.trim()) {
query.endTime = queryForm.endTime.trim()
}
// 添加分页参数
query.pageNum = pagination.value.pageNum
query.pageSize = pagination.value.pageSize
const response = await queryOrders(query)
if (response.code === '0000' && response.data) {
const pageResult = response.data
orderList.value = pageResult.records || []
// 更新分页信息
pagination.value.total = pageResult.total || 0
pagination.value.pageNum = pageResult.current || 1
pagination.value.pageSize = pageResult.size || 10
if (orderList.value.length === 0) {
ElMessage.info('未找到符合条件的订单')
} else {
ElMessage.success(`查询到 ${pagination.value.total} 条订单,当前第 ${pagination.value.pageNum}`)
}
} else {
ElMessage.error(response.message || '查询订单列表失败')
orderList.value = []
pagination.value.total = 0
}
} catch (error) {
console.error('查询订单列表失败:', error)
ElMessage.error('查询订单列表失败')
orderList.value = []
pagination.value.total = 0
} finally {
loading.value = false
}
}
// 重置查询条件
const handleReset = () => {
Object.assign(queryForm, {
orderNo: '',
paypalOrderId: '',
merchantOrderNo: '',
status: '',
paymentStatus: '',
paypalStatus: '',
paypalPaymentStatus: '',
customerName: '',
customerPhone: '',
customerEmail: '',
productName: '',
payerEmail: '',
payerName: '',
startTime: '',
endTime: ''
})
pagination.value = {
pageNum: 1,
pageSize: 10,
total: 0
}
handleQuery()
}
// 分页大小改变
const handleSizeChange = (size) => {
pagination.value.pageSize = size
pagination.value.pageNum = 1 // 重置到第一页
handleQuery()
}
// 页码改变
const handlePageChange = (page) => {
pagination.value.pageNum = page
handleQuery()
}
// 查看订单详情
const viewOrderDetail = (orderId) => {
router.push(`/order/detail/${orderId}`)
}
// 获取订单状态文本
const getStatusText = (status) => {
const statusMap = {
'PENDING': '待支付',
'PAID': '已支付',
'SHIPPED': '已发货',
'COMPLETED': '已完成',
'CANCELLED': '已取消'
}
return statusMap[status] || status
}
// 获取订单状态标签类型
const getStatusTagType = (status) => {
const typeMap = {
'PENDING': 'warning',
'PAID': 'success',
'SHIPPED': 'info',
'COMPLETED': 'success',
'CANCELLED': 'danger'
}
return typeMap[status] || ''
}
// 获取支付状态文本
const getPaymentStatusText = (status) => {
const statusMap = {
'UNPAID': '未支付',
'PAID': '已支付',
'FAILED': '支付失败'
}
return statusMap[status] || status
}
// 获取支付状态标签类型
const getPaymentStatusTagType = (status) => {
const typeMap = {
'UNPAID': 'warning',
'PAID': 'success',
'FAILED': 'danger'
}
return typeMap[status] || ''
}
// 获取PayPal状态标签类型
const getPaypalStatusTagType = (status) => {
const typeMap = {
'CREATED': 'info',
'APPROVED': 'success',
'COMPLETED': 'success',
'VOIDED': 'danger'
}
return typeMap[status] || ''
}
// 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return ''
const date = new Date(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 组件挂载时加载订单列表
onMounted(() => {
handleQuery()
})
</script>
<style scoped>
.order-manage {
padding: 20px;
}
.search-card {
margin-bottom: 20px;
}
.search-form {
margin-bottom: 0;
}
.product-name {
font-weight: bold;
margin-bottom: 4px;
}
.sku-name {
color: #909399;
font-size: 12px;
margin-bottom: 2px;
}
.quantity {
color: #909399;
font-size: 12px;
}
.original-amount {
color: #909399;
font-size: 12px;
text-decoration: line-through;
}
.payment-amount {
color: #f56c6c;
font-weight: bold;
}
.customer-phone {
color: #909399;
font-size: 12px;
margin-top: 2px;
}
.customer-email {
color: #909399;
font-size: 12px;
margin-top: 2px;
}
.paypal-order-id {
font-size: 12px;
color: #606266;
margin-bottom: 4px;
}
.payer-email {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.no-paypal {
color: #c0c4cc;
font-size: 12px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
padding: 20px 0;
}
@media (max-width: 768px) {
.pagination-container {
justify-content: center;
}
.pagination-container :deep(.el-pagination) {
flex-wrap: wrap;
}
}
</style>

View File

@@ -17,6 +17,69 @@
</div>
</template>
<!-- 查询表单 -->
<el-card class="search-card" style="margin-bottom: 20px">
<el-form :model="queryForm" :inline="true" class="search-form">
<el-form-item label="商品名称">
<el-input
v-model="queryForm.name"
placeholder="请输入商品名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="链接码">
<el-input
v-model="queryForm.linkCode"
placeholder="请输入链接码或完整链接"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="商品状态">
<el-select
v-model="queryForm.status"
placeholder="请选择状态"
clearable
style="width: 150px"
>
<el-option label="上架" value="ACTIVE" />
<el-option label="下架" value="INACTIVE" />
</el-select>
</el-form-item>
<el-form-item label="发售地区">
<el-select
v-model="queryForm.salesRegion"
placeholder="请选择地区"
clearable
style="width: 150px"
>
<el-option label="马来西亚" value="MYR" />
<el-option label="菲律宾" value="PHP" />
<el-option label="泰国" value="THB" />
<el-option label="越南" value="VND" />
<el-option label="新加坡" value="SGD" />
<el-option label="中国" value="CNY" />
<el-option label="美国" value="USD" />
<el-option label="欧洲" value="EUR" />
<el-option label="英国" value="GBP" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 商品列表 -->
<el-table :data="productList" v-loading="loading" style="width: 100%">
<!-- 商品封面图 -->
@@ -115,12 +178,31 @@
<el-button type="success" link size="small" @click="copyProductUrl(row.id, row.productUrl)">
复制
</el-button>
<el-button type="danger" link size="small" @click="deleteProduct(row.id)">
删除
<el-button
type="danger"
link
size="small"
@click="offShelfProduct(row.id)"
:disabled="row.status === 'INACTIVE'"
>
{{ row.status === 'INACTIVE' ? '已下架' : '下架' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-container" v-if="pagination.total > 0">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</el-card>
</div>
@@ -130,14 +212,31 @@
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, User } from '@element-plus/icons-vue'
import { getProductList, getProductUrl } from '../api/product'
import { Plus, User, Search, Refresh } from '@element-plus/icons-vue'
import { getProductList, getProductUrl, offShelfProductById, queryProducts } from '../api/product'
import { formatAmount } from '../utils/helpers'
const router = useRouter()
const loading = ref(false)
const productList = ref([])
// 查询表单
const queryForm = ref({
name: '',
linkCode: '',
status: '',
salesRegion: '',
pageNum: 1,
pageSize: 10
})
// 分页信息
const pagination = ref({
pageNum: 1,
pageSize: 10,
total: 0
})
// 跳转到新增商品页面
const goToCreate = () => {
router.push('/manage/product/create')
@@ -191,7 +290,56 @@ const getSalesRegions = (row) => {
return regions
}
// 加载商品列表
// 从完整URL中提取链接码
const extractLinkCodeFromUrl = (input) => {
if (!input || !input.trim()) {
return input
}
const trimmed = input.trim()
// 如果包含 "/product/"说明是完整URL提取链接码
const productIndex = trimmed.indexOf('/product/')
if (productIndex >= 0) {
let linkCode = trimmed.substring(productIndex + '/product/'.length)
// 移除可能的查询参数和锚点
const queryIndex = linkCode.indexOf('?')
if (queryIndex >= 0) {
linkCode = linkCode.substring(0, queryIndex)
}
const hashIndex = linkCode.indexOf('#')
if (hashIndex >= 0) {
linkCode = linkCode.substring(0, hashIndex)
}
// 移除尾部斜杠
linkCode = linkCode.replace(/\/$/, '')
return linkCode.trim()
}
// 如果不包含 "/product/",可能是直接输入的链接码,直接返回
// 但也可能是其他格式的URL尝试提取最后一段作为链接码
if (trimmed.includes('/')) {
const parts = trimmed.split('/')
if (parts.length > 0) {
let lastPart = parts[parts.length - 1]
// 移除查询参数和锚点
const queryIndex = lastPart.indexOf('?')
if (queryIndex >= 0) {
lastPart = lastPart.substring(0, queryIndex)
}
const hashIndex = lastPart.indexOf('#')
if (hashIndex >= 0) {
lastPart = lastPart.substring(0, hashIndex)
}
return lastPart.trim()
}
}
// 直接返回(可能是纯链接码)
return trimmed
}
// 加载商品列表(无查询条件)
const loadProductList = async () => {
loading.value = true
try {
@@ -212,6 +360,91 @@ const loadProductList = async () => {
}
}
// 查询商品列表
const handleQuery = async () => {
loading.value = true
try {
// 构建查询条件(只包含非空字段)
const query = {}
if (queryForm.value.name && queryForm.value.name.trim()) {
query.name = queryForm.value.name.trim()
}
if (queryForm.value.linkCode && queryForm.value.linkCode.trim()) {
// 自动从完整URL中提取链接码
const extractedLinkCode = extractLinkCodeFromUrl(queryForm.value.linkCode.trim())
query.linkCode = extractedLinkCode
}
if (queryForm.value.status) {
query.status = queryForm.value.status
}
if (queryForm.value.salesRegion) {
query.salesRegion = queryForm.value.salesRegion
}
// 添加分页参数
query.pageNum = pagination.value.pageNum
query.pageSize = pagination.value.pageSize
const response = await queryProducts(query)
if (response.code === '0000' && response.data) {
const pageResult = response.data
productList.value = pageResult.records || []
// 更新分页信息
pagination.value.total = pageResult.total || 0
pagination.value.pageNum = pageResult.current || 1
pagination.value.pageSize = pageResult.size || 10
if (productList.value.length === 0) {
ElMessage.info('未找到符合条件的商品')
} else {
ElMessage.success(`查询到 ${pagination.value.total} 条商品,当前第 ${pagination.value.pageNum}`)
}
} else {
ElMessage.error(response.message || '查询商品列表失败')
productList.value = []
pagination.value.total = 0
}
} catch (error) {
console.error('查询商品列表失败:', error)
ElMessage.error('查询商品列表失败')
productList.value = []
} finally {
loading.value = false
}
}
// 重置查询条件
const handleReset = () => {
queryForm.value = {
name: '',
linkCode: '',
status: '',
salesRegion: '',
pageNum: 1,
pageSize: 10
}
pagination.value = {
pageNum: 1,
pageSize: 10,
total: 0
}
loadProductList()
}
// 分页大小改变
const handleSizeChange = (size) => {
pagination.value.pageSize = size
pagination.value.pageNum = 1 // 重置到第一页
handleQuery()
}
// 页码改变
const handlePageChange = (page) => {
pagination.value.pageNum = page
handleQuery()
}
// 编辑商品
const editProduct = (id) => {
// TODO: 实现编辑功能,跳转到编辑页面
@@ -259,24 +492,30 @@ const copyProductUrl = async (id, url) => {
}
}
// 删除商品
const deleteProduct = async (id) => {
// 下架商品
const offShelfProduct = async (id) => {
try {
await ElMessageBox.confirm('确定要删除该商品吗?', '提示', {
await ElMessageBox.confirm(
'确定要下架该商品吗下架后商品所有SKU库存将改为0链接将失效无法再被访问。',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
}
)
// TODO: 实现删除商品API
ElMessage.info('删除功能待实现')
// await request.delete(`/api/product/${id}`)
// ElMessage.success('商品删除成功')
// loadProductList()
const response = await offShelfProductById(id)
if (response.code === '0000') {
ElMessage.success('商品下架成功')
loadProductList()
} else {
ElMessage.error(response.message || '商品下架失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除商品失败:', error)
ElMessage.error('删除商品失败')
console.error('下架商品失败:', error)
ElMessage.error('下架商品失败')
}
}
}
@@ -367,5 +606,53 @@ onMounted(() => {
color: #c0c4cc;
font-size: 12px;
}
/* 分页容器 */
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
padding: 20px 0;
}
@media (max-width: 768px) {
.pagination-container {
justify-content: center;
}
.pagination-container :deep(.el-pagination) {
flex-wrap: wrap;
}
}
/* 查询表单 */
.search-card {
margin-bottom: 20px;
}
.search-form {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.search-form .el-form-item {
margin-bottom: 0;
}
@media (max-width: 768px) {
.search-form {
flex-direction: column;
}
.search-form .el-form-item {
width: 100%;
}
.search-form .el-form-item .el-input,
.search-form .el-form-item .el-select {
width: 100% !important;
}
}
</style>

View File

@@ -12,11 +12,22 @@ export default defineConfig({
'@': path.resolve(__dirname, 'src')
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
// 确保资源路径正确
assetsInlineLimit: 4096,
// 生成source map生产环境可以关闭
sourcemap: false
},
// 基础路径(如果部署在子目录下需要配置)
base: '/',
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://127.0.0.1:8082', // 使用 127.0.0.1 而不是 localhost避免 IPv6 问题
target: 'http://127.0.0.1:8082',
changeOrigin: true,
secure: false,
ws: true,