Files
OpticalSystem/admin-front/src/views/jysystem/order/index.vue
2026-07-14 14:53:12 +08:00

2452 lines
89 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="订单编号" prop="orderNo">
<el-input
v-model="queryParams.orderNo"
placeholder="请输入订单编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="品牌方" prop="brandTenantId">
<el-select v-model="queryParams.brandTenantId" placeholder="请选择品牌方" clearable filterable>
<el-option
v-for="item in brandTenantList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="镜片厂" prop="lensFactoryTenantId">
<el-select v-model="queryParams.lensFactoryTenantId" placeholder="请选择镜片厂" clearable filterable>
<el-option
v-for="item in lensFactoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="订单状态" prop="orderStatus">
<el-select v-model="queryParams.orderStatus" placeholder="请选择订单状态" clearable @change="handleOrderStatusChange">
<el-option
v-for="dict in dict.type.jy_order_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="采购结算" prop="purchaseSettleStatus">
<el-select v-model="queryParams.purchaseSettleStatus" placeholder="请选择" clearable>
<el-option
v-for="dict in dict.type.jy_purchase_settle_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="销售结算" prop="saleSettleStatus">
<el-select v-model="queryParams.saleSettleStatus" placeholder="请选择" clearable>
<el-option
v-for="dict in dict.type.jy_sale_settle_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="收货人" prop="receiverName">
<el-input
v-model="queryParams.receiverName"
placeholder="请输入收货人姓名"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['jysystem:order:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['jysystem:order:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['jysystem:order:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-document"
size="mini"
@click="handleGenerateSettlement"
v-hasPermi="['jysystem:feeSettlement:generate']"
>生成费用结算单</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
:disabled="multiple"
@click="handleBatchExportExcel"
v-hasPermi="['jysystem:order:export']"
>批量导出Excel</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-download"
size="mini"
:disabled="multiple"
@click="handleBatchExportPdf"
v-hasPermi="['jysystem:order:export']"
>批量导出PDF</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['jysystem:order:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
:type="activeQuickFilter === 'pendingAssign' ? 'primary' : ''"
plain
size="mini"
@click="handleQuickFilter('pendingAssign')"
>待分配</el-button>
</el-col>
<el-col :span="1.5">
<el-button
:type="activeQuickFilter === 'exchangeApply' ? 'primary' : ''"
plain
size="mini"
@click="handleQuickFilter('exchangeApply')"
>换货申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button
:type="activeQuickFilter === 'exchangeRejected' ? 'primary' : ''"
plain
size="mini"
@click="handleQuickFilter('exchangeRejected')"
>拒绝换货申请</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="orderList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="订单编号" align="center" prop="orderNo" min-width="200" show-overflow-tooltip />
<el-table-column label="品牌方" align="center" prop="brandTenantName" min-width="110" show-overflow-tooltip />
<el-table-column label="镜片厂" align="center" min-width="110" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{ formatLensFactory(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="通用镜片" align="center" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{ formatLens(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column label="B价格" align="center" width="90">
<template slot-scope="scope">
<span>{{ formatPriceAmount(formatPriceB(scope.row)) }}</span>
</template>
</el-table-column>
<el-table-column label="C价格" align="center" width="90">
<template slot-scope="scope">
<span>{{ formatPriceAmount(formatPriceC(scope.row)) }}</span>
</template>
</el-table-column>
<el-table-column label="物流费用" align="center" width="90">
<template slot-scope="scope">
<span>{{ formatPriceAmount(formatLogisticsFee(scope.row)) }}</span>
</template>
</el-table-column>
<el-table-column label="渐进多焦点价格" align="center" width="120">
<template slot-scope="scope">
<span>{{ formatPriceAmount(formatProgressiveMultifocalPrice(scope.row)) }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" align="center" prop="orderStatus" width="130">
<template slot-scope="scope">
<el-select
v-if="canEditOrderStatus"
v-model="scope.row.orderStatus"
size="mini"
class="order-status-select"
@visible-change="visible => handleStatusSelectVisible(scope.row, visible)"
@change="handleOrderStatusChange(scope.row)"
>
<el-option
v-for="dict in dict.type.jy_order_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
<dict-tag v-else :options="dict.type.jy_order_status" :value="scope.row.orderStatus"/>
</template>
</el-table-column>
<el-table-column label="采购结算" align="center" prop="purchaseSettleStatus" width="90">
<template slot-scope="scope">
<dict-tag :options="dict.type.jy_purchase_settle_status" :value="scope.row.purchaseSettleStatus"/>
</template>
</el-table-column>
<el-table-column label="销售结算" align="center" prop="saleSettleStatus" width="90">
<template slot-scope="scope">
<dict-tag :options="dict.type.jy_sale_settle_status" :value="scope.row.saleSettleStatus"/>
</template>
</el-table-column>
<el-table-column label="收货人" align="center" prop="receiverName" width="90" />
<el-table-column label="手机号" align="center" prop="receiverPhone" width="120" />
<el-table-column label="收货地址" align="center" prop="receiverAddress" min-width="160" show-overflow-tooltip />
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="420" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleDetail(scope.row)"
v-hasPermi="['jysystem:order:query']"
>详情</el-button>
<el-button
v-if="canViewLogistics(scope.row)"
size="mini"
type="text"
@click="handleViewLogistics(scope.row)"
v-hasPermi="['jysystem:order:query']"
>查看物流</el-button>
<el-button
v-if="scope.row.orderStatus === ORDER_STATUS_SUBMITTED || scope.row.orderStatus === ORDER_STATUS_REJECTED || scope.row.orderStatus === ORDER_STATUS_CANCELLED || scope.row.orderStatus === ORDER_STATUS_WITHDRAWN"
size="mini"
type="text"
icon="el-icon-s-check"
@click="handleAssign(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>分配</el-button>
<el-button
v-if="scope.row.orderStatus === ORDER_STATUS_SUBMITTED"
size="mini"
type="text"
icon="el-icon-close"
@click="handleReject(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>驳回</el-button>
<el-button
v-if="scope.row.orderStatus === ORDER_STATUS_WITHDRAWN"
size="mini"
type="text"
icon="el-icon-circle-close"
@click="handleClose(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>关闭</el-button>
<el-button
v-if="canCompleteOrder(scope.row)"
size="mini"
type="text"
icon="el-icon-check"
@click="handleComplete(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>完成</el-button>
<el-button
v-if="scope.row.orderStatus === ORDER_STATUS_EXCHANGE_APPLY"
size="mini"
type="text"
icon="el-icon-check"
style="color: #67c23a"
@click="handleAgreeExchange(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>同意换货申请</el-button>
<el-button
v-if="scope.row.orderStatus === ORDER_STATUS_EXCHANGE_APPLY"
size="mini"
type="text"
icon="el-icon-close"
style="color: #f56c6c"
@click="handleRejectExchange(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>拒绝换货申请</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['jysystem:order:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['jysystem:order:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog
:visible.sync="open"
width="1200px"
append-to-body
destroy-on-close
custom-class="lens-order-form-dialog"
>
<div slot="title" class="lens-dialog-header">
<div class="lens-dialog-title-wrap">
<h3 class="lens-dialog-title">{{ title }}</h3>
<p class="lens-dialog-subtitle">{{ formDialogSubtitle }}</p>
</div>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="110px" class="lens-order-form">
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">01</span>
<div class="section-text">
<h4>订单信息</h4>
<p>订单基础信息、品牌方与镜片配置</p>
</div>
</div>
<el-row :gutter="20" class="section-body">
<el-col :span="12">
<el-form-item label="订单编号" prop="orderNo">
<el-input v-model="form.orderNo" placeholder="留空则自动生成" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="订单状态" prop="orderStatus">
<el-select v-model="form.orderStatus" placeholder="请选择订单状态" style="width: 100%">
<el-option
v-for="dict in dict.type.jy_order_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品牌方" prop="brandTenantId">
<el-select v-model="form.brandTenantId" placeholder="请选择品牌方" clearable filterable style="width: 100%">
<el-option
v-for="item in brandTenantList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="镜片厂" prop="lensFactoryTenantId">
<el-select v-model="form.lensFactoryTenantId" placeholder="请选择镜片厂" clearable filterable style="width: 100%" @change="handleFormLensFactoryChange">
<el-option
v-for="item in lensFactoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品">
<el-input :value="formatProduct(form)" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="通用镜片" prop="lensId">
<el-select v-model="form.lensId" placeholder="请选择通用镜片" clearable filterable style="width: 100%">
<el-option
v-for="item in lensList"
:key="item.id"
:label="formatLensOption(item)"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">02</span>
<div class="section-text">
<h4>价格与结算</h4>
<p>价格信息及采购、销售结算状态</p>
</div>
</div>
<el-row :gutter="20" class="section-body">
<el-col :span="8">
<el-form-item label="B价格" prop="priceB">
<el-input-number v-model="form.priceB" :precision="2" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="C价格" prop="priceC">
<el-input-number v-model="form.priceC" :precision="2" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="物流费用" prop="logisticsFee">
<el-input-number v-model="form.logisticsFee" :precision="2" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="渐进多焦点">
<el-switch
v-model="form.progressiveMultifocal"
:active-value="1"
:inactive-value="0"
active-text=""
inactive-text=""
@change="handleProgressiveMultifocalChange"
/>
</el-form-item>
</el-col>
<el-col v-if="form.progressiveMultifocal === 1" :span="8">
<el-form-item label="渐进多焦点价格" prop="progressiveMultifocalPrice">
<el-input-number
v-model="form.progressiveMultifocalPrice"
:precision="2"
:min="0"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购结算" prop="purchaseSettleStatus">
<el-radio-group v-model="form.purchaseSettleStatus">
<el-radio
v-for="dict in dict.type.jy_purchase_settle_status"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="销售结算" prop="saleSettleStatus">
<el-radio-group v-model="form.saleSettleStatus">
<el-radio
v-for="dict in dict.type.jy_sale_settle_status"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">03</span>
<div class="section-text">
<h4>收货信息</h4>
<p>镜片寄送地址与联系方式</p>
</div>
</div>
<el-row :gutter="20" class="section-body">
<el-col :span="8">
<el-form-item label="收货人姓名" prop="receiverName">
<el-input v-model="form.receiverName" placeholder="请输入收货人姓名" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="收货人手机号" prop="receiverPhone">
<el-input v-model="form.receiverPhone" placeholder="请输入收货人手机号" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="收货人地址" prop="receiverAddress">
<el-input v-model="form.receiverAddress" placeholder="请输入收货人地址" />
</el-form-item>
</el-col>
</el-row>
</section>
<section class="lens-form-section lens-form-section--optometry">
<div class="section-head">
<span class="section-index">04</span>
<div class="section-text">
<h4>验光参数</h4>
<p>录入左右眼验光数据</p>
</div>
</div>
<div class="section-body">
<el-table :data="form.optometryList" class="lens-optometry-table" size="small">
<el-table-column label="左右" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.eyeSide === 'OS' ? '左眼(OS)' : '右眼(OD)' }}</span>
</template>
</el-table-column>
<el-table-column label="球镜S" align="center" min-width="90">
<template slot-scope="scope">
<el-input-number v-model="scope.row.sphereS" :precision="2" :step="0.25" controls-position="right" style="width: 100%" @change="val => onSphereSChange(val, scope.row)" />
</template>
</el-table-column>
<el-table-column label="柱镜C" align="center" min-width="90">
<template slot-scope="scope">
<el-input-number
v-model="scope.row.cylinderC"
:precision="2"
:step="0.25"
:min="getCylinderCMin(scope.row)"
:max="getCylinderCMax(scope.row)"
controls-position="right"
style="width: 100%"
@change="val => onCylinderCChange(val, scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="轴位A" align="center" min-width="80">
<template slot-scope="scope">
<el-input-number v-model="scope.row.axisA" :min="0" :max="180" :disabled="!isAxisAEditable(scope.row)" controls-position="right" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="瞳距(mm)" align="center" min-width="90">
<template slot-scope="scope">
<el-input-number v-model="scope.row.pd" :precision="2" :min="0" controls-position="right" style="width: 100%" />
</template>
</el-table-column>
<el-table-column v-if="form.progressiveMultifocal === 1" label="瞳高(mm)" align="center" min-width="80">
<template slot-scope="scope">
<el-input-number v-model="scope.row.pupilHeight" :precision="2" :min="0" controls-position="right" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="远用VA(视力值)" align="center" min-width="90">
<template slot-scope="scope">
<el-input v-model="scope.row.distanceVa" placeholder="远用VA" />
</template>
</el-table-column>
<el-table-column v-if="form.progressiveMultifocal === 1" label="近用ADD(屈光度)" align="center" min-width="100">
<template slot-scope="scope">
<el-input-number v-model="scope.row.nearAdd" :precision="2" :step="0.25" :min="0.75" :max="4" controls-position="right" style="width: 100%" />
</template>
</el-table-column>
</el-table>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">05</span>
<div class="section-text">
<h4>附件与备注</h4>
<p>上传相关资料并补充说明</p>
</div>
</div>
<el-row :gutter="20" class="section-body">
<el-col :span="24">
<el-form-item label="附件" prop="attachment">
<file-upload v-model="form.attachment" :limit="5" :file-type="attachmentFileTypes" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</section>
</el-form>
<div slot="footer" class="lens-dialog-footer">
<el-button class="lens-btn-ghost" @click="cancel">取 消</el-button>
<el-button class="lens-btn-primary" @click="submitForm">确 定</el-button>
</div>
</el-dialog>
<el-dialog title="驳回订单" :visible.sync="rejectOpen" width="520px" append-to-body>
<el-form ref="rejectForm" :model="rejectForm" :rules="rejectRules" label-width="80px">
<el-form-item label="订单编号">
<span>{{ rejectForm.orderNo || '-' }}</span>
</el-form-item>
<el-form-item label="驳回原因" prop="reason">
<el-input
v-model="rejectForm.reason"
type="textarea"
:rows="5"
placeholder="请输入驳回原因"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="danger" :loading="rejectLoading" @click="submitReject">确 定</el-button>
<el-button @click="rejectOpen = false">取 消</el-button>
</div>
</el-dialog>
<el-dialog title="拒绝换货申请" :visible.sync="exchangeRejectOpen" width="520px" append-to-body>
<el-form ref="exchangeRejectForm" :model="exchangeRejectForm" :rules="exchangeRejectRules" label-width="110px">
<el-form-item label="订单编号">
<span>{{ exchangeRejectForm.orderNo || '-' }}</span>
</el-form-item>
<el-form-item label="拒绝原因" prop="reason">
<el-input
v-model="exchangeRejectForm.reason"
type="textarea"
:rows="5"
placeholder="请输入拒绝换货申请原因"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="danger" :loading="exchangeRejectLoading" @click="submitExchangeReject">确 定</el-button>
<el-button @click="exchangeRejectOpen = false">取 消</el-button>
</div>
</el-dialog>
<el-dialog
:key="assignDialogKey"
:visible.sync="assignOpen"
width="720px"
append-to-body
destroy-on-close
custom-class="lens-order-detail-dialog"
@closed="resetAssignDialog"
>
<div slot="title" class="lens-dialog-header">
<div class="lens-dialog-title-wrap">
<h3 class="lens-dialog-title">分配镜片厂</h3>
<p class="lens-dialog-subtitle">{{ assignDialogSubtitle }}</p>
</div>
</div>
<el-form ref="assignForm" :model="assignForm" :rules="assignRules" label-width="110px" class="lens-order-detail">
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">01</span>
<div class="section-text">
<h4>订单信息</h4>
<p>待分配订单的基础信息</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions">
<el-descriptions-item label="订单编号">{{ assignForm.orderNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="渐进多焦点">{{ assignForm.progressiveMultifocal === 1 ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="通用镜片" :span="2">{{ assignForm.lensText || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">02</span>
<div class="section-text">
<h4>分配镜片厂</h4>
<p>选择承接该订单的镜片厂</p>
</div>
</div>
<div class="section-body">
<el-form-item label="镜片厂" prop="lensFactoryTenantId">
<el-select
v-model="assignForm.lensFactoryTenantId"
placeholder="请选择镜片厂"
clearable
filterable
style="width: 100%"
@change="handleAssignFactoryChange"
>
<el-option
v-for="item in assignLensFactoryList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</div>
</section>
<section v-if="hasAssignPreview" class="lens-form-section">
<div class="section-head">
<span class="section-index">03</span>
<div class="section-text">
<h4>分配价格</h4>
<p>根据镜片厂自动核算的价格信息</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions detail-descriptions--price">
<el-descriptions-item v-if="assignPreview.priceB != null" label="B价格">
{{ formatPriceAmount(assignPreview.priceB) }}
</el-descriptions-item>
<el-descriptions-item v-if="assignPreview.priceC != null" label="C价格">
{{ formatPriceAmount(assignPreview.priceC) }}
</el-descriptions-item>
<el-descriptions-item
v-if="assignForm.progressiveMultifocal === 1 && assignPreview.progressiveMultifocalPrice != null"
label="渐进多焦点价格"
:span="2"
>
{{ formatPriceAmount(assignPreview.progressiveMultifocalPrice) }}
</el-descriptions-item>
</el-descriptions>
</div>
</section>
</el-form>
<div slot="footer" class="lens-dialog-footer">
<el-button class="lens-btn-ghost" @click="assignOpen = false">取 消</el-button>
<el-button class="lens-btn-primary" :loading="assignLoading" @click="submitAssign">保 存</el-button>
</div>
</el-dialog>
<el-dialog
:visible.sync="detailOpen"
width="1200px"
append-to-body
custom-class="lens-order-detail-dialog"
>
<div slot="title" class="lens-dialog-header">
<div class="lens-dialog-title-wrap">
<h3 class="lens-dialog-title">订单详情</h3>
<p class="lens-dialog-subtitle">{{ detailDialogSubtitle }}</p>
</div>
</div>
<div class="lens-order-detail">
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">01</span>
<div class="section-text">
<h4>订单信息</h4>
<p>订单基础信息、品牌方与镜片配置</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions">
<el-descriptions-item label="订单编号">{{ detail.orderNo }}</el-descriptions-item>
<el-descriptions-item label="订单状态">
<dict-tag :options="dict.type.jy_order_status" :value="detail.orderStatus" />
</el-descriptions-item>
<el-descriptions-item label="品牌方">{{ detail.brandTenantName || '-' }}</el-descriptions-item>
<el-descriptions-item label="产品">{{ formatProduct(detail) }}</el-descriptions-item>
<el-descriptions-item label="镜片厂">
<el-tag
v-if="isPendingTagText(formatLensFactory(detail))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatLensFactory(detail) }}</el-tag>
<span v-else>{{ formatLensFactory(detail) }}</span>
</el-descriptions-item>
<el-descriptions-item label="通用镜片">{{ formatLens(detail) }}</el-descriptions-item>
<el-descriptions-item label="采购结算">
<dict-tag :options="dict.type.jy_purchase_settle_status" :value="detail.purchaseSettleStatus" />
</el-descriptions-item>
<el-descriptions-item label="销售结算">
<dict-tag :options="dict.type.jy_sale_settle_status" :value="detail.saleSettleStatus" />
</el-descriptions-item>
</el-descriptions>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">02</span>
<div class="section-text">
<h4>收货信息</h4>
<p>镜片寄送地址与联系方式</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions">
<el-descriptions-item label="收货人">{{ detail.receiverName || '-' }}</el-descriptions-item>
<el-descriptions-item label="手机号">{{ detail.receiverPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="收货地址" :span="2">{{ detail.receiverAddress || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">03</span>
<div class="section-text">
<h4>价格与结算</h4>
<p>价格信息及合计金额</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions detail-descriptions--price">
<el-descriptions-item label="B价格">
<el-tag
v-if="isPendingTagText(formatPriceAmount(formatPriceB(detail)))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatPriceAmount(formatPriceB(detail)) }}</el-tag>
<span v-else>{{ formatPriceAmount(formatPriceB(detail)) }}</span>
</el-descriptions-item>
<el-descriptions-item label="C价格">
<el-tag
v-if="isPendingTagText(formatPriceAmount(formatPriceC(detail)))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatPriceAmount(formatPriceC(detail)) }}</el-tag>
<span v-else>{{ formatPriceAmount(formatPriceC(detail)) }}</span>
</el-descriptions-item>
<el-descriptions-item label="物流费用">{{ formatPriceAmount(formatDetailLogisticsFee(detail)) }}</el-descriptions-item>
<el-descriptions-item label="渐进多焦点">{{ detail.progressiveMultifocal === 1 ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item v-if="detail.progressiveMultifocal === 1" label="渐进多焦点价格">
<el-tag
v-if="isPendingTagText(formatPriceAmount(formatProgressiveMultifocalPrice(detail)))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatPriceAmount(formatProgressiveMultifocalPrice(detail)) }}</el-tag>
<span v-else>{{ formatPriceAmount(formatProgressiveMultifocalPrice(detail)) }}</span>
</el-descriptions-item>
<el-descriptions-item label="B合计">
<el-tag
v-if="isPendingTagText(formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')) }}</el-tag>
<span v-else class="price-total-value">{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')) }}</span>
</el-descriptions-item>
<el-descriptions-item label="C合计">
<el-tag
v-if="isPendingTagText(formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')))"
type="danger"
size="small"
:disable-transitions="true"
>{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')) }}</el-tag>
<span v-else class="price-total-value">{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')) }}</span>
</el-descriptions-item>
</el-descriptions>
<div class="detail-logistics-fee-block">
<el-table :data="detailLogisticsLogList" class="lens-detail-table detail-logistics-fee-table" size="small">
<el-table-column label="物流公司" align="center" prop="expressCompany" min-width="120" show-overflow-tooltip />
<el-table-column label="物流单号" align="center" prop="expressNo" min-width="160" show-overflow-tooltip />
<el-table-column label="物流费用" align="center" width="100">
<template slot-scope="scope">
<span>{{ formatPriceAmount(formatLogisticsLogFee(scope.row, detail)) }}</span>
</template>
</el-table-column>
<el-table-column label="发货日期" align="center" width="120">
<template slot-scope="scope">
<span>{{ formatShipDate(scope.row.operateTime) }}</span>
</template>
</el-table-column>
</el-table>
<div v-if="!detailLogisticsLogList.length" class="detail-empty-tip">暂无物流费用记录</div>
</div>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">04</span>
<div class="section-text">
<h4>附件与备注</h4>
<p>订单相关资料与补充说明</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="2" border size="small" class="detail-descriptions">
<el-descriptions-item label="附件" :span="2">
<template v-if="parseAttachment(detail.attachment).length">
<template v-for="(url, index) in parseAttachment(detail.attachment)">
<el-link
v-if="isAttachmentImage(url)"
:key="'preview-' + index"
type="primary"
class="detail-attachment-link"
@click="handleAttachmentPreview(url)"
>{{ getAttachmentName(url) }}</el-link>
<el-link
v-else
:key="'link-' + index"
:href="getAttachmentUrl(url)"
target="_blank"
type="primary"
class="detail-attachment-link"
>{{ getAttachmentName(url) }}</el-link>
</template>
</template>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</section>
<section class="lens-form-section lens-form-section--optometry">
<div class="section-head">
<span class="section-index">05</span>
<div class="section-text">
<h4>验光参数</h4>
<p>左右眼验光数据明细</p>
</div>
</div>
<div class="section-body">
<el-table
:data="detailOptometryList"
:span-method="detailOptometrySpanMethod"
class="lens-optometry-table"
size="small"
>
<el-table-column label="左右" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.eyeSide === 'OS' ? '左眼(OS)' : '右眼(OD)' }}</span>
</template>
</el-table-column>
<el-table-column label="球镜S" align="center" min-width="90">
<template slot-scope="scope">
<span>{{ formatDetailOptometryDecimal(scope.row.sphereS) }}</span>
</template>
</el-table-column>
<el-table-column label="柱镜C" align="center" min-width="90">
<template slot-scope="scope">
<span>{{ formatDetailOptometryDecimal(scope.row.cylinderC) }}</span>
</template>
</el-table-column>
<el-table-column align="center" min-width="80">
<template slot="header">
<span>轴位A</span>
</template>
<template slot-scope="scope">
<span>{{ scope.row.axisA != null ? scope.row.axisA : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="瞳距(mm)" align="center" min-width="90">
<template slot-scope="scope">
<span>{{ formatDetailPdValue(scope.row, scope.$index) }}</span>
</template>
</el-table-column>
<el-table-column v-if="detail.progressiveMultifocal === 1" label="瞳高(mm)" align="center" min-width="80">
<template slot-scope="scope">
<span>{{ scope.row.pupilHeight != null ? scope.row.pupilHeight : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="远用VA(视力值)" align="center" min-width="90">
<template slot-scope="scope">
<span>{{ scope.row.distanceVa != null && scope.row.distanceVa !== '' ? scope.row.distanceVa : '-' }}</span>
</template>
</el-table-column>
<el-table-column v-if="detail.progressiveMultifocal === 1" label="近用ADD(屈光度)" align="center" min-width="100">
<template slot-scope="scope">
<span>{{ formatDetailNearAdd(scope.row.nearAdd) }}</span>
</template>
</el-table-column>
</el-table>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">06</span>
<div class="section-text">
<h4>状态变更记录</h4>
<p>订单全流程状态流转轨迹</p>
</div>
</div>
<div class="section-body">
<el-table :data="detail.statusLogList || []" class="lens-detail-table" size="small">
<el-table-column label="订单状态" align="center" prop="orderStatus" width="120">
<template slot-scope="scope">
<dict-tag :options="dict.type.jy_order_status" :value="scope.row.orderStatus" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="160" show-overflow-tooltip />
<el-table-column label="操作人" align="center" prop="operator" width="100" />
<el-table-column label="操作时间" align="center" prop="operateTime" width="160" />
</el-table>
</div>
</section>
</div>
</el-dialog>
<el-dialog :visible.sync="attachmentPreviewVisible" title="图片预览" width="800px" append-to-body>
<img :src="attachmentPreviewUrl" style="max-width: 100%; display: block; margin: 0 auto;" />
</el-dialog>
<el-dialog
:visible.sync="logisticsDialogVisible"
width="720px"
append-to-body
custom-class="lens-order-detail-dialog"
>
<div slot="title" class="lens-dialog-header">
<div class="lens-dialog-title-wrap">
<h3 class="lens-dialog-title">物流信息</h3>
<p class="lens-dialog-subtitle">{{ logisticsDialogSubtitle }}</p>
</div>
</div>
<div class="lens-order-detail">
<section v-if="logisticsBasic" class="lens-form-section">
<div class="section-head">
<span class="section-index">01</span>
<div class="section-text">
<h4>物流概要</h4>
<p>订单关联的物流公司与单号</p>
</div>
</div>
<div class="section-body">
<el-descriptions :column="1" border size="small" class="detail-descriptions">
<el-descriptions-item label="订单编号">{{ logisticsBasic.orderNo }}</el-descriptions-item>
<el-descriptions-item label="物流公司">{{ logisticsBasic.expressCompany }}</el-descriptions-item>
<el-descriptions-item label="物流单号">{{ logisticsBasic.expressNo }}</el-descriptions-item>
</el-descriptions>
</div>
</section>
<section class="lens-form-section">
<div class="section-head">
<span class="section-index">02</span>
<div class="section-text">
<h4>物流轨迹</h4>
<p>最新物流动态与时间记录</p>
</div>
</div>
<div class="section-body">
<div v-if="logisticsDetail && logisticsDetail.traces && logisticsDetail.traces.length > 0" class="logistics-traces">
<el-timeline>
<el-timeline-item
v-for="(item, idx) in logisticsDetail.traces"
:key="idx"
:timestamp="item.timeDesc || ''"
>
{{ item.line }}
</el-timeline-item>
</el-timeline>
</div>
<div v-else class="detail-empty-tip">未查到物流轨迹</div>
</div>
</section>
</div>
<div slot="footer" class="lens-dialog-footer">
<el-button class="lens-btn-ghost" @click="logisticsDialogVisible = false">关 闭</el-button>
</div>
</el-dialog>
<el-dialog title="生成费用结算单" :visible.sync="settlementGenerateOpen" width="480px" append-to-body>
<el-form ref="settlementGenerateForm" :model="settlementGenerateForm" :rules="settlementGenerateRules" label-width="100px">
<el-form-item label="结算年月" prop="settleYearMonth">
<el-date-picker
v-model="settlementGenerateForm.settleYearMonth"
type="month"
value-format="yyyy-MM"
placeholder="选择年月"
style="width: 100%"
/>
</el-form-item>
<el-alert
title="仅汇总所选年月内已完成或换货完成状态的订单品牌方生成应收(AR)镜片厂生成应付(AP)"
type="info"
:closable="false"
show-icon
/>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="settlementGenerateOpen = false">取 消</el-button>
<el-button type="primary" :loading="settlementGenerateLoading" @click="submitGenerateSettlement">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import axios from 'axios'
import { getToken } from '@/utils/auth'
import { isExternal } from '@/utils/validate'
import { listOrder, getOrder, getOrderLogistics, delOrder, addOrder, updateOrder, previewAssignPrice, assignOrder, rejectOrder, closeOrder, completeOrder, agreeExchangeOrder, rejectExchangeOrder, changeOrderStatus } from "@/api/jysystem/order";
import { checkPermi } from "@/utils/permission";
import { listBrandTenant, listLensFactoryTenant } from "@/api/jysystem/tenant";
import { listLensOptions } from "@/api/jysystem/lens";
import { checkFeeSettlement, generateFeeSettlement } from "@/api/jysystem/feeSettlement";
export default {
name: "Order",
dicts: ['jy_order_status', 'jy_purchase_settle_status', 'jy_sale_settle_status'],
data() {
return {
loading: true,
ids: [],
single: true,
multiple: true,
showSearch: true,
total: 0,
orderList: [],
brandTenantList: [],
lensFactoryList: [],
assignLensFactoryList: [],
lensList: [],
title: "",
open: false,
detailOpen: false,
detail: {},
detailPdMode: 'single',
PD_TYPE_SINGLE: '1',
PD_TYPE_DUAL: '2',
OPTOMETRY_PD_COLUMN_INDEX: 4,
assignOpen: false,
assignLoading: false,
assignDialogKey: 0,
assignDialogToken: 0,
assignForm: {
orderId: null,
orderNo: null,
lensId: null,
lensText: '',
progressiveMultifocal: 0,
lensFactoryTenantId: null
},
assignPreview: {},
assignRules: {
lensFactoryTenantId: [{ required: true, message: '请选择镜片厂', trigger: 'change' }]
},
rejectOpen: false,
rejectLoading: false,
rejectForm: {
orderId: null,
orderNo: '',
reason: ''
},
rejectRules: {
reason: [{ required: true, message: '请输入驳回原因', trigger: 'blur' }]
},
exchangeRejectOpen: false,
exchangeRejectLoading: false,
exchangeRejectForm: {
orderId: null,
orderNo: '',
reason: ''
},
exchangeRejectRules: {
reason: [{ required: true, message: '请输入拒绝换货申请原因', trigger: 'blur' }]
},
ORDER_STATUS_DRAFT: '1',
ORDER_STATUS_SUBMITTED: '2',
ORDER_STATUS_REJECTED: '5',
ORDER_STATUS_CANCELLED: '18',
ORDER_STATUS_WITHDRAWN: '20',
ORDER_STATUS_SHIPPED: '8',
ORDER_STATUS_COMPLETED: '9',
ORDER_STATUS_EXCHANGE_APPLY: '12',
ORDER_STATUS_EXCHANGE_AGREED: '13',
ORDER_STATUS_EXCHANGE_LOGISTICS: '15',
ORDER_STATUS_EXCHANGE_COMPLETED: '17',
ORDER_STATUS_EXCHANGE_REJECTED: '21',
logisticsDialogVisible: false,
logisticsBasic: null,
logisticsDetail: null,
attachmentPreviewVisible: false,
attachmentPreviewUrl: '',
settlementGenerateOpen: false,
settlementGenerateLoading: false,
settlementGenerateForm: {
settleYearMonth: undefined
},
settlementGenerateRules: {
settleYearMonth: [{ required: true, message: '请选择结算年月', trigger: 'change' }]
},
activeQuickFilter: null,
queryParams: {
pageNum: 1,
pageSize: 10,
orderNo: null,
brandTenantId: null,
lensFactoryTenantId: null,
orderStatus: null,
orderStatusIn: null,
purchaseSettleStatus: null,
saleSettleStatus: null,
receiverName: null
},
form: {},
rules: {},
baseUrl: process.env.VUE_APP_BASE_API,
attachmentFileTypes: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf', 'png', 'jpg', 'jpeg', 'zip']
};
},
computed: {
formDialogSubtitle() {
if (this.form && this.form.id) {
return '更新订单完整信息、价格结算与验光参数';
}
return '填写订单信息、价格结算与验光参数';
},
detailDialogSubtitle() {
if (this.detail && this.detail.orderNo) {
return `订单编号 ${this.detail.orderNo} · 查看订单完整信息与流转记录`;
}
return '查看订单完整信息、价格结算与流转记录';
},
logisticsDialogSubtitle() {
if (this.logisticsBasic && this.logisticsBasic.orderNo) {
return `订单编号 ${this.logisticsBasic.orderNo} · 查看物流公司与轨迹信息`;
}
return '查看物流公司与轨迹信息';
},
assignDialogSubtitle() {
if (this.assignForm && this.assignForm.orderNo) {
return `订单编号 ${this.assignForm.orderNo} · 选择镜片厂并确认分配价格`;
}
return '选择镜片厂并确认分配价格';
},
hasAssignPreview() {
const preview = this.assignPreview || {};
return preview.priceB != null
|| preview.priceC != null
|| (this.assignForm.progressiveMultifocal === 1 && preview.progressiveMultifocalPrice != null);
},
detailPdLabel() {
return this.detailPdMode === 'single' ? '单瞳距' : '瞳距';
},
detailLogisticsLogList() {
return this.buildDetailLogisticsLogList(this.detail);
},
detailOptometryList() {
return this.sortOptometryList((this.detail && this.detail.optometryList) || []);
},
canEditOrderStatus() {
return checkPermi(['jysystem:order:edit']);
}
},
created() {
this.getList();
this.loadBrandTenants();
this.loadLensFactoryList();
this.loadLensList();
},
methods: {
getList() {
this.loading = true;
listOrder(this.queryParams).then(response => {
this.orderList = response.rows;
this.total = response.total;
this.loading = false;
});
},
loadBrandTenants() {
listBrandTenant().then(response => {
this.brandTenantList = response.data || [];
});
},
loadLensFactoryList() {
listLensFactoryTenant().then(response => {
this.lensFactoryList = response.data || [];
});
},
loadLensList() {
listLensOptions().then(response => {
this.lensList = response.data || [];
});
},
isPendingTagText(text) {
return text === '未分配' || text === '未核算';
},
formatLensOption(item) {
if (!item) return '-';
const name = item.productName || '';
const material = item.material || '';
return material ? `${name}(${material})` : name;
},
formatProduct(row) {
if (!row) {
return '-';
}
if (row.productModel || row.productName) {
const model = row.productModel || '';
const name = row.productName || '';
return model ? `${model}(${name})` : name || '-';
}
return '-';
},
formatLens(row) {
if (!row) {
return '-';
}
if (row.lensProductName || row.lensMaterial) {
return this.formatLensOption({ productName: row.lensProductName, material: row.lensMaterial });
}
return '-';
},
isPendingAssign(row) {
if (!row) return false;
const status = String(row.orderStatus);
return status === this.ORDER_STATUS_DRAFT
|| status === this.ORDER_STATUS_SUBMITTED
|| status === this.ORDER_STATUS_REJECTED
|| status === this.ORDER_STATUS_CANCELLED
|| status === this.ORDER_STATUS_WITHDRAWN;
},
formatLensFactory(row) {
if (!row) return '-';
if (this.isPendingAssign(row)) return '未分配';
return row.lensFactoryTenantName || '-';
},
formatPriceB(row) {
if (!row) return '-';
if (this.isExchangeOrder(row)) return '-';
if (this.isPendingAssign(row)) return '未核算';
return row.priceB != null ? row.priceB : '-';
},
formatPriceC(row) {
if (!row) return '-';
if (this.isExchangeOrder(row)) return '-';
if (this.isPendingAssign(row)) return '未核算';
return row.priceC != null ? row.priceC : '-';
},
formatProgressiveMultifocalPrice(row) {
if (!row) return '-';
if (this.isExchangeOrder(row)) return '-';
if (Number(row.progressiveMultifocal) !== 1) return '-';
if (this.isPendingAssign(row)) return '未核算';
return row.progressiveMultifocalPrice != null ? row.progressiveMultifocalPrice : '-';
},
isExchangeOrder(row) {
if (!row) return false;
if (row.orderNo && String(row.orderNo).startsWith('HH')) return true;
const status = row.orderStatus != null ? String(row.orderStatus) : '';
return status === this.ORDER_STATUS_EXCHANGE_APPLY
|| status === this.ORDER_STATUS_EXCHANGE_AGREED
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED;
},
formatDetailPriceTotal(detail, priceKey) {
if (this.isExchangeOrder(detail)) return this.formatDetailLogisticsFee(detail);
return this.formatPriceTotal(detail, priceKey);
},
formatLogisticsFee(row) {
if (!row) return '-';
if (!this.isExchangeOrder(row)) return '-';
const fee = this.parseLogisticsFeeValue(row.logisticsFee);
return fee != null ? fee : '-';
},
formatPriceBC(row) {
return `${this.formatPriceB(row)} / ${this.formatPriceC(row)}`;
},
formatPriceAmount(value) {
if (value == null || value === '' || value === '-') return '-';
if (value === '未核算') return '未核算';
const num = Number(value);
if (Number.isNaN(num)) return value;
return num.toFixed(2);
},
buildDetailLogisticsLogList(detail) {
const list = (detail && detail.statusLogList) || [];
return list
.filter(item => item.expressNo != null && String(item.expressNo).trim() !== '')
.sort((a, b) => {
const timeA = a.operateTime ? new Date(a.operateTime).getTime() : 0;
const timeB = b.operateTime ? new Date(b.operateTime).getTime() : 0;
return timeB - timeA;
});
},
parseLogisticsFeeValue(value) {
if (value == null || value === '') return null;
const num = Number(value);
return Number.isNaN(num) ? null : num;
},
formatLogisticsLogFee(log, detail) {
if (!log) return '-';
const fee = this.parseLogisticsFeeValue(log.logisticsFee);
if (fee != null) return fee;
const logs = this.buildDetailLogisticsLogList(detail);
if (logs.length === 1 && detail && detail.logisticsFee != null) {
const legacyFee = this.parseLogisticsFeeValue(detail.logisticsFee);
if (legacyFee != null) return legacyFee;
}
return '-';
},
sumDetailLogisticsFee(detail) {
if (!detail) return '-';
const logs = this.buildDetailLogisticsLogList(detail);
let sum = 0;
let hasFee = false;
logs.forEach(log => {
const fee = this.parseLogisticsFeeValue(log.logisticsFee);
if (fee != null) {
sum += fee;
hasFee = true;
}
});
if (!hasFee && detail.logisticsFee != null) {
const legacyFee = this.parseLogisticsFeeValue(detail.logisticsFee);
if (legacyFee != null) return legacyFee;
}
if (!hasFee) return '-';
return sum;
},
formatDetailLogisticsFee(detail) {
return this.sumDetailLogisticsFee(detail);
},
formatShipDate(time) {
if (!time) {
return '-';
}
const text = String(time);
return text.length >= 10 ? text.substring(0, 10) : text;
},
formatPriceTotal(row, priceKey) {
if (!row) return '-';
if (this.isPendingAssign(row)) return '未核算';
const base = row[priceKey] != null ? Number(row[priceKey]) : 0;
const logisticsRaw = this.sumDetailLogisticsFee(row);
const logistics = logisticsRaw === '-' ? 0 : Number(logisticsRaw);
const pmf = row.progressiveMultifocalPrice != null ? Number(row.progressiveMultifocalPrice) : 0;
if ([base, logistics, pmf].some(value => Number.isNaN(value))) return '-';
return (base + logistics + pmf).toFixed(2);
},
parseAttachment(attachment) {
if (!attachment) return [];
return String(attachment).split(',').map(item => item.trim()).filter(item => item !== '');
},
getAttachmentName(url) {
if (!url) return '';
const index = url.lastIndexOf('/');
return index > -1 ? url.slice(index + 1) : url;
},
getAttachmentUrl(url) {
if (!url) return '';
if (isExternal(url)) return url;
return this.baseUrl + url;
},
isAttachmentImage(url) {
const fileName = this.getAttachmentName(url);
const ext = (fileName.split('.').pop() || '').toLowerCase();
return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext);
},
handleAttachmentPreview(url) {
this.attachmentPreviewUrl = this.getAttachmentUrl(url);
this.attachmentPreviewVisible = true;
},
sortOptometryList(list) {
const order = { OS: 0, OD: 1 };
return [...list].sort((a, b) => {
const aOrder = order[a.eyeSide] != null ? order[a.eyeSide] : 99;
const bOrder = order[b.eyeSide] != null ? order[b.eyeSide] : 99;
return aOrder - bOrder;
});
},
createDefaultOptometryList() {
return [
{ eyeSide: 'OS', sphereS: 0, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null },
{ eyeSide: 'OD', sphereS: 0, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null }
];
},
detectPdMode(optometryList) {
if (!optometryList || !optometryList.length) {
return 'single';
}
const pdType = optometryList[0].pdType;
if (pdType === this.PD_TYPE_DUAL) return 'dual';
if (pdType === this.PD_TYPE_SINGLE) return 'single';
const hasDual = optometryList.some(item => item.dualPd != null);
return hasDual ? 'dual' : 'single';
},
detailOptometrySpanMethod({ rowIndex, columnIndex }) {
if (columnIndex !== this.OPTOMETRY_PD_COLUMN_INDEX || this.detailPdMode !== 'single') {
return { rowspan: 1, colspan: 1 };
}
if (rowIndex === 0) {
return { rowspan: 2, colspan: 1 };
}
return { rowspan: 0, colspan: 0 };
},
formatDetailPdValue(row, rowIndex) {
if (!row) return '-';
if (this.detailPdMode === 'dual') {
return row.dualPd != null ? row.dualPd : '-';
}
if (rowIndex > 0) return '';
const list = this.detailOptometryList;
const pdRow = list.find(item => item.pd != null) || list[0];
return pdRow && pdRow.pd != null ? pdRow.pd : '-';
},
formatDetailOptometryDecimal(value) {
if (value == null || value === '') return '-';
const num = Number(value);
if (Number.isNaN(num)) return String(value);
return num.toFixed(2);
},
formatDetailNearAdd(value) {
if (value == null || value === '') return '-';
const num = Number(value);
if (Number.isNaN(num)) return String(value);
return '+' + num.toFixed(2);
},
isAxisAEditable(row) {
const val = row && row.cylinderC;
if (val == null || val === '') return false;
const num = Number(val);
return !Number.isNaN(num) && num !== 0;
},
getSphereSign(sphereS) {
if (sphereS == null || sphereS === '') return 0;
const num = Number(sphereS);
if (Number.isNaN(num) || num === 0) return 0;
return num > 0 ? 1 : -1;
},
getCylinderCMin(row) {
return this.getSphereSign(row && row.sphereS) > 0 ? 0 : -10;
},
getCylinderCMax(row) {
return this.getSphereSign(row && row.sphereS) < 0 ? 0 : 10;
},
alignCylinderSignWithSphere(row) {
if (!row || row.cylinderC == null || row.cylinderC === '') return;
let cylinder = Number(row.cylinderC);
if (Number.isNaN(cylinder) || cylinder === 0) return;
const sphereSign = this.getSphereSign(row.sphereS);
if (sphereSign > 0 && cylinder < 0) cylinder = Math.abs(cylinder);
else if (sphereSign < 0 && cylinder > 0) cylinder = -Math.abs(cylinder);
row.cylinderC = Math.min(this.getCylinderCMax(row), Math.max(this.getCylinderCMin(row), cylinder));
},
onSphereSChange(val, row) {
this.alignCylinderSignWithSphere(row);
if (!this.isAxisAEditable(row)) row.axisA = null;
},
onCylinderCChange(val, row) {
if (val == null || val === '' || Number(val) === 0) {
row.axisA = null;
return;
}
this.alignCylinderSignWithSphere(row);
if (!this.isAxisAEditable(row)) row.axisA = null;
},
cancel() {
this.open = false;
this.reset();
},
handleProgressiveMultifocalChange(value) {
if (Number(value) !== 1) {
this.form.progressiveMultifocalPrice = 0;
if (this.form.optometryList) {
this.form.optometryList.forEach(row => {
row.nearAdd = null;
row.pupilHeight = null;
});
}
return;
}
if (!this.form.lensFactoryTenantId) {
this.$modal.msgWarning("请先选择镜片厂");
this.form.progressiveMultifocalPrice = 0;
return;
}
this.refreshFormPricesByFactory(this.form.lensFactoryTenantId, { progressiveOnly: true });
},
handleFormLensFactoryChange(lensFactoryTenantId) {
this.refreshFormPricesByFactory(lensFactoryTenantId);
},
refreshFormPricesByFactory(lensFactoryTenantId, options = {}) {
const progressiveOnly = options.progressiveOnly === true;
if (!lensFactoryTenantId) {
if (!progressiveOnly) {
this.form.priceB = 0;
this.form.priceC = 0;
}
this.form.progressiveMultifocalPrice = 0;
return;
}
if (!this.form.id) {
if (Number(this.form.progressiveMultifocal) === 1) {
this.form.progressiveMultifocalPrice = this.getProgressiveMultifocalPriceByFactory(lensFactoryTenantId);
}
return;
}
previewAssignPrice(this.form.id, lensFactoryTenantId).then(response => {
this.applyAssignPreviewPrices(response.data || {}, { progressiveOnly });
}).catch(() => {
if (!progressiveOnly) {
this.form.priceB = 0;
this.form.priceC = 0;
}
this.form.progressiveMultifocalPrice = 0;
this.$modal.msgWarning("未能获取该镜片厂价格请确认镜片厂已配置对应镜片价格");
});
},
applyAssignPreviewPrices(preview, options = {}) {
const progressiveOnly = options.progressiveOnly === true;
if (!progressiveOnly) {
if (preview.priceB != null) {
this.form.priceB = Number(preview.priceB);
}
if (preview.priceC != null) {
this.form.priceC = Number(preview.priceC);
}
}
if (Number(this.form.progressiveMultifocal) === 1) {
if (preview.progressiveMultifocalPrice != null) {
this.form.progressiveMultifocalPrice = Number(preview.progressiveMultifocalPrice);
} else {
this.form.progressiveMultifocalPrice = this.getProgressiveMultifocalPriceByFactory(this.form.lensFactoryTenantId);
}
} else {
this.form.progressiveMultifocalPrice = 0;
}
},
getProgressiveMultifocalPriceByFactory(lensFactoryTenantId) {
if (lensFactoryTenantId == null || lensFactoryTenantId === "") {
return 0;
}
const factory = (this.lensFactoryList || []).find(item => String(item.id) === String(lensFactoryTenantId));
if (!factory || factory.progressiveMultifocalPrice == null) {
return 0;
}
const num = Number(factory.progressiveMultifocalPrice);
return Number.isNaN(num) ? 0 : num;
},
resolveProgressiveMultifocalPrice() {
const price = this.form.progressiveMultifocalPrice;
if (price != null && Number(price) > 0) {
return Number(price);
}
return this.getProgressiveMultifocalPriceByFactory(this.form.lensFactoryTenantId);
},
reset() {
this.form = {
id: null,
orderNo: null,
brandTenantId: null,
lensFactoryTenantId: null,
lensId: null,
progressiveMultifocal: 0,
progressiveMultifocalPrice: 0,
priceB: 0,
priceC: 0,
logisticsFee: null,
orderStatus: '1',
purchaseSettleStatus: '0',
saleSettleStatus: '0',
receiverName: null,
receiverPhone: null,
receiverAddress: null,
attachment: null,
remark: null,
optometryList: this.createDefaultOptometryList()
};
this.resetForm("form");
},
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
handleOrderStatusChange() {
this.activeQuickFilter = null;
this.queryParams.orderStatusIn = null;
},
handleQuickFilter(type) {
if (this.activeQuickFilter === type) {
this.activeQuickFilter = null;
this.queryParams.orderStatusIn = null;
} else {
this.activeQuickFilter = type;
this.queryParams.orderStatus = null;
if (type === 'pendingAssign') {
this.queryParams.orderStatusIn = `${this.ORDER_STATUS_SUBMITTED},${this.ORDER_STATUS_WITHDRAWN}`;
} else if (type === 'exchangeApply') {
this.queryParams.orderStatusIn = this.ORDER_STATUS_EXCHANGE_APPLY;
} else if (type === 'exchangeRejected') {
this.queryParams.orderStatusIn = this.ORDER_STATUS_EXCHANGE_REJECTED;
}
}
this.handleQuery();
},
resetQuery() {
this.resetForm("queryForm");
this.activeQuickFilter = null;
this.queryParams.orderStatusIn = null;
this.handleQuery();
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
handleAdd() {
this.reset();
this.open = true;
this.title = "添加订单";
},
handleUpdate(row) {
this.reset();
const id = row.id || this.ids;
getOrder(id).then(response => {
this.form = response.data;
this.form.progressiveMultifocal = this.form.progressiveMultifocal != null
? Number(this.form.progressiveMultifocal) : 0;
this.form.progressiveMultifocalPrice = this.form.progressiveMultifocalPrice != null
? Number(this.form.progressiveMultifocalPrice) : 0;
if (Number(this.form.progressiveMultifocal) !== 1) {
this.form.progressiveMultifocalPrice = 0;
}
if (!this.form.optometryList || this.form.optometryList.length === 0) {
this.form.optometryList = this.createDefaultOptometryList();
}
this.open = true;
this.title = "修改订单";
});
},
handleDetail(row) {
getOrder(row.id).then(response => {
this.detail = response.data || {};
this.detailPdMode = this.detectPdMode(this.detail.optometryList);
this.detailOpen = true;
});
},
canViewLogistics(row) {
if (!row || row.orderStatus == null) return false;
const status = String(row.orderStatus);
return status === this.ORDER_STATUS_SHIPPED
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|| status === this.ORDER_STATUS_COMPLETED
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED;
},
canCompleteOrder(row) {
if (!row || row.orderStatus == null) return false;
const status = String(row.orderStatus);
return status === this.ORDER_STATUS_SHIPPED
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS;
},
handleViewLogistics(row) {
if (!row || !row.id) return;
const noLogisticsTip = '未查到物流信息';
getOrderLogistics(row.id).then((body) => {
const data = body && body.data;
if (!data || !data.expressNo) {
this.$message.info(noLogisticsTip);
return;
}
const orderNo = data.orderNo || row.orderNo;
const expressCompany = data.expressCompany || '未知物流公司';
const expressNo = data.expressNo;
const mobile = data.receiverPhone || row.receiverPhone;
const base = process.env.VUE_APP_BASE_API || '';
return axios.get(`${base}/tool/express/query`, {
params: { expressNo, mobile: mobile || undefined },
headers: {
Authorization: getToken() ? 'Bearer ' + getToken() : ''
},
timeout: 15000
}).then((res) => {
const pkg = res.data;
this.logisticsBasic = { orderNo, expressCompany, expressNo };
if (!pkg || Number(pkg.code) !== 200) {
this.logisticsDetail = null;
this.logisticsDialogVisible = true;
return;
}
const detailRaw = pkg.data && pkg.data.data ? pkg.data.data : pkg.data;
if (!detailRaw) {
this.logisticsDetail = null;
this.logisticsDialogVisible = true;
return;
}
const traceRawList = detailRaw.logisticsTraceDetailList;
const traceList = Array.isArray(traceRawList) ? traceRawList.slice() : [];
traceList.sort((a, b) => (Number(b.time) || 0) - (Number(a.time) || 0));
const traces = traceList.map(item => ({
timeDesc: item.timeDesc || '',
line: [item.timeDesc, item.desc].filter(Boolean).join(' ')
})).filter(t => t.line);
this.logisticsDetail = traces.length > 0 ? { traces } : null;
this.logisticsDialogVisible = true;
}).catch(() => {
this.logisticsBasic = { orderNo, expressCompany, expressNo };
this.logisticsDetail = null;
this.logisticsDialogVisible = true;
});
}).catch(() => {
this.$message.info(noLogisticsTip);
});
},
handleStatusSelectVisible(row, visible) {
if (visible) {
row._prevOrderStatus = row.orderStatus;
}
},
handleOrderStatusChange(row) {
if (!row || !row.id) {
return;
}
if (row.orderStatus === row._prevOrderStatus) {
return;
}
const label = this.selectDictLabel(this.dict.type.jy_order_status, row.orderStatus);
this.$modal.confirm(`确认将订单"${row.orderNo}"状态变更为"${label}"吗?`).then(() => {
return changeOrderStatus({
orderId: row.id,
orderStatus: row.orderStatus
});
}).then(() => {
row._prevOrderStatus = row.orderStatus;
this.$modal.msgSuccess('状态更新成功');
}).catch(() => {
row.orderStatus = row._prevOrderStatus;
});
},
handleReject(row) {
if (!row || !row.id) {
return;
}
this.rejectForm = {
orderId: row.id,
orderNo: row.orderNo,
reason: ''
};
this.rejectOpen = true;
this.$nextTick(() => {
if (this.$refs.rejectForm) {
this.$refs.rejectForm.clearValidate();
}
});
},
submitReject() {
this.$refs.rejectForm.validate(valid => {
if (!valid) {
return;
}
this.rejectLoading = true;
rejectOrder({
orderId: this.rejectForm.orderId,
reason: this.rejectForm.reason
}).then(() => {
this.$modal.msgSuccess('驳回成功');
this.rejectOpen = false;
this.getList();
}).finally(() => {
this.rejectLoading = false;
});
});
},
handleClose(row) {
if (!row || !row.id) {
return;
}
this.$modal.confirm(`确认关闭订单"${row.orderNo}"吗?`).then(() => {
return closeOrder({ orderId: row.id });
}).then(() => {
this.$modal.msgSuccess('关闭成功');
this.getList();
}).catch(() => {});
},
handleComplete(row) {
if (!row || !row.id) {
return;
}
this.$modal.confirm(`确认完成订单"${row.orderNo}"吗?`).then(() => {
return completeOrder({ orderId: row.id });
}).then(() => {
this.$modal.msgSuccess('操作成功');
this.getList();
}).catch(() => {});
},
handleAgreeExchange(row) {
if (!row || !row.id) {
return;
}
this.$modal.confirm(`确认同意订单"${row.orderNo}"的换货申请吗?`).then(() => {
return agreeExchangeOrder({ orderId: row.id });
}).then(() => {
this.$modal.msgSuccess('已同意换货申请');
this.getList();
}).catch(() => {});
},
handleRejectExchange(row) {
if (!row || !row.id) {
return;
}
this.exchangeRejectForm = {
orderId: row.id,
orderNo: row.orderNo,
reason: ''
};
this.exchangeRejectOpen = true;
this.$nextTick(() => {
if (this.$refs.exchangeRejectForm) {
this.$refs.exchangeRejectForm.clearValidate();
}
});
},
submitExchangeReject() {
this.$refs.exchangeRejectForm.validate(valid => {
if (!valid) {
return;
}
this.exchangeRejectLoading = true;
rejectExchangeOrder({
orderId: this.exchangeRejectForm.orderId,
reason: this.exchangeRejectForm.reason
}).then(() => {
this.$modal.msgSuccess('已拒绝换货申请');
this.exchangeRejectOpen = false;
this.getList();
}).finally(() => {
this.exchangeRejectLoading = false;
});
});
},
loadAssignLensFactoryList(lensId, token) {
if (!lensId) {
this.assignLensFactoryList = [];
return;
}
listLensFactoryTenant(lensId).then(response => {
if (token !== this.assignDialogToken) {
return;
}
this.assignLensFactoryList = response.data || [];
});
},
resetAssignDialog() {
this.assignDialogToken += 1;
this.assignPreview = {};
this.assignLensFactoryList = [];
this.assignLoading = false;
this.assignForm = {
orderId: null,
orderNo: null,
lensId: null,
lensText: '',
progressiveMultifocal: 0,
lensFactoryTenantId: null
};
},
handleAssign(row) {
this.assignDialogToken += 1;
const token = this.assignDialogToken;
this.assignDialogKey += 1;
this.assignPreview = {};
this.assignLensFactoryList = [];
this.assignForm = {
orderId: row.id,
orderNo: row.orderNo,
lensId: row.lensId,
lensText: this.formatLens(row),
progressiveMultifocal: row.progressiveMultifocal,
lensFactoryTenantId: null
};
this.assignOpen = true;
this.loadAssignLensFactoryList(row.lensId, token);
this.$nextTick(() => {
if (this.$refs.assignForm) {
this.resetForm('assignForm');
}
});
},
handleAssignFactoryChange(lensFactoryTenantId) {
if (!lensFactoryTenantId) {
this.assignPreview = {};
return;
}
const token = this.assignDialogToken;
const orderId = this.assignForm.orderId;
previewAssignPrice(orderId, lensFactoryTenantId).then(response => {
if (token !== this.assignDialogToken || this.assignForm.orderId !== orderId) {
return;
}
this.assignPreview = response.data || {};
}).catch(() => {
if (token !== this.assignDialogToken || this.assignForm.orderId !== orderId) {
return;
}
this.assignPreview = {};
});
},
submitAssign() {
this.$refs.assignForm.validate(valid => {
if (!valid) {
return;
}
this.assignLoading = true;
assignOrder({
orderId: this.assignForm.orderId,
lensFactoryTenantId: this.assignForm.lensFactoryTenantId
}).then(() => {
this.$modal.msgSuccess('分配成功');
this.assignOpen = false;
this.getList();
}).finally(() => {
this.assignLoading = false;
});
});
},
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
const payload = {
...this.form,
progressiveMultifocal: this.form.progressiveMultifocal != null
? Number(this.form.progressiveMultifocal) : 0,
progressiveMultifocalPrice: Number(this.form.progressiveMultifocal) === 1
? this.resolveProgressiveMultifocalPrice()
: 0
};
if (Number(payload.progressiveMultifocal) !== 1 && payload.optometryList) {
payload.optometryList.forEach(row => {
row.nearAdd = null;
row.pupilHeight = null;
});
}
if (payload.optometryList) {
payload.optometryList.forEach(row => {
if (!this.isAxisAEditable(row)) {
row.axisA = null;
}
});
}
if (Number(payload.progressiveMultifocal) === 1) {
if (!payload.lensFactoryTenantId) {
this.$modal.msgWarning("选择渐进多焦点时需先选择镜片厂");
return;
}
if (!payload.progressiveMultifocalPrice || Number(payload.progressiveMultifocalPrice) <= 0) {
this.$modal.msgWarning("所选镜片厂未配置渐进多焦点价格");
return;
}
}
if (this.form.id != null) {
updateOrder(payload).then(() => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addOrder(payload).then(() => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除订单编号为"' + ids + '"的数据项?').then(function() {
return delOrder(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
handleGenerateSettlement() {
this.settlementGenerateForm = { settleYearMonth: undefined };
this.settlementGenerateOpen = true;
this.$nextTick(() => {
if (this.$refs.settlementGenerateForm) {
this.$refs.settlementGenerateForm.clearValidate();
}
});
},
submitGenerateSettlement() {
this.$refs.settlementGenerateForm.validate(valid => {
if (!valid) return;
const yearMonth = this.settlementGenerateForm.settleYearMonth;
this.settlementGenerateLoading = true;
checkFeeSettlement(yearMonth).then(res => {
const check = res.data || {};
if (check.exists) {
const nos = (check.settlementNos || []).join('、');
return this.$modal.confirm(
`该年月已存在 ${check.count} 张费用结算单${nos ? '' + nos + '' : ''},是否删除后重新生成?`
).then(() => {
return generateFeeSettlement({ settleYearMonth: yearMonth, forceRegenerate: true });
});
}
return generateFeeSettlement({ settleYearMonth: yearMonth, forceRegenerate: false });
}).then(res => {
if (res) {
this.$modal.msgSuccess(res.msg || '生成成功');
this.settlementGenerateOpen = false;
}
}).catch(() => {}).finally(() => {
this.settlementGenerateLoading = false;
});
});
},
handleExport() {
this.download('jysystem/order/export', {
...this.queryParams
}, `order_${new Date().getTime()}.xlsx`);
},
handleBatchExportExcel() {
if (!this.ids.length) {
this.$modal.msgWarning('请先选择要导出的订单');
return;
}
this.download('jysystem/order/export/batch/' + this.ids.join(','), {}, `order_batch_${new Date().getTime()}.xlsx`);
},
handleBatchExportPdf() {
if (!this.ids.length) {
this.$modal.msgWarning('请先选择要导出的订单');
return;
}
this.download('jysystem/order/exportPdf/batch/' + this.ids.join(','), {}, `order_batch_${new Date().getTime()}.pdf`);
}
}
};
</script>
<style scoped>
.degree-sup {
font-size: 10px;
line-height: 1;
vertical-align: super;
}
.price-total-value {
font-weight: 700;
font-size: 16px;
color: #f56c6c;
}
</style>
<style lang="scss">
.lens-order-form-dialog,
.lens-order-detail-dialog {
border-radius: 16px;
overflow: hidden;
box-shadow: 0 24px 48px rgba(37, 99, 235, 0.12), 0 8px 24px rgba(15, 23, 42, 0.08);
.el-dialog__header {
padding: 20px 28px 18px;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.08) 0%, rgba(147, 51, 234, 0.06) 100%);
border-bottom: 1px solid rgba(37, 99, 235, 0.12);
}
.el-dialog__headerbtn {
top: 22px;
right: 24px;
.el-dialog__close {
color: #64748b;
font-size: 18px;
&:hover {
color: #2563eb;
}
}
}
.el-dialog__body {
padding: 20px 28px 8px;
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
overflow: visible;
}
.el-dialog__footer {
padding: 16px 28px 22px;
background: #fff;
border-top: 1px solid rgba(37, 99, 235, 0.08);
}
}
.lens-dialog-header {
.lens-dialog-title {
margin: 0 0 6px;
font-size: 20px;
font-weight: 700;
background: linear-gradient(90deg, #2563eb 0%, #7c3aed 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.lens-dialog-subtitle {
margin: 0;
font-size: 13px;
color: #64748b;
letter-spacing: 0.02em;
}
}
.lens-order-form,
.lens-order-detail {
.lens-form-section {
margin-bottom: 18px;
padding: 18px 20px 8px;
background: #fff;
border: 1px solid rgba(37, 99, 235, 0.1);
border-radius: 12px;
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #2563eb 0%, #9333ea 100%);
opacity: 0.85;
}
&--optometry {
padding-bottom: 16px;
}
}
.section-head {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 16px;
}
.section-index {
flex-shrink: 0;
width: 36px;
height: 36px;
line-height: 36px;
text-align: center;
font-size: 13px;
font-weight: 700;
color: #2563eb;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.12) 0%, rgba(147, 51, 234, 0.1) 100%);
border: 1px solid rgba(37, 99, 235, 0.15);
border-radius: 10px;
}
.section-text {
h4 {
margin: 0 0 4px;
font-size: 15px;
font-weight: 600;
color: #1e293b;
}
p {
margin: 0;
font-size: 12px;
color: #94a3b8;
}
}
.section-body {
padding-left: 2px;
}
.lens-optometry-table {
border-radius: 10px;
overflow: hidden;
border: 1px solid rgba(37, 99, 235, 0.12);
&::before {
display: none;
}
th {
background: linear-gradient(90deg, #eff6ff 0%, #f5f3ff 100%) !important;
color: #334155 !important;
font-weight: 600;
border-color: rgba(37, 99, 235, 0.1) !important;
}
td {
border-color: rgba(148, 163, 184, 0.2) !important;
background: #fff;
}
tr:hover > td {
background: rgba(239, 246, 255, 0.5) !important;
}
}
.el-input__inner,
.el-textarea__inner {
border-radius: 8px;
border-color: rgba(37, 99, 235, 0.15);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
}
.el-form-item__label {
color: #475569;
font-weight: 500;
}
}
.lens-order-detail {
.detail-descriptions {
border-radius: 10px;
overflow: hidden;
.el-descriptions__table {
table-layout: fixed;
width: 100%;
}
.el-descriptions-item__label {
width: 116px;
min-width: 116px;
background: linear-gradient(90deg, #eff6ff 0%, #f5f3ff 100%) !important;
color: #475569 !important;
font-weight: 600;
border-color: rgba(37, 99, 235, 0.1) !important;
}
.el-descriptions-item__content {
color: #1e293b;
border-color: rgba(37, 99, 235, 0.1) !important;
}
}
.detail-descriptions--price {
.el-descriptions-item__content {
font-variant-numeric: tabular-nums;
}
}
.detail-attachment-link {
margin-right: 12px;
font-weight: 500;
}
.price-total-value {
font-weight: 700;
font-size: 16px;
color: #ef4444;
}
.lens-detail-table {
border-radius: 10px;
overflow: hidden;
border: 1px solid rgba(37, 99, 235, 0.12);
&::before {
display: none;
}
th {
background: linear-gradient(90deg, #eff6ff 0%, #f5f3ff 100%) !important;
color: #334155 !important;
font-weight: 600;
border-color: rgba(37, 99, 235, 0.1) !important;
}
td {
border-color: rgba(148, 163, 184, 0.2) !important;
background: #fff;
color: #334155;
}
tr:hover > td {
background: rgba(239, 246, 255, 0.5) !important;
}
}
.detail-logistics-fee-block {
margin-top: 12px;
}
.detail-empty-tip {
margin-top: 10px;
padding: 14px;
text-align: center;
font-size: 13px;
color: #94a3b8;
background: linear-gradient(90deg, rgba(239, 246, 255, 0.6) 0%, rgba(245, 243, 255, 0.6) 100%);
border: 1px dashed rgba(37, 99, 235, 0.15);
border-radius: 10px;
}
}
.logistics-traces {
max-height: 360px;
overflow-y: auto;
padding: 4px 8px 0;
}
.lens-dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
.lens-btn-ghost {
border-color: #cbd5e1;
color: #64748b;
border-radius: 8px;
padding: 10px 20px;
&:hover {
color: #2563eb;
border-color: #93c5fd;
background: #f8fafc;
}
}
.lens-btn-primary {
border: none;
color: #fff;
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
border-radius: 8px;
padding: 10px 24px;
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.28);
&:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #6d28d9 100%);
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.35);
}
}
}
</style>