| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- <template>
- <div class="order-manage admin-page">
- <div class="header">
- <h2 class="admin-page-title">订单管理</h2>
- <div class="filters filter-bar">
- <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
- <el-select v-model="statusFilter" placeholder="订单状态" @change="loadList" clearable>
- <el-option label="全部状态" value="" />
- <el-option label="待支付" value="pending" />
- <el-option label="已支付(待发货)" value="paid" />
- <el-option label="待收货" value="pending_receipt" />
- <el-option label="已完成" value="completed" />
- <el-option label="已取消" value="cancelled" />
- <el-option label="退款中" value="refunding" />
- <el-option label="已退款" value="refunded" />
- <el-option label="已关闭" value="closed" />
- </el-select>
- </div>
- </div>
- <div class="table-scroll-wrap-lg">
- <div style="overflow:auto;max-height:calc(100vh - 300px);">
- <el-table style="max-height:calc(100vh - 300px);" :data="list" v-loading="loading" border stripe>
- <el-table-column prop="orderNo" label="订单号" width="180" />
- <el-table-column label="商品" min-width="160">
- <template slot-scope="{ row }">
- <div style="display:flex;align-items:center;gap:8px;">
- <img v-if="row.coverImage" :src="row.coverImage" style="width:40px;height:40px;object-fit:cover;border-radius:4px;" />
- <span>{{ row.productName || '-' }}</span>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="quantity" label="数量" width="60" align="center" />
- <el-table-column label="金额" width="100">
- <template slot-scope="{ row }">
- {{ formatPrice(row.totalAmount) }}
- </template>
- </el-table-column>
- <el-table-column label="状态" width="100">
- <template slot-scope="{ row }">
- <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="paymentMethod" label="支付方式" width="100">
- <template slot-scope="{ row }">
- {{ payMethodLabel(row.paymentMethod) }}
- </template>
- </el-table-column>
- <el-table-column label="下单时间" width="155">
- <template slot-scope="{ row }">
- {{ formatTime(row.createdAt) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200" fixed="right">
- <template slot-scope="{ row }">
- <el-button size="mini" type="primary" @click="viewDetail(row)">详情</el-button>
- <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
- <el-button size="mini">
- 更多<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item v-if="row.status === 'paid'" command="ship" icon="el-icon-s-cooperation">发货</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'paid' || row.status === 'refunding'" command="close" icon="el-icon-circle-close">关闭</el-dropdown-item>
- <el-dropdown-item v-if="row.status === 'refunding'" command="refund" icon="el-icon-warning-outline">退款审核</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table></div>
- </div>
- <el-pagination
- @size-change="onSizeChange"
- @current-change="onPageChange"
- :current-page="page"
- :page-size="size"
- :total="total"
- layout="total, sizes, prev, pager, next"
- :page-sizes="[10, 20, 50]"
- class="pagination-wrap"
- />
- <!-- Detail Dialog -->
- <el-dialog title="订单详情" :visible.sync="detailDialogVisible" width="700px" top="5vh">
- <template v-if="detail">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="订单编号" :span="2">{{ detail.orderNo }}</el-descriptions-item>
- <el-descriptions-item label="商品名称">{{ detail.productName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="商品类型">{{ typeLabel(detail.productType) }}</el-descriptions-item>
- <el-descriptions-item label="数量">{{ detail.quantity }}</el-descriptions-item>
- <el-descriptions-item label="单价">{{ formatPrice(detail.unitPrice) }}</el-descriptions-item>
- <el-descriptions-item label="总金额">{{ formatPrice(detail.totalAmount) }}</el-descriptions-item>
- <el-descriptions-item label="优惠金额">{{ detail.discountAmount ? formatPrice(detail.discountAmount) : '-' }}</el-descriptions-item>
- <el-descriptions-item label="订单状态">
- <el-tag :type="statusType(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
- </el-descriptions-item>
- <el-descriptions-item label="支付方式">{{ payMethodLabel(detail.paymentMethod) }}</el-descriptions-item>
- <el-descriptions-item label="支付时间">{{ detail.paidAt ? formatTime(detail.paidAt) : '-' }}</el-descriptions-item>
- <el-descriptions-item label="买家备注">{{ detail.remark || '-' }}</el-descriptions-item>
- <el-descriptions-item label="下单时间">{{ formatTime(detail.createdAt) }}</el-descriptions-item>
- </el-descriptions>
- </template>
- <span slot="footer">
- <el-button @click="detailDialogVisible = false">关闭</el-button>
- </span>
- </el-dialog>
- <!-- Ship Dialog -->
- <el-dialog title="发货" :visible.sync="shipDialogVisible" width="450px">
- <el-alert title="确认给以下订单发货" type="info" :closable="false" style="margin-bottom:15px;">
- <template slot>
- 订单号:<strong>{{ shipTarget?.orderNo }}</strong>
- </template>
- </el-alert>
- <el-form :model="shipForm" label-width="90px">
- <el-form-item label="物流公司">
- <el-input v-model="shipForm.expressCompany" placeholder="如:顺丰、中通" />
- </el-form-item>
- <el-form-item label="物流单号">
- <el-input v-model="shipForm.trackingNumber" placeholder="请输入物流单号" />
- </el-form-item>
- </el-form>
- <span slot="footer">
- <el-button @click="shipDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="confirmShip" :loading="shipLoading">确认发货</el-button>
- </span>
- </el-dialog>
- <!-- Refund Dialog -->
- <el-dialog title="退款审核" :visible.sync="refundDialogVisible" width="450px">
- <template v-if="refundTarget">
- <el-alert title="退款申请" type="warning" :closable="false" style="margin-bottom:15px;">
- <template slot>
- 订单号:<strong>{{ refundTarget.orderNo }}</strong><br>
- 退款原因:{{ refundTarget.refundReason || '-' }}
- </template>
- </el-alert>
- <el-input v-model="refundRejectReason" type="textarea" placeholder="拒绝原因(选填)" :rows="3" />
- </template>
- <span slot="footer">
- <el-button @click="refundDialogVisible = false">取消</el-button>
- <el-button type="danger" @click="handleRejectRefund" :loading="refundLoading">拒绝退款</el-button>
- <el-button type="success" @click="handleApproveRefundConfirm" :loading="refundLoading">同意退款</el-button>
- </span>
- </el-dialog>
- </div>
- </template>
- <script>
- import { getOrderList, shipOrder, closeOrder, approveRefund, rejectRefund } from '@/api/admin'
- export default {
- data() {
- return {
- list: [],
- loading: false,
- page: 1,
- size: 20,
- total: 0,
- statusFilter: '',
- keyword: '',
- detailDialogVisible: false,
- detail: null,
- shipDialogVisible: false,
- shipTarget: null,
- shipLoading: false,
- shipForm: { expressCompany: '', trackingNumber: '' },
- refundDialogVisible: false,
- refundTarget: null,
- refundLoading: false,
- refundRejectReason: ''
- }
- },
- mounted() {
- this.loadList()
- },
- methods: {
- handleSearch() {
- this.page = 1
- this.loadList()
- },
- async loadList() {
- this.loading = true
- try {
- var params = { page: this.page, size: this.size }
- if (this.statusFilter) {
- params.status = this.statusFilter
- }
- if (this.keyword) {
- params.keyword = this.keyword
- }
- var res = await getOrderList(params)
- this.list = res.data.records || []
- this.total = res.data.total || 0
- this.page = res.data.page || this.page
- this.size = res.data.size || this.size
- } catch (e) {
- this.$message.error('加载订单列表失败')
- } finally {
- this.loading = false
- }
- },
- onPageChange(val) {
- this.page = val
- this.loadList()
- },
- onSizeChange(val) {
- this.size = val
- this.page = 1
- this.loadList()
- },
- viewDetail(row) {
- this.detail = row
- this.detailDialogVisible = true
- },
- showShipDialog(row) {
- this.shipTarget = row
- this.shipForm = { expressCompany: '', trackingNumber: '' }
- this.shipDialogVisible = true
- },
- async confirmShip() {
- if (!this.shipForm.trackingNumber.trim()) {
- return this.$message.warning('请填写物流单号')
- }
- this.shipLoading = true
- try {
- await shipOrder(this.shipTarget.orderNo, this.shipForm.trackingNumber, this.shipForm.expressCompany)
- this.$message.success('发货成功')
- this.shipDialogVisible = false
- this.loadList()
- } catch (e) {
- this.$message.error(e.message || '发货失败')
- } finally {
- this.shipLoading = false
- }
- },
- async handleClose(row) {
- try {
- await this.$confirm('确定关闭订单 ' + row.orderNo + '?', '提示', { type: 'warning' })
- await closeOrder(row.orderNo)
- this.$message.success('订单已关闭')
- this.loadList()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error(e.message || '关闭失败')
- }
- }
- },
- showRefundDialog(row) {
- this.refundTarget = row
- this.refundRejectReason = ''
- this.refundDialogVisible = true
- },
- async handleApproveRefundConfirm() {
- this.refundLoading = true
- try {
- await this.$confirm('确认同意退款?', '提示', { type: 'warning' })
- await approveRefund(this.refundTarget.orderNo)
- this.$message.success('已同意退款')
- this.refundDialogVisible = false
- this.loadList()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error(e.message || '操作失败')
- }
- } finally {
- this.refundLoading = false
- }
- },
- async handleRejectRefund() {
- this.refundLoading = true
- try {
- await this.$confirm('确认拒绝退款?', '提示', { type: 'warning' })
- await rejectRefund(this.refundTarget.orderNo, this.refundRejectReason)
- this.$message.success('已拒绝退款')
- this.refundDialogVisible = false
- this.loadList()
- } catch (e) {
- if (e !== 'cancel') {
- this.$message.error(e.message || '操作失败')
- }
- } finally {
- this.refundLoading = false
- }
- },
- canClose(status) {
- return ['pending', 'paid', 'pending_receipt', 'refunding'].includes(status)
- },
- statusType(status) {
- var map = { pending: 'warning', paid: 'primary', pending_receipt: '', completed: 'success', cancelled: 'info', refunding: 'danger', refunded: 'danger', closed: 'info' }
- return map[status] || 'info'
- },
- statusLabel(status) {
- var map = { pending: '待支付', paid: '待发货', pending_receipt: '待收货', completed: '已完成', cancelled: '已取消', refunding: '退款中', refunded: '已退款', closed: '已关闭' }
- return map[status] || status
- },
- payMethodLabel(method) {
- var map = { wechat: '微信支付', balance: '余额支付', points: '积分兑换', test: '测试支付' }
- return map[method] || method || '-'
- },
- typeLabel(type) {
- var map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
- return map[type] || type || '-'
- },
- formatPrice(cents) {
- if (cents == null) return '-'
- return '¥' + (cents / 100).toFixed(2)
- },
- formatTime(t) {
- if (!t) return '-'
- return t.replace ? t.replace('T', ' ').substring(0, 19) : t
- },
- handleActionCmd(row, cmd) {
- switch (cmd) {
- case 'ship': this.showShipDialog(row); break;
- case 'close': this.handleClose(row); break;
- case 'refund': this.showRefundDialog(row); break;
- }
- }
- }
- }
- </script>
- <style scoped>
- .order-manage { padding: 20px; }
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- flex-wrap: wrap;
- }
- .header h2 { margin: 0; }
- .filters {
- display: flex;
- gap: 10px;
- align-items: center;
- }
- </style>
|