4180f73c53
- 订单列表重构为卡片式布局并新增筛选功能 - 设置管理支持struct/struct_list类型配置 - 新增短信签名和模板独立管理页面 - 通知渠道新增短信渠道配置 - 产品参数管理优化 Co-authored-by: Cursor <cursoragent@cursor.com>
1323 lines
44 KiB
Vue
1323 lines
44 KiB
Vue
<template>
|
||
<div class="order-list-container">
|
||
<!-- 主容器 -->
|
||
<el-card class="main-container" shadow="never">
|
||
<!-- 搜索和操作栏 -->
|
||
<div class="filter-section">
|
||
<div class="filter-content">
|
||
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||
<el-form-item label="关键词">
|
||
<el-input v-model="queryParams.key" placeholder="订单名称/ID" clearable style="width: 150px" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="用户ID">
|
||
<el-input v-model="queryParams.user_id" placeholder="用户ID" clearable style="width: 120px" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="用户关键词">
|
||
<el-input v-model="queryParams.user_key" placeholder="用户名/手机号/邮箱" clearable style="width: 180px" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="queryParams.state" placeholder="全部" clearable style="width: 120px">
|
||
<el-option label="待支付" value="0" />
|
||
<el-option label="已支付" value="1" />
|
||
<el-option label="已失效" value="2" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="错误信息">
|
||
<el-select v-model="queryParams.error" placeholder="全部" clearable style="width: 140px">
|
||
<el-option label="有错误的订单" :value="true" />
|
||
<el-option label="无错误的订单" :value="false" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleQuery">
|
||
<el-icon><Search /></el-icon>搜索
|
||
</el-button>
|
||
<el-button @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div class="action-bar">
|
||
<el-button type="primary" @click="handleAdd">
|
||
<el-icon><Plus /></el-icon>新增订单
|
||
</el-button>
|
||
<el-button type="success" @click="fetchOrderList">
|
||
<el-icon><Refresh /></el-icon>刷新
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 订单列表 -->
|
||
<div class="table-section">
|
||
<!-- 骨架屏 -->
|
||
<div v-if="loading" class="skeleton-container">
|
||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||
<div class="skeleton-cell skeleton-id"></div>
|
||
<div class="skeleton-cell skeleton-name"></div>
|
||
<div class="skeleton-cell skeleton-user"></div>
|
||
<div class="skeleton-cell skeleton-price"></div>
|
||
<div class="skeleton-cell skeleton-status"></div>
|
||
<div class="skeleton-cell skeleton-time"></div>
|
||
<div class="skeleton-cell skeleton-action"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
v-else
|
||
v-loading="loading"
|
||
:data="orderList"
|
||
@selection-change="handleSelectionChange"
|
||
style="width: 100%"
|
||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||
>
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="id" label="订单ID" width="100" />
|
||
<el-table-column prop="name" label="订单名称" min-width="180" />
|
||
<el-table-column label="类型" width="90">
|
||
<template #default="{ row }">
|
||
<el-tag size="small" :type="row.type === 'create' ? 'success' : row.type === 'renew' ? 'warning' : 'info'">{{ getTypeText(row.type) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="用户ID" width="100">
|
||
<template #default="{ row }">
|
||
<el-link v-if="row.userId" type="primary" :underline="false" @click.stop="router.push({ path: '/user/detail', query: { user_id: row.userId } })">{{ row.userId }}</el-link>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="商品ID" width="100">
|
||
<template #default="{ row }">
|
||
<el-link v-if="row.commodityId" type="primary" :underline="false" @click.stop="router.push({ path: '/user-goods/list', query: { good_id: row.commodityId } })">{{ row.commodityId }}</el-link>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="表名" width="120">
|
||
<template #default="{ row }">
|
||
<el-tag size="small">{{ row.table || '未知' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="订单金额" width="120">
|
||
<template #default="{ row }">
|
||
<span class="amount">¥{{ (row.price / 100).toFixed(2) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="续费价格" width="120">
|
||
<template #default="{ row }">
|
||
<span class="renew-price">¥{{ (row.renewPrice / 100).toFixed(2) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="数量" width="80">
|
||
<template #default="{ row }">
|
||
<span>{{ row.payNum }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="订单状态" width="120">
|
||
<template #default="{ row }">
|
||
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
|
||
<el-tag :type="getStatusType(row.state)">
|
||
{{ getStatusText(row.state) }}
|
||
</el-tag>
|
||
<el-tag v-if="row.error" type="danger" size="small">异常</el-tag>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="错误信息" min-width="250">
|
||
<template #default="{ row }">
|
||
<el-tooltip v-if="row.error" :content="row.error" placement="top" :show-after="300">
|
||
<span class="error-text">{{ row.error }}</span>
|
||
</el-tooltip>
|
||
<span v-else class="text-muted">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="支付方式" width="100">
|
||
<template #default="{ row }">
|
||
<span>{{ row.payType || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="过期时间" width="170">
|
||
<template #default="{ row }">
|
||
<span>{{ formatDate(row.expireTime) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="创建时间" width="170">
|
||
<template #default="{ row }">
|
||
<span>{{ formatDate(row.CreatedAt) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="250" fixed="right">
|
||
<template #default="{ row }">
|
||
<div class="action-buttons">
|
||
<el-button type="primary" link @click="handleView(row)">查看</el-button>
|
||
<el-button type="warning" link @click="handleEdit(row)">编辑</el-button>
|
||
<el-button type="danger" link @click="handleRetryOrder(row)">重试流程</el-button>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<el-pagination
|
||
v-model:current-page="queryParams.page"
|
||
v-model:page-size="queryParams.count"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="total"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
background
|
||
class="pagination"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 订单详情对话框 -->
|
||
<el-dialog
|
||
v-model="detailDialogVisible"
|
||
title="订单详情"
|
||
width="800px"
|
||
append-to-body
|
||
>
|
||
<el-descriptions :column="2" border v-if="orderDetail">
|
||
<el-descriptions-item label="订单ID">{{ orderDetail.id }}</el-descriptions-item>
|
||
<el-descriptions-item label="订单名称">{{ orderDetail.name }}</el-descriptions-item>
|
||
<el-descriptions-item label="订单类型">
|
||
<el-tag size="small">{{ getTypeText(orderDetail.type) }}</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="用户ID">{{ orderDetail.userId }}</el-descriptions-item>
|
||
<el-descriptions-item label="商品ID">{{ orderDetail.commodityId }}</el-descriptions-item>
|
||
<el-descriptions-item label="套餐ID">{{ orderDetail.planId || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="表名">{{ orderDetail.table }}</el-descriptions-item>
|
||
<el-descriptions-item label="数量">{{ orderDetail.payNum }}</el-descriptions-item>
|
||
<el-descriptions-item label="订单金额">¥{{ (orderDetail.price / 100).toFixed(2) }}</el-descriptions-item>
|
||
<el-descriptions-item label="续费价格">¥{{ (orderDetail.renewPrice / 100).toFixed(2) }}</el-descriptions-item>
|
||
<el-descriptions-item label="订单状态">
|
||
<el-tag :type="getStatusType(orderDetail.state)">
|
||
{{ getStatusText(orderDetail.state) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="支付方式">{{ orderDetail.payType || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="过期时间">{{ formatDate(orderDetail.expireTime) }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ formatDate(orderDetail.CreatedAt) }}</el-descriptions-item>
|
||
<el-descriptions-item label="更新时间">{{ formatDate(orderDetail.UpdatedAt) }}</el-descriptions-item>
|
||
<el-descriptions-item label="参数信息">{{ orderDetail.args || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item v-if="orderDetail.error" label="错误信息" :span="2">
|
||
<el-tag type="danger" size="small" style="margin-right: 6px;">异常</el-tag>
|
||
<span style="color: #f56c6c;">{{ orderDetail.error }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">{{ orderDetail.note || '无' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-dialog>
|
||
|
||
<!-- 订单表单对话框(分步向导) -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogType === 'add' ? '新增订单' : '编辑订单'"
|
||
width="820px"
|
||
append-to-body
|
||
destroy-on-close
|
||
>
|
||
<div class="wizard-container">
|
||
<el-steps :active="currentStep" finish-status="success" align-center class="wizard-steps">
|
||
<el-step title="选择用户" description="指定订单用户与类型" />
|
||
<el-step title="商品配置" description="选择商品与套餐" />
|
||
<el-step title="订单详情" description="价格、支付与优惠" />
|
||
</el-steps>
|
||
|
||
<div class="wizard-body">
|
||
<!-- Step 1: 用户与订单类型 -->
|
||
<el-form v-show="currentStep === 0" ref="step1FormRef" :model="orderForm" :rules="step1Rules" label-width="110px">
|
||
<el-form-item label="用户" prop="user_id">
|
||
<el-input
|
||
v-if="selectedUserInfo"
|
||
:model-value="`${selectedUserInfo.user_name} (ID: ${orderForm.user_id})`"
|
||
readonly
|
||
>
|
||
<template #suffix>
|
||
<el-icon class="clear-icon" @click="clearUser"><Close /></el-icon>
|
||
</template>
|
||
<template #append>
|
||
<el-button @click="userSelectorVisible = true"><el-icon><User /></el-icon></el-button>
|
||
</template>
|
||
</el-input>
|
||
<el-input v-else placeholder="请选择用户" readonly @click="userSelectorVisible = true">
|
||
<template #append>
|
||
<el-button @click="userSelectorVisible = true"><el-icon><User /></el-icon></el-button>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="订单类型" prop="type">
|
||
<el-select v-model="orderForm.type" placeholder="请选择订单类型" style="width: 100%" @change="handleTypeChange">
|
||
<el-option label="新购" value="create" />
|
||
<el-option label="续费" value="renew" />
|
||
<el-option label="升级" value="update" />
|
||
<el-option label="快照" value="snapshot" />
|
||
<el-option label="备份" value="backup" />
|
||
<el-option label="数据盘" value="data_volume" />
|
||
<el-option label="IPv4" value="ipv4" />
|
||
<el-option label="IPv6" value="ipv6" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="订单名称" prop="name">
|
||
<el-input v-model="orderForm.name" placeholder="请输入订单名称" maxlength="200" show-word-limit />
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- Step 2: 商品与套餐 -->
|
||
<el-form v-show="currentStep === 1" ref="step2FormRef" :model="orderForm" :rules="step2Rules" label-width="110px">
|
||
<el-form-item label="商品" prop="commodity_id">
|
||
<el-input
|
||
v-if="selectedProductInfo"
|
||
:model-value="`${selectedProductInfo.name} (ID: ${orderForm.commodity_id})`"
|
||
readonly
|
||
>
|
||
<template #suffix>
|
||
<el-icon class="clear-icon" @click="clearProduct"><Close /></el-icon>
|
||
</template>
|
||
<template #append>
|
||
<el-button @click="productSelectorVisible = true"><el-icon><ShoppingCart /></el-icon></el-button>
|
||
</template>
|
||
</el-input>
|
||
<el-input v-else placeholder="请选择商品" readonly @click="productSelectorVisible = true">
|
||
<template #append>
|
||
<el-button @click="productSelectorVisible = true"><el-icon><ShoppingCart /></el-icon></el-button>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
<el-form-item label="套餐" prop="plan_id">
|
||
<el-select v-model="orderForm.plan_id" placeholder="请选择套餐(可选)" clearable style="width: 100%" :loading="planLoading" @change="handlePlanChange">
|
||
<el-option v-for="p in planList" :key="p.id" :label="`${p.name} - ¥${(p.price / 100).toFixed(2)}`" :value="p.id" />
|
||
</el-select>
|
||
<div v-if="!planList.length && orderForm.commodity_id && !planLoading" class="form-tip">该商品暂无套餐,可直接进入下一步手动设置价格</div>
|
||
</el-form-item>
|
||
<el-form-item label="所属表">
|
||
<el-input v-model="orderForm.table" placeholder="选择商品后自动填充,或手动输入" />
|
||
</el-form-item>
|
||
|
||
<!-- 动态商品参数 -->
|
||
<div v-if="productParams.length" class="args-section">
|
||
<div class="args-section-title">
|
||
<el-icon><Setting /></el-icon>商品参数配置
|
||
</div>
|
||
<el-form-item
|
||
v-for="param in productParams"
|
||
:key="param.id"
|
||
:label="param.name"
|
||
:required="param.must"
|
||
>
|
||
<!-- select 类型 -->
|
||
<el-select
|
||
v-if="param.type === 'select'"
|
||
v-model="argValues[param.id]"
|
||
:placeholder="`请选择${param.name}`"
|
||
style="width: 100%"
|
||
>
|
||
<el-option
|
||
v-for="attr in param.attrs"
|
||
:key="attr.id"
|
||
:label="`${attr.name}${attr.price ? ' (¥' + (attr.price / 100).toFixed(2) + ')' : ''}`"
|
||
:value="attr.id"
|
||
/>
|
||
</el-select>
|
||
<!-- number 类型 -->
|
||
<div v-else-if="param.type === 'number'" class="number-arg-row">
|
||
<el-input-number
|
||
v-model="argValues[param.id]"
|
||
:min="param.min || 0"
|
||
:max="param.max || 9999"
|
||
:step="param.step || 1"
|
||
style="flex: 1"
|
||
/>
|
||
<span v-if="param.arg_key" class="unit-text">{{ param.arg_key }}</span>
|
||
</div>
|
||
<!-- string 类型 -->
|
||
<el-input
|
||
v-else
|
||
v-model="argValues[param.id]"
|
||
:placeholder="`请输入${param.name}`"
|
||
/>
|
||
</el-form-item>
|
||
</div>
|
||
<div v-else-if="paramsLoading" class="form-tip" style="text-align: center; padding: 12px 0;">
|
||
<el-icon class="is-loading"><Loading /></el-icon> 加载商品参数中...
|
||
</div>
|
||
</el-form>
|
||
|
||
<!-- Step 3: 价格与支付 -->
|
||
<el-form v-show="currentStep === 2" ref="step3FormRef" :model="orderForm" :rules="step3Rules" label-width="110px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="价格" prop="price">
|
||
<div class="unit-input-row">
|
||
<el-input-number v-model="orderForm.price" :min="0" :precision="0" style="flex:1" />
|
||
<span class="unit-text">分</span>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="续费价格">
|
||
<div class="unit-input-row">
|
||
<el-input-number v-model="orderForm.renew_price" :min="0" :precision="0" style="flex:1" />
|
||
<span class="unit-text">分</span>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="购买数量" prop="pay_num">
|
||
<el-input-number v-model="orderForm.pay_num" :min="1" style="width: 100%" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="过期时间">
|
||
<el-date-picker v-model="orderForm.expire_time" type="datetime" placeholder="请选择" format="YYYY-MM-DD HH:mm:ss" value-format="x" style="width: 100%" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-form-item label="订单状态">
|
||
<el-radio-group v-model="orderForm.state">
|
||
<el-radio :value="0">待支付</el-radio>
|
||
<el-radio :value="1">已支付</el-radio>
|
||
<el-radio :value="2">已失效</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="支付方式">
|
||
<el-select v-model="orderForm.pay_type" placeholder="请选择" clearable style="width: 100%">
|
||
<el-option label="默认(余额)" value="default" />
|
||
<el-option label="支付宝" value="ali" />
|
||
<el-option label="微信" value="wx" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="优惠码">
|
||
<el-input
|
||
v-if="selectedDiscountCodeInfo"
|
||
:model-value="`${selectedDiscountCodeInfo.name || selectedDiscountCodeInfo.code} (ID: ${orderForm.discount_code_id})`"
|
||
readonly
|
||
>
|
||
<template #suffix><el-icon class="clear-icon" @click="clearDiscountCode"><Close /></el-icon></template>
|
||
<template #append><el-button @click="discountCodeSelectorVisible = true"><el-icon><Ticket /></el-icon></el-button></template>
|
||
</el-input>
|
||
<el-input v-else placeholder="选择优惠码(可选)" readonly @click="discountCodeSelectorVisible = true">
|
||
<template #append><el-button @click="discountCodeSelectorVisible = true"><el-icon><Ticket /></el-icon></el-button></template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="代金券">
|
||
<el-input
|
||
v-if="selectedVoucherInfo"
|
||
:model-value="`${selectedVoucherInfo.name || selectedVoucherInfo.code} (ID: ${orderForm.user_coupon_id})`"
|
||
readonly
|
||
>
|
||
<template #suffix><el-icon class="clear-icon" @click="clearVoucher"><Close /></el-icon></template>
|
||
<template #append><el-button @click="voucherSelectorVisible = true"><el-icon><Money /></el-icon></el-button></template>
|
||
</el-input>
|
||
<el-input v-else placeholder="选择代金券(可选)" readonly @click="voucherSelectorVisible = true">
|
||
<template #append><el-button @click="voucherSelectorVisible = true"><el-icon><Money /></el-icon></el-button></template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="orderForm.note" type="textarea" :rows="2" placeholder="请输入备注(可选)" />
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<div class="wizard-footer">
|
||
<el-button v-if="currentStep > 0" @click="currentStep--">上一步</el-button>
|
||
<el-button v-if="currentStep < 2" type="primary" @click="handleNextStep">下一步</el-button>
|
||
<el-button v-if="currentStep === 2" type="primary" :loading="submitLoading" @click="submitForm">提交订单</el-button>
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 用户选择器 -->
|
||
<UserListSelector
|
||
v-model="userSelectorVisible"
|
||
:current-user-id="orderForm.user_id"
|
||
@confirm="handleUserSelect"
|
||
/>
|
||
|
||
<!-- 商品选择器 -->
|
||
<ProductSelector
|
||
v-model="productSelectorVisible"
|
||
:current-product-id="orderForm.commodity_id"
|
||
@confirm="handleProductSelect"
|
||
/>
|
||
|
||
<!-- 优惠码选择器 -->
|
||
<DiscountCodeSelector
|
||
v-model="discountCodeSelectorVisible"
|
||
:current-code-id="orderForm.discount_code_id"
|
||
@confirm="handleDiscountCodeSelect"
|
||
/>
|
||
|
||
<!-- 代金券选择器 -->
|
||
<VoucherSelector
|
||
v-model="voucherSelectorVisible"
|
||
:current-voucher-id="orderForm.user_coupon_id"
|
||
@confirm="handleVoucherSelect"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus, Delete, Search, Download, Refresh, User, ShoppingCart, Ticket, Money, Close, Setting, Loading } from '@element-plus/icons-vue'
|
||
import { getOrderList, getOrderDetail, createOrder, updateOrder, deleteOrder, retryOrderHook } from '@/api/admin/order'
|
||
import { getProductPlanList, getProductParameterList, getProductParameterDetail } from '@/api/admin/product'
|
||
import UserListSelector from '@/components/admin/UserListSelector.vue'
|
||
import ProductSelector from '@/components/admin/ProductSelector.vue'
|
||
import DiscountCodeSelector from '@/components/admin/DiscountCodeSelector.vue'
|
||
import VoucherSelector from '@/components/admin/VoucherSelector.vue'
|
||
import { isoToMilliseconds, timeToTimestamp, formatDate as formatDateTool } from '@/utils/tool'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
// 查询参数
|
||
const queryParams = reactive({
|
||
page: 1,
|
||
count: 10,
|
||
key: '',
|
||
state: '',
|
||
user_id: '',
|
||
user_key: '',
|
||
error: null
|
||
})
|
||
|
||
// 订单表单
|
||
const orderForm = reactive({
|
||
order_id: undefined,
|
||
name: '',
|
||
table: '',
|
||
type: 'create',
|
||
user_id: undefined,
|
||
commodity_id: 0,
|
||
plan_id: null,
|
||
pay_num: 1,
|
||
price: 0,
|
||
renew_price: 0,
|
||
expire_time: 0,
|
||
discount_code_id: 0,
|
||
user_coupon_id: 0,
|
||
state: 0,
|
||
pay_type: 'default',
|
||
payment_order_id: '',
|
||
args: '',
|
||
note: ''
|
||
})
|
||
|
||
// 分步验证规则
|
||
const step1Rules = {
|
||
user_id: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
||
type: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
|
||
name: [{ required: true, message: '请输入订单名称', trigger: 'blur' }]
|
||
}
|
||
const step2Rules = {
|
||
commodity_id: [{ required: true, message: '请选择商品', trigger: 'change' }]
|
||
}
|
||
const step3Rules = {
|
||
price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
|
||
pay_num: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
||
}
|
||
|
||
// 分步向导
|
||
const currentStep = ref(0)
|
||
const submitLoading = ref(false)
|
||
const step1FormRef = ref(null)
|
||
const step2FormRef = ref(null)
|
||
const step3FormRef = ref(null)
|
||
|
||
// 套餐相关
|
||
const planList = ref([])
|
||
const planLoading = ref(false)
|
||
|
||
// 商品参数相关
|
||
const productParams = ref([])
|
||
const paramsLoading = ref(false)
|
||
const argValues = reactive({})
|
||
|
||
// 状态数据
|
||
const loading = ref(false)
|
||
const orderList = ref([])
|
||
const orderDetail = ref(null)
|
||
const total = ref(0)
|
||
const selectedRows = ref([])
|
||
const dialogVisible = ref(false)
|
||
const detailDialogVisible = ref(false)
|
||
const dialogType = ref('add')
|
||
|
||
// 选择器弹窗状态
|
||
const userSelectorVisible = ref(false)
|
||
const productSelectorVisible = ref(false)
|
||
const discountCodeSelectorVisible = ref(false)
|
||
const voucherSelectorVisible = ref(false)
|
||
|
||
// 选择的显示信息
|
||
const selectedUserInfo = ref(null)
|
||
const selectedProductInfo = ref(null)
|
||
const selectedDiscountCodeInfo = ref(null)
|
||
const selectedVoucherInfo = ref(null)
|
||
|
||
// 获取订单列表
|
||
const fetchOrderList = async () => {
|
||
loading.value = true
|
||
try {
|
||
// 过滤空值参数
|
||
const params = {}
|
||
Object.keys(queryParams).forEach(key => {
|
||
if (queryParams[key] !== '' && queryParams[key] !== null && queryParams[key] !== undefined) {
|
||
params[key] = queryParams[key]
|
||
}
|
||
})
|
||
const res = await getOrderList(params)
|
||
console.log('订单列表数据:', res.data)
|
||
if (res.data.code === 200) {
|
||
// 处理时间数据:将ISO格式转换为毫秒级时间戳(用于时间选择器)
|
||
const list = (res.data.data.list || []).map(item => {
|
||
if (item.expireTime) {
|
||
// 保存原始时间用于显示
|
||
item._originalExpireTime = item.expireTime
|
||
// 转换为毫秒级时间戳用于时间选择器
|
||
item._expireTimeMs = isoToMilliseconds(item.expireTime)
|
||
}
|
||
return item
|
||
})
|
||
orderList.value = list
|
||
total.value = res.data.data.all_count || 0
|
||
}
|
||
} catch (error) {
|
||
console.error('获取订单列表失败:', error)
|
||
ElMessage.error('获取订单列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 格式化日期 - 使用工具函数
|
||
const formatDate = (dateStr) => {
|
||
return formatDateTool(dateStr)
|
||
}
|
||
|
||
// 获取订单状态类型
|
||
const getStatusType = (status) => {
|
||
const statusMap = {
|
||
0: 'warning', // 待支付
|
||
1: 'success', // 已支付
|
||
2: 'info', // 已失效
|
||
}
|
||
return statusMap[status] || 'info'
|
||
}
|
||
|
||
// 获取订单状态文本
|
||
const getStatusText = (status) => {
|
||
const statusMap = { 0: '待支付', 1: '已支付', 2: '已失效' }
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
// 获取订单类型文本
|
||
const getTypeText = (type) => {
|
||
const typeMap = { create: '新购', renew: '续费', update: '升级', snapshot: '快照', backup: '备份', data_volume: '数据盘', ipv4: 'IPv4', ipv6: 'IPv6' }
|
||
return typeMap[type] || type || '-'
|
||
}
|
||
|
||
// 查询
|
||
const handleQuery = () => {
|
||
queryParams.page = 1
|
||
fetchOrderList()
|
||
}
|
||
|
||
// 重置查询
|
||
const resetQuery = () => {
|
||
queryParams.key = ''
|
||
queryParams.state = ''
|
||
queryParams.user_id = ''
|
||
queryParams.user_key = ''
|
||
queryParams.error = null
|
||
queryParams.page = 1
|
||
fetchOrderList()
|
||
}
|
||
|
||
// 选择项变化
|
||
const handleSelectionChange = (selection) => {
|
||
selectedRows.value = selection
|
||
}
|
||
|
||
// 分页
|
||
const handleSizeChange = (size) => {
|
||
queryParams.count = size
|
||
fetchOrderList()
|
||
}
|
||
|
||
const handleCurrentChange = (page) => {
|
||
queryParams.page = page
|
||
fetchOrderList()
|
||
}
|
||
|
||
// 新增订单
|
||
const handleAdd = () => {
|
||
dialogType.value = 'add'
|
||
currentStep.value = 0
|
||
clearAllSelections()
|
||
planList.value = []
|
||
productParams.value = []
|
||
Object.keys(argValues).forEach(k => delete argValues[k])
|
||
Object.assign(orderForm, {
|
||
order_id: undefined,
|
||
name: '',
|
||
table: 'good',
|
||
type: 'create',
|
||
user_id: undefined,
|
||
commodity_id: 0,
|
||
plan_id: null,
|
||
pay_num: 1,
|
||
price: 0,
|
||
renew_price: 0,
|
||
expire_time: 0,
|
||
discount_code_id: 0,
|
||
user_coupon_id: 0,
|
||
state: 0,
|
||
pay_type: 'default',
|
||
payment_order_id: '',
|
||
args: '',
|
||
note: ''
|
||
})
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 查看订单详情
|
||
const handleView = async (row) => {
|
||
try {
|
||
const res = await getOrderDetail({ order_id: row.id })
|
||
if (res.data.code === 200) {
|
||
orderDetail.value = res.data.data
|
||
detailDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
console.error('获取订单详情失败:', error)
|
||
ElMessage.error('获取订单详情失败')
|
||
}
|
||
}
|
||
|
||
// 编辑订单
|
||
const handleEdit = (row) => {
|
||
dialogType.value = 'edit'
|
||
currentStep.value = 0
|
||
clearAllSelections()
|
||
planList.value = []
|
||
productParams.value = []
|
||
Object.keys(argValues).forEach(k => delete argValues[k])
|
||
|
||
let expireTimeMs = null
|
||
if (row._expireTimeMs !== undefined) {
|
||
expireTimeMs = row._expireTimeMs
|
||
} else if (row.expireTime) {
|
||
expireTimeMs = isoToMilliseconds(row.expireTime)
|
||
}
|
||
|
||
Object.assign(orderForm, {
|
||
order_id: row.id,
|
||
name: row.name,
|
||
table: row.table || '',
|
||
type: row.type || 'create',
|
||
user_id: row.userId,
|
||
commodity_id: row.commodityId || 0,
|
||
plan_id: row.planId || null,
|
||
pay_num: row.payNum,
|
||
price: row.price,
|
||
renew_price: row.renewPrice,
|
||
expire_time: expireTimeMs,
|
||
discount_code_id: 0,
|
||
user_coupon_id: 0,
|
||
state: row.state,
|
||
pay_type: row.payType || 'default',
|
||
payment_order_id: row.paymentOrderId || '',
|
||
args: row.args || '',
|
||
note: row.note || ''
|
||
})
|
||
|
||
if (row.userId) {
|
||
selectedUserInfo.value = { user_id: row.userId, user_name: `用户${row.userId}` }
|
||
}
|
||
if (row.commodityId) {
|
||
selectedProductInfo.value = { id: row.commodityId, name: `商品${row.commodityId}` }
|
||
fetchPlanList(row.commodityId)
|
||
fetchProductParams(row.commodityId).then(() => {
|
||
// 从已有 args 中恢复参数值
|
||
if (row.args) {
|
||
try {
|
||
const existingArgs = JSON.parse(row.args)
|
||
if (Array.isArray(existingArgs)) {
|
||
for (const a of existingArgs) {
|
||
const param = productParams.value.find(p => p.id === a.arg_id)
|
||
if (!param) continue
|
||
if (param.type === 'select') {
|
||
argValues[param.id] = a.attr_id
|
||
} else if (param.type === 'number') {
|
||
argValues[param.id] = a.number
|
||
} else {
|
||
argValues[param.id] = a.value || ''
|
||
}
|
||
}
|
||
}
|
||
} catch {}
|
||
}
|
||
})
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 重试订单流程
|
||
const handleRetryOrder = (row) => {
|
||
ElMessageBox.confirm(
|
||
`确认对订单「${row.name}」(ID: ${row.id}) 重试流程吗?`,
|
||
'重试订单流程',
|
||
{
|
||
confirmButtonText: '确认重试',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(async () => {
|
||
try {
|
||
const res = await retryOrderHook({ order_id: row.id })
|
||
if (res.data.code === 200) {
|
||
ElMessage.success('重试流程已触发')
|
||
fetchOrderList()
|
||
} else {
|
||
ElMessage.error(res.data.message || '重试失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('重试订单流程失败:', error)
|
||
ElMessage.error(error.response?.data?.message || '重试订单流程失败')
|
||
}
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 删除订单
|
||
const handleDelete = (row) => {
|
||
ElMessageBox.confirm(`确认删除订单 ${row.name} 吗?`, '警告', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(async () => {
|
||
try {
|
||
const res = await deleteOrder({ id: row.id })
|
||
if (res.data.code === 200) {
|
||
ElMessage.success('删除成功')
|
||
fetchOrderList()
|
||
}
|
||
} catch (error) {
|
||
console.error('删除失败:', error)
|
||
ElMessage.error(error.response?.data?.message || '删除失败')
|
||
}
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 批量删除
|
||
const handleBatchDelete = () => {
|
||
if (selectedRows.value.length === 0) {
|
||
ElMessage.warning('请至少选择一条记录')
|
||
return
|
||
}
|
||
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
ElMessage.success('批量删除成功')
|
||
fetchOrderList()
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 订单类型变更
|
||
const handleTypeChange = (type) => {
|
||
if (type === 'create') {
|
||
orderForm.table = 'good'
|
||
}
|
||
}
|
||
|
||
// 分步导航
|
||
const handleNextStep = async () => {
|
||
if (currentStep.value === 0) {
|
||
const formEl = step1FormRef.value
|
||
if (!formEl) return
|
||
const valid = await formEl.validate().catch(() => false)
|
||
if (!valid) return
|
||
} else if (currentStep.value === 1) {
|
||
const formEl = step2FormRef.value
|
||
if (!formEl) return
|
||
const valid = await formEl.validate().catch(() => false)
|
||
if (!valid) return
|
||
}
|
||
currentStep.value++
|
||
}
|
||
|
||
// 加载套餐列表
|
||
const fetchPlanList = async (goodId) => {
|
||
if (!goodId) { planList.value = []; return }
|
||
planLoading.value = true
|
||
try {
|
||
const res = await getProductPlanList({ good_id: goodId, page: 1, count: 100 })
|
||
if (res.data.code === 200) {
|
||
planList.value = res.data.data?.list || res.data.data || []
|
||
} else {
|
||
planList.value = []
|
||
}
|
||
} catch { planList.value = [] }
|
||
finally { planLoading.value = false }
|
||
}
|
||
|
||
// 加载商品参数列表
|
||
const fetchProductParams = async (goodId) => {
|
||
if (!goodId) { productParams.value = []; return }
|
||
paramsLoading.value = true
|
||
try {
|
||
const res = await getProductParameterList({ good_id: goodId })
|
||
if (res.data.code === 200) {
|
||
const list = res.data.data || []
|
||
await Promise.all(list.map(async (param) => {
|
||
try {
|
||
const detail = await getProductParameterDetail({ good_id: goodId, arg_id: param.id })
|
||
if (detail.data.code === 200) {
|
||
const attrs = detail.data.data.attrs || []
|
||
attrs.sort((a, b) => (Number(a.index) || 0) - (Number(b.index) || 0))
|
||
param.attrs = attrs
|
||
} else { param.attrs = [] }
|
||
} catch { param.attrs = [] }
|
||
}))
|
||
productParams.value = list
|
||
// 初始化默认值
|
||
for (const param of list) {
|
||
if (argValues[param.id] !== undefined) continue
|
||
if (param.type === 'select' && param.attrs?.length) {
|
||
argValues[param.id] = param.attrs[0].id
|
||
} else if (param.type === 'number') {
|
||
argValues[param.id] = param.min || 0
|
||
} else {
|
||
argValues[param.id] = ''
|
||
}
|
||
}
|
||
} else { productParams.value = [] }
|
||
} catch { productParams.value = [] }
|
||
finally { paramsLoading.value = false }
|
||
}
|
||
|
||
// 根据参数表单值构建 args JSON
|
||
const buildArgsJson = () => {
|
||
if (!productParams.value.length) return ''
|
||
const args = []
|
||
for (const param of productParams.value) {
|
||
const val = argValues[param.id]
|
||
if (val === undefined || val === null || val === '') continue
|
||
if (param.type === 'select') {
|
||
const attr = param.attrs?.find(a => a.id === val)
|
||
if (attr) {
|
||
args.push({ arg_id: param.id, name: param.name, attr_id: attr.id, value: attr.value || attr.name })
|
||
}
|
||
} else if (param.type === 'number') {
|
||
const matchedAttr = findMatchedAttr(param, val)
|
||
args.push({ arg_id: param.id, name: param.name, attr_id: matchedAttr?.id || (param.attrs?.[0]?.id || 0), number: val })
|
||
} else {
|
||
const attr = param.attrs?.[0]
|
||
args.push({ arg_id: param.id, name: param.name, attr_id: attr?.id || 0, value: val })
|
||
}
|
||
}
|
||
return args.length > 0 ? JSON.stringify(args) : ''
|
||
}
|
||
|
||
const findMatchedAttr = (param, numValue) => {
|
||
if (!param.attrs || !param.attrs.length) return null
|
||
const sorted = [...param.attrs].sort((a, b) => (a.phase || 0) - (b.phase || 0))
|
||
for (const attr of sorted) {
|
||
const phase = attr.phase || 0
|
||
if (attr.rangeType === 'before' && numValue <= phase) return attr
|
||
if (attr.rangeType === 'after' && numValue >= phase) return attr
|
||
if (attr.rangeType === 'equal' && numValue === phase) return attr
|
||
}
|
||
return sorted[sorted.length - 1]
|
||
}
|
||
|
||
// 套餐选择变更
|
||
const handlePlanChange = (planId) => {
|
||
if (!planId) return
|
||
const plan = planList.value.find(p => p.id === planId)
|
||
if (plan) {
|
||
if (plan.price) orderForm.price = plan.price
|
||
if (plan.renew_price) orderForm.renew_price = plan.renew_price
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const submitForm = async () => {
|
||
const formEl = step3FormRef.value
|
||
if (formEl) {
|
||
const valid = await formEl.validate().catch(() => false)
|
||
if (!valid) return
|
||
}
|
||
|
||
submitLoading.value = true
|
||
try {
|
||
let expireTimeStr = ''
|
||
if (orderForm.expire_time) {
|
||
expireTimeStr = new Date(Number(orderForm.expire_time)).toISOString()
|
||
}
|
||
|
||
const argsStr = productParams.value.length ? buildArgsJson() : orderForm.args || ''
|
||
|
||
const submitData = {
|
||
name: orderForm.name,
|
||
table: orderForm.table || '',
|
||
type: orderForm.type,
|
||
user_id: Number(orderForm.user_id),
|
||
commodity_id: Number(orderForm.commodity_id),
|
||
pay_num: Number(orderForm.pay_num),
|
||
price: Number(orderForm.price) / 100,
|
||
renew_price: Number(orderForm.renew_price) / 100,
|
||
expire_time: expireTimeStr,
|
||
state: Number(orderForm.state),
|
||
pay_type: orderForm.pay_type || 'default',
|
||
args: argsStr,
|
||
note: orderForm.note || ''
|
||
}
|
||
|
||
if (orderForm.plan_id) submitData.plan_id = Number(orderForm.plan_id)
|
||
if (orderForm.discount_code_id) submitData.discount_code_id = Number(orderForm.discount_code_id)
|
||
if (orderForm.user_coupon_id) submitData.user_coupon_id = Number(orderForm.user_coupon_id)
|
||
if (orderForm.payment_order_id) submitData.payment_order_id = orderForm.payment_order_id
|
||
|
||
if (dialogType.value === 'edit') {
|
||
submitData.order_id = Number(orderForm.order_id)
|
||
}
|
||
|
||
let res
|
||
if (dialogType.value === 'add') {
|
||
res = await createOrder(submitData)
|
||
} else {
|
||
res = await updateOrder(submitData)
|
||
}
|
||
|
||
if (res.data.code === 200) {
|
||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||
dialogVisible.value = false
|
||
fetchOrderList()
|
||
} else {
|
||
ElMessage.error(res.data.message || '操作失败')
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 用户选择处理
|
||
const handleUserSelect = (user) => {
|
||
orderForm.user_id = user.user_id
|
||
selectedUserInfo.value = user
|
||
}
|
||
|
||
const clearUser = () => {
|
||
orderForm.user_id = undefined
|
||
selectedUserInfo.value = null
|
||
}
|
||
|
||
// 商品选择处理
|
||
const handleProductSelect = (product) => {
|
||
orderForm.commodity_id = product.id
|
||
selectedProductInfo.value = product
|
||
if (product.table) orderForm.table = product.table
|
||
orderForm.plan_id = null
|
||
// 清空旧参数值
|
||
Object.keys(argValues).forEach(k => delete argValues[k])
|
||
fetchPlanList(product.id)
|
||
fetchProductParams(product.id)
|
||
}
|
||
|
||
const clearProduct = () => {
|
||
orderForm.commodity_id = 0
|
||
orderForm.plan_id = null
|
||
selectedProductInfo.value = null
|
||
planList.value = []
|
||
productParams.value = []
|
||
Object.keys(argValues).forEach(k => delete argValues[k])
|
||
}
|
||
|
||
// 优惠码选择处理
|
||
const handleDiscountCodeSelect = (code) => {
|
||
orderForm.discount_code_id = code.id
|
||
selectedDiscountCodeInfo.value = code
|
||
}
|
||
|
||
const clearDiscountCode = () => {
|
||
orderForm.discount_code_id = 0
|
||
selectedDiscountCodeInfo.value = null
|
||
}
|
||
|
||
// 代金券选择处理
|
||
const handleVoucherSelect = (voucher) => {
|
||
orderForm.user_coupon_id = voucher.id
|
||
selectedVoucherInfo.value = voucher
|
||
}
|
||
|
||
const clearVoucher = () => {
|
||
orderForm.user_coupon_id = 0
|
||
selectedVoucherInfo.value = null
|
||
}
|
||
|
||
// 清除所有选择信息
|
||
const clearAllSelections = () => {
|
||
selectedUserInfo.value = null
|
||
selectedProductInfo.value = null
|
||
selectedDiscountCodeInfo.value = null
|
||
selectedVoucherInfo.value = null
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
if (route.query.key) queryParams.key = String(route.query.key)
|
||
if (route.query.user_id) queryParams.user_id = String(route.query.user_id)
|
||
if (route.query.state) queryParams.state = String(route.query.state)
|
||
fetchOrderList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.order-list-container {
|
||
padding: 0;
|
||
}
|
||
|
||
.main-container {
|
||
border: 1px solid #e1e8ed;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.filter-section {
|
||
padding: 0;
|
||
border-bottom: 1px solid #e1e8ed;
|
||
background: #fafbfc;
|
||
}
|
||
|
||
.filter-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
padding: 16px 20px;
|
||
gap: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-form {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-form :deep(.el-form-item) {
|
||
margin-bottom: 0;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.filter-form :deep(.el-form-item__label) {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.table-section {
|
||
padding: 0;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.amount {
|
||
color: #f56c6c;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.renew-price {
|
||
color: #409eff;
|
||
font-weight: 500;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 20px;
|
||
padding: 16px 20px;
|
||
border-top: 1px solid #e1e8ed;
|
||
background: #fafbfc;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
/* 分步向导样式 */
|
||
.wizard-container {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.wizard-steps {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.wizard-body {
|
||
min-height: 280px;
|
||
}
|
||
|
||
.wizard-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
}
|
||
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-top: 4px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.args-section {
|
||
margin-top: 16px;
|
||
padding: 16px;
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
border: 1px solid #ebeef5;
|
||
}
|
||
|
||
.args-section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.number-arg-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 表格样式优化 */
|
||
:deep(.el-table) {
|
||
border: none;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
:deep(.el-table__header) {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
:deep(.el-table th) {
|
||
background: #f8f9fa !important;
|
||
border-bottom: 2px solid #e1e8ed;
|
||
color: #2c3e50;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
|
||
:deep(.el-table td) {
|
||
border-bottom: 1px solid #f0f2f5;
|
||
color: #34495e;
|
||
}
|
||
|
||
:deep(.el-table tr:hover > td) {
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 0;
|
||
}
|
||
|
||
/* 骨架屏样式 */
|
||
.skeleton-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.skeleton-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
gap: 16px;
|
||
}
|
||
|
||
.skeleton-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.skeleton-cell {
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.skeleton-checkbox { width: 55px; }
|
||
.skeleton-id { width: 100px; }
|
||
.skeleton-name { width: 180px; }
|
||
.skeleton-user { width: 100px; }
|
||
.skeleton-price { width: 120px; }
|
||
.skeleton-status { width: 100px; }
|
||
.skeleton-time { width: 170px; }
|
||
.skeleton-action { width: 200px; height: 32px; }
|
||
|
||
@keyframes skeleton-loading {
|
||
0% { background-position: 200% 0; }
|
||
100% { background-position: -200% 0; }
|
||
}
|
||
|
||
/* 选择器清除图标样式 */
|
||
.clear-icon {
|
||
cursor: pointer;
|
||
color: #909399;
|
||
transition: color 0.2s;
|
||
}
|
||
|
||
.clear-icon:hover {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.unit-input-row { display: flex; align-items: center; gap: 6px; width: 100%; }
|
||
.unit-text { font-size: 13px; color: #606266; flex-shrink: 0; white-space: nowrap; }
|
||
|
||
.error-text {
|
||
color: #f56c6c;
|
||
font-size: 12px;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
word-break: break-all;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.text-muted {
|
||
color: #c0c4cc;
|
||
}
|
||
</style>
|