Merge branch 'invoice_view'

This commit is contained in:
田香琪
2026-04-14 18:06:59 +08:00
7 changed files with 1069 additions and 382 deletions

View File

@@ -180,6 +180,15 @@ export function receiptList () {
}); });
} }
// 发票详情
export function receiptDetail (id) {
return request({
url: `/buyer/trade/receipt/${id}`,
method: Method.GET,
needToken: true
});
}
// 保存发票信息 // 保存发票信息
export function saveReceipt (params) { export function saveReceipt (params) {
return request({ return request({

View File

@@ -1,30 +1,31 @@
<template> <template>
<div class="invoice-modal"> <div class="invoice-modal">
<Modal v-model="invoiceAvailable" width="600" footer-hide> <Modal v-model="invoiceAvailable" width="640" footer-hide>
<p slot="header"> <p slot="header">
<span>发票信息</span> <span>发票信息</span>
</p> </p>
<!-- 普通发票 -->
<div class="nav-content"> <div class="nav-content">
<Form :model="invoiceForm" ref="form" label-position="left" :rules="ruleInline" :label-width="110"> <Form :model="invoiceForm" ref="form" label-position="left" :rules="formRules" :label-width="118">
<FormItem label="发票类型"> <FormItem label="发票类型">
<RadioGroup v-model="invoice" type="button" button-style="solid"> <RadioGroup v-model="invoice" type="button" button-style="solid" @on-change="onInvoiceKindChange">
<Radio @on-change="changeInvoice" :label="1">电子普通发票</Radio> <Radio :label="1">电子普通发票</Radio>
<Radio :label="2" :disabled="true">增值税专用发票</Radio> <Radio :label="2">增值税专用发票</Radio>
</RadioGroup> </RadioGroup>
</FormItem> </FormItem>
<!-- 电子普通发票 -->
<div v-if="invoice === 1" key="invoice-normal">
<FormItem label="发票抬头"> <FormItem label="发票抬头">
<RadioGroup v-model="type" @on-change="changeInvoice" type="button" button-style="solid"> <RadioGroup v-model="type" type="button" button-style="solid" @on-change="onHeaderTypeChange">
<Radio :label="1">个人</Radio> <Radio :label="1">个人</Radio>
<Radio :label="2">单位</Radio> <Radio :label="2">单位</Radio>
</RadioGroup> </RadioGroup>
</FormItem> </FormItem>
<FormItem label="个人名称" v-if="type === 1" prop="receiptTitle"> <FormItem :label="type === 1 ? '个人名称' : '单位名称'" :prop="type === 1 ? 'personalName' : 'companyName'">
<i-input v-model="invoiceForm.receiptTitle"></i-input> <i-input v-if="type === 1" v-model="invoiceForm.personalName"></i-input>
</FormItem> <i-input v-else v-model="invoiceForm.companyName"></i-input>
<FormItem label="单位名称" v-if="type === 2" prop="receiptTitle">
<i-input v-model="invoiceForm.receiptTitle"></i-input>
</FormItem> </FormItem>
<FormItem label="纳税人识别号" v-if="type === 2" prop="taxpayerId"> <FormItem label="纳税人识别号" v-if="type === 2" prop="taxpayerId">
<i-input v-model="invoiceForm.taxpayerId"></i-input> <i-input v-model="invoiceForm.taxpayerId"></i-input>
</FormItem> </FormItem>
@@ -34,6 +35,49 @@
<Radio label="商品类别">商品类别</Radio> <Radio label="商品类别">商品类别</Radio>
</RadioGroup> </RadioGroup>
</FormItem> </FormItem>
<FormItem label="收票人手机" prop="receiptPhone">
<i-input v-model="invoiceForm.receiptPhone"></i-input>
</FormItem>
<FormItem label="收票人邮箱" prop="receiptEmail">
<i-input v-model="invoiceForm.receiptEmail" placeholder="可选"></i-input>
</FormItem>
</div>
<!-- 增值税专用发票固定为单位 -->
<div v-else key="invoice-vat">
<FormItem label="发票抬头">
<span class="inv-title-fixed">单位</span>
</FormItem>
<FormItem label="单位名称" prop="companyName">
<i-input v-model="invoiceForm.companyName" placeholder="与营业执照一致"></i-input>
</FormItem>
<FormItem label="纳税人识别号" prop="taxpayerId">
<i-input v-model="invoiceForm.taxpayerId"></i-input>
</FormItem>
<FormItem label="单位地址" prop="companyAddress">
<i-input v-model="invoiceForm.companyAddress" placeholder="注册地址"></i-input>
</FormItem>
<FormItem label="单位电话" prop="companyPhone">
<i-input v-model="invoiceForm.companyPhone" placeholder="固话或手机"></i-input>
</FormItem>
<FormItem label="开户银行" prop="bankName">
<i-input v-model="invoiceForm.bankName"></i-input>
</FormItem>
<FormItem label="银行账号" prop="bankAccount">
<i-input v-model="invoiceForm.bankAccount"></i-input>
</FormItem>
<FormItem label="发票内容">
<RadioGroup v-model="invoiceForm.receiptContent" type="button" button-style="solid">
<Radio label="商品明细">商品明细</Radio>
</RadioGroup>
</FormItem>
<FormItem label="收票人手机" prop="receiptPhone">
<i-input v-model="invoiceForm.receiptPhone"></i-input>
</FormItem>
<FormItem label="收票人邮箱" prop="receiptEmail">
<i-input v-model="invoiceForm.receiptEmail" placeholder="可选"></i-input>
</FormItem>
</div>
</Form> </Form>
<div style="text-align: center"> <div style="text-align: center">
<Button type="primary" :loading="loading" @click="submit">保存发票信息</Button> <Button type="primary" :loading="loading" @click="submit">保存发票信息</Button>
@@ -45,106 +89,209 @@
</template> </template>
<script> <script>
import { receiptSelect } from '@/api/cart.js'; import { receiptSelect } from '@/api/cart.js';
import { TINumber } from '@/plugins/RegExp.js'; import { mobile, email } from '@/plugins/RegExp.js';
export default { export default {
name: 'invoiceModal', name: 'invoiceModal',
data () { data () {
return { return {
invoice: 1, // 发票类型 invoice: 1, // 1 电子普通发票 2 增值税专用发票
invoiceAvailable: false, // 模态框显隐 invoiceAvailable: false,
loading: false, // 提交状态 loading: false,
invoiceForm: { invoiceForm: {
// 普票表单 receiptTitle: '',
receiptTitle: '', // 发票抬头 personalName: '',
taxpayerId: '', // 纳税人识别号 companyName: '',
receiptContent: '商品明细' // 发票内容 taxpayerId: '',
receiptContent: '商品明细',
receiptPhone: '',
receiptEmail: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: ''
}, },
type: 1, // 1 个人 2 单位 type: 1
ruleInline: {
taxpayerId: [
{ required: true, message: '请填写纳税人识别号' },
{ pattern: TINumber, message: '请填写正确的纳税人识别号' }
]
}
}; };
}, },
props: ['invoiceData'], props: ['invoiceData'],
computed: {
formRules () {
const receiptPhoneRules = [
{ required: true, message: '请填写收票人手机', trigger: 'blur' },
{ pattern: mobile, message: '请填写正确的手机号码', trigger: 'blur' }
];
// 收票人邮箱可选:仅校验格式;提示文案须与手机区分,避免误用「请填写收票人手机」
const receiptEmailRules = [
{
validator (rule, val, cb) {
const s = val != null ? String(val).trim() : '';
if (!s) {
cb();
return;
}
if (!email.test(s)) {
cb(new Error('请填写正确的收票人邮箱'));
return;
}
cb();
},
trigger: 'blur'
}
];
if (this.invoice === 2) {
return {
companyName: [{ required: true, message: '请填写单位名称', trigger: 'blur' }],
taxpayerId: [{ required: true, message: '请填写纳税人识别号', trigger: 'blur' }],
companyAddress: [{ required: true, message: '请填写单位地址', trigger: 'blur' }],
companyPhone: [{ required: true, message: '请填写单位电话', trigger: 'blur' }],
bankName: [{ required: true, message: '请填写开户银行', trigger: 'blur' }],
bankAccount: [{ required: true, message: '请填写银行账号', trigger: 'blur' }],
receiptPhone: receiptPhoneRules,
receiptEmail: receiptEmailRules
};
}
const rules = {
receiptPhone: receiptPhoneRules,
receiptEmail: receiptEmailRules
};
if (this.type === 1) {
rules.personalName = [{ required: true, message: '请填写个人名称', trigger: 'blur' }];
} else {
rules.companyName = [{ required: true, message: '请填写单位名称', trigger: 'blur' }];
rules.taxpayerId = [{ required: true, message: '请填写纳税人识别号', trigger: 'blur' }];
}
return rules;
}
},
watch: { watch: {
// 回显的发票信息
invoiceData: { invoiceData: {
handler (val) { handler (val) {
this.invoiceForm = { ...val }; if (!val) return;
this.invoiceForm = { ...this.invoiceForm, ...val };
if (!this.invoiceForm.receiptPhone) this.invoiceForm.receiptPhone = '';
if (!this.invoiceForm.receiptEmail) this.invoiceForm.receiptEmail = '';
if (!this.invoiceForm.receiptContent) this.invoiceForm.receiptContent = '商品明细';
if (!this.invoiceForm.companyAddress) this.invoiceForm.companyAddress = '';
if (!this.invoiceForm.companyPhone) this.invoiceForm.companyPhone = '';
if (!this.invoiceForm.bankName) this.invoiceForm.bankName = '';
if (!this.invoiceForm.bankAccount) this.invoiceForm.bankAccount = '';
if (!this.invoiceForm.personalName) this.invoiceForm.personalName = '';
if (!this.invoiceForm.companyName) this.invoiceForm.companyName = '';
if (val.taxpayerId) { const rt = val.receiptType;
if (rt === 2 || rt === '2' || val.invoiceKind === 'VAT_SPECIAL') {
this.invoice = 2;
this.type = 2; this.type = 2;
} else { } else {
this.type = 1; this.invoice = 1;
if (val.taxpayerId) this.type = 2;
else this.type = 1;
}
// 兼容后端 receiptTitle 存「个人/单位」,实际名称在 personalName/companyName旧数据名称可能只在 receiptTitle
const titleAsName = (t) => (t && t !== '个人' && t !== '单位' ? t : '');
if (this.invoice === 2) {
this.invoiceForm.companyName = val.companyName || titleAsName(val.receiptTitle) || '';
} else if (this.type === 2) {
this.invoiceForm.companyName = val.companyName || titleAsName(val.receiptTitle) || '';
this.invoiceForm.personalName = val.personalName || '';
} else {
this.invoiceForm.personalName = val.personalName || titleAsName(val.receiptTitle) || '';
this.invoiceForm.companyName = val.companyName || '';
}
if (this.invoice === 1) {
this.invoiceForm.companyAddress = '';
this.invoiceForm.companyPhone = '';
this.invoiceForm.bankName = '';
this.invoiceForm.bankAccount = '';
}
if (this.invoice === 2) {
this.invoiceForm.personalName = '';
} }
}, },
deep: true, deep: true,
immeadite: true immediate: true
} }
}, },
methods: { methods: {
/** onHeaderTypeChange (val) {
* 选择发票抬头 if (this.invoice === 1 && val === 1 && this.invoiceForm.personalName === '个人') {
*/ this.invoiceForm.personalName = '';
changeInvoice (val) { }
this.$nextTick(() => { this.$nextTick(() => {
this.type = val; if (this.$refs.form) this.$refs.form.clearValidate();
});
},
onInvoiceKindChange (val) {
this.type = val === 2 ? 2 : 1;
this.invoiceForm = {
receiptTitle: '',
personalName: '',
companyName: '',
taxpayerId: '',
receiptContent: '商品明细',
receiptPhone: '',
receiptEmail: '',
companyAddress: '',
companyPhone: '',
bankName: '',
bankAccount: ''
};
this.$nextTick(() => {
if (this.$refs.form) this.$refs.form.clearValidate();
}); });
}, },
/** async save () {
* 保存判断 if (this.invoice === 2) {
*/ this.invoiceForm.receiptContent = '商品明细';
save () {
let flage = true;
// 保存分为两种类型,个人以及企业
const { receiptTitle } = JSON.parse(
JSON.stringify(this.invoiceForm)
);
// 判断是否填写发票抬头
if (!receiptTitle) {
this.$Message.error('请填写发票抬头!');
flage = false;
return false;
} }
if (this.type === 2) { return await new Promise(resolve => {
this.$refs.form.validate((valid) => { this.$refs.form.validate(valid => resolve(valid));
if (!valid) {
flage = false;
}
}); });
} else {
delete this.invoiceForm.taxpayerId;
}
return flage;
}, },
// 保存发票信息
async submit () { async submit () {
if (this.save()) { if (!await this.save()) return;
this.loading = true; this.loading = true;
let submit = { const raw = JSON.parse(JSON.stringify(this.invoiceForm));
if (this.invoice === 1) {
delete raw.companyAddress;
delete raw.companyPhone;
delete raw.bankName;
delete raw.bankAccount;
delete raw.invoiceAddress;
if (this.type === 1) {
delete raw.taxpayerId;
}
} else {
delete raw.personalName;
}
const isCompanyTitle = this.invoice === 2 || this.type === 2;
const titleName = isCompanyTitle
? (raw.companyName != null ? String(raw.companyName).trim() : '')
: (raw.personalName != null ? String(raw.personalName).trim() : '');
const submit = {
way: this.$route.query.way, way: this.$route.query.way,
...this.invoiceForm ...raw,
receiptType: this.invoice,
receiptTitle: isCompanyTitle ? '单位' : '个人',
personalName: isCompanyTitle ? '' : titleName,
companyName: isCompanyTitle ? titleName : ''
}; };
let receipt = await receiptSelect(submit); let receipt = await receiptSelect(submit);
if (receipt.success) { if (receipt.success) {
this.$emit('change', true); this.$emit('change', true);
} }
this.loading = false; this.loading = false;
} }
} }
}
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/** 普票 */
.inv-type { .inv-type {
text-align: center; text-align: center;
} }
@@ -158,7 +305,12 @@ export default {
} }
.nav-content { .nav-content {
width: 500px; width: 520px;
margin: 10px auto; margin: 10px auto;
} }
.inv-title-fixed {
line-height: 32px;
color: #515a6e;
}
</style> </style>

View File

@@ -95,12 +95,43 @@
</div> </div>
<div class="order-card" v-if="order.order.payStatus === 'PAID'"> <div class="order-card" v-if="order.order.payStatus === 'PAID'">
<h3>发票信息</h3> <h3>发票信息</h3>
<template v-if="order.order.needReceipt && order.receipt"> <!-- 以 li_receipt 为准needReceipt 可能为 null/false 导致有 receipt 仍显示「未开发票」 -->
<p>发票抬头:{{ order.receipt.receiptTitle }}</p> <template v-if="hasValidReceipt()">
<p>发票内容{{ order.receipt.receiptContent }}</p> <p>发票类型{{ formatReceiptType(order.receipt) }}</p>
<p>发票抬头:{{ formatReceiptHeaderType(order.receipt) }}</p>
<p v-if="isPersonalReceipt(order.receipt) && order.receipt.personalName">
个人名称:{{ order.receipt.personalName }}
</p>
<p v-if="order.receipt.companyName">
单位名称:{{ order.receipt.companyName }}
</p>
<p v-if="order.receipt.taxpayerId"> <p v-if="order.receipt.taxpayerId">
纳税人识别号:{{ order.receipt.taxpayerId }} 纳税人识别号:{{ order.receipt.taxpayerId }}
</p> </p>
<p v-if="order.receipt.companyAddress">
单位地址:{{ order.receipt.companyAddress }}
</p>
<p v-if="order.receipt.companyPhone">
单位电话:{{ order.receipt.companyPhone }}
</p>
<p v-if="order.receipt.bankName">
开户银行:{{ order.receipt.bankName }}
</p>
<p v-if="order.receipt.bankAccount">
银行账号:{{ order.receipt.bankAccount }}
</p>
<p>发票内容:{{ order.receipt.receiptContent }}</p>
<p v-if="order.receipt.receiptPhone">
收票人手机:{{ order.receipt.receiptPhone }}
</p>
<p v-if="order.receipt.receiptEmail">
收票人邮箱:{{ order.receipt.receiptEmail }}
</p>
<div v-if="Number(order.receipt.receiptStatus) === 1" class="receipt-action">
<Button size="small" type="primary" ghost @click="viewReceiptInvoice(order.receipt)">
查看发票
</Button>
</div>
</template> </template>
<div v-else style="color: #999; margin-left: 5px">未开发票</div> <div v-else style="color: #999; margin-left: 5px">未开发票</div>
</div> </div>
@@ -307,7 +338,7 @@ import {
cancelOrder, cancelOrder,
getPackage getPackage
} from "@/api/order.js"; } from "@/api/order.js";
import { afterSaleReason } from "@/api/member"; import { afterSaleReason, receiptDetail } from "@/api/member";
export default { export default {
name: "order-detail", name: "order-detail",
data() { data() {
@@ -328,6 +359,51 @@ export default {
}; };
}, },
methods: { methods: {
isVatSpecialReceipt (receipt) {
if (!receipt) return false;
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "2" || rt === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL") {
return true;
}
return false;
},
isPersonalReceipt (receipt) {
return !this.isVatSpecialReceipt(receipt);
},
formatReceiptType (receipt) {
if (!receipt) return "";
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (rt === "电子普通发票" || rt === "增值税专用发票") return rt;
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
},
formatReceiptHeaderType (receipt) {
if (!receipt) return "";
if (this.isVatSpecialReceipt(receipt)) return "单位";
if (receipt.taxpayerId) return "单位";
return "个人";
},
hasValidReceipt () {
const receipt = this.order && this.order.receipt;
if (!receipt) return false;
const content = receipt.receiptContent ? String(receipt.receiptContent).trim() : '';
const title = receipt.receiptTitle ? String(receipt.receiptTitle).trim() : '';
const taxpayerId = receipt.taxpayerId ? String(receipt.taxpayerId).trim() : '';
// 后端有时会返回占位 receipt如 receiptContent=不开发票),这里过滤掉
if (content === '不开发票') return false;
return Boolean(content || title || taxpayerId);
},
getInvoiceAddress (receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
},
viewReceiptInvoice (receipt) {
const invoiceAddress = this.getInvoiceAddress(receipt);
if (!invoiceAddress) {
this.$Message.warning("暂无发票附件");
return;
}
window.open(invoiceAddress, "_blank");
},
// 退款状态枚举 // 退款状态枚举
refundPriceList(status) { refundPriceList(status) {
switch (status) { switch (status) {
@@ -359,11 +435,21 @@ export default {
}); });
window.open(routeUrl.href, "_blank"); window.open(routeUrl.href, "_blank");
}, },
getDetail() { async getDetail() {
// 获取订单详情 // 获取订单详情
orderDetail(this.$route.query.sn).then((res) => { orderDetail(this.$route.query.sn).then(async (res) => {
if (res.success) { if (res.success) {
this.order = res.result; this.order = res.result;
const receiptId =
(res.result.order && res.result.order.receiptId) ||
res.result.receiptId ||
(res.result.receipt && res.result.receipt.id);
if (receiptId) {
const receiptRes = await receiptDetail(receiptId);
if (receiptRes && receiptRes.success && receiptRes.result) {
this.$set(this.order, "receipt", receiptRes.result);
}
}
this.progressList = res.result.orderLogs; this.progressList = res.result.orderLogs;
if (this.order.order.deliveryMethod === 'LOGISTICS') { if (this.order.order.deliveryMethod === 'LOGISTICS') {
this.getOrderPackage(this.order.order.sn); this.getOrderPackage(this.order.order.sn);
@@ -625,6 +711,10 @@ table {
overflow-x: auto; overflow-x: auto;
} }
.receipt-action {
margin-top: 12px;
}
.express-log { .express-log {
/*margin: 5px -10px 5px 5px;*/ /*margin: 5px -10px 5px 5px;*/
padding: 10px; padding: 10px;

View File

@@ -157,8 +157,11 @@
</span></span> </span></span>
</div> </div>
<div class="inovice-content"> <div class="inovice-content">
<span>{{ invoiceData.receiptTitle }}</span> <template v-if="hasInvoiceInfo">
<span>{{ invoiceDisplayTitle }}</span>
<span>{{ invoiceData.receiptContent }}</span> <span>{{ invoiceData.receiptContent }}</span>
</template>
<span v-else>不开发票</span>
<span @click="editInvoice">编辑</span> <span @click="editInvoice">编辑</span>
</div> </div>
</div> </div>
@@ -258,6 +261,28 @@ import { canUseCouponList } from "@/api/member.js";
export default { export default {
name: "Pay", name: "Pay",
components: { invoiceModal, addressManage }, components: { invoiceModal, addressManage },
computed: {
hasInvoiceInfo() {
const title =
this.invoiceData.personalName ||
this.invoiceData.companyName ||
this.invoiceData.receiptTitle;
const content = this.invoiceData.receiptContent;
if (content && String(content).trim() === "不开发票") return false;
return Boolean(
(title && String(title).trim()) ||
(content && String(content).trim())
);
},
invoiceDisplayTitle() {
return (
this.invoiceData.personalName ||
this.invoiceData.companyName ||
this.invoiceData.receiptTitle ||
""
);
},
},
data() { data() {
return { return {
selectedStoreAddress: 'm', selectedStoreAddress: 'm',
@@ -270,8 +295,7 @@ export default {
storeMoreAddr: false, storeMoreAddr: false,
invoiceData: { invoiceData: {
// 发票数据 // 发票数据
receiptTitle: "个人", //receiptContent: "不开发票",
receiptContent: "不开发票",
}, },
searchForm: { searchForm: {
pageNumber: 1, pageNumber: 1,

View File

@@ -1,5 +1,6 @@
<style lang="scss"> <style lang="scss">
@import "./ossManage.scss"; @import "./ossManage.scss";
.group-row { .group-row {
padding-top: 10px; padding-top: 10px;
border-top: 1px solid #ededed; border-top: 1px solid #ededed;
@@ -7,32 +8,57 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
> * { > * {
margin-right: 10px; margin-right: 10px;
} }
} }
.article-category { .article-category {
flex: 2; flex: 2;
min-width: 200px; min-width: 200px;
} }
.table { .table {
flex: 11; flex: 11;
} }
.ivu-card { .ivu-card {
width: 100%; width: 100%;
} }
.modal-footer { .modal-footer {
text-align: center; text-align: center;
> * { > * {
margin: 0 10px; margin: 0 10px;
} }
} }
.tab-empty {
padding: 48px 0;
color: #999;
text-align: center;
}
.tabs-card {
margin-bottom: 16px;
}
</style> </style>
<template> <template>
<div class="search"> <div class="search">
<Card class="tabs-card">
<Tabs v-model="activeRoleTab" @on-click="handleRoleTabChange">
<TabPane label="管理员" name="MANAGER"></TabPane>
<TabPane label="商家" name="STORE"></TabPane>
<TabPane label="用户" name="MEMBER"></TabPane>
<TabPane label="客服" name="CUSTOMER"></TabPane>
</Tabs>
</Card>
<Row> <Row>
<Card> <Card>
<div>
<div class="operation"> <div class="operation">
<Row @keydown.enter.native="handleSearch"> <Row @keydown.enter.native="handleSearch">
<Form <Form
@@ -53,15 +79,6 @@
@on-change="selectDateRange" @on-change="selectDateRange"
></DatePicker> ></DatePicker>
</Form-item> </Form-item>
<Form-item label="上传人" prop="ownerName">
<Input
type="text"
v-model="searchForm.ownerName"
placeholder="图片拥有者名称"
clearable
style="width: 240px"
/>
</Form-item>
<Button <Button
class="search-btn" class="search-btn"
icon="ios-search" icon="ios-search"
@@ -71,30 +88,31 @@
</Button> </Button>
</Form> </Form>
</Row> </Row>
</div></Card><Card><div> </div>
<Row class="oss-manage-box"> <Row class="oss-manage-box">
<Col :span="isComponent?5:4"> <Col v-if="canShowGroups" :span="isComponent?5:4">
<div class="file-list"> <div class="file-list">
<div class="article-category mr_10"> <div class="article-category mr_10">
<Tree :data="treeData" :render="renderContent" @on-select-change.self="handleCateChange" class="demo-tree-render"></Tree> <Tree :data="treeData" :render="renderContent" @on-select-change.self="handleCateChange"
class="demo-tree-render"></Tree>
<div class="group-row flex" v-if="!isComponent"> <div class="group-row flex" v-if="!isComponent">
<Button @click="handleClickAddGroup">添加分组</Button> <Button @click="handleClickAddGroup">添加分组</Button>
</div> </div>
</div> </div>
</div> </div>
</Col> </Col>
<Col :span="isComponent?19:20"> <Col :span="canShowGroups ? (isComponent ? 19 : 20) : 24">
<div class="pic-list"> <div class="pic-list">
<div> <div>
<div class="oss-operation padding-row" style="display: flex;flex-direction: row-reverse;"> <div class="oss-operation padding-row" style="display: flex;flex-direction: row-reverse;">
<div> <div>
<Upload <Upload
v-if="canUploadImages"
ref="up" ref="up"
:action="commonUrl + '/common/common/upload/file'" :action="commonUrl + '/common/common/upload/file'"
:data="{ :data="uploadData"
directoryPath: searchForm.fileDirectoryId,
}"
:headers="accessToken" :headers="accessToken"
:before-upload="beforeUpload"
:max-size="20480" :max-size="20480"
:on-error="handleError" :on-error="handleError"
:on-exceeded-size="handleMaxSize" :on-exceeded-size="handleMaxSize"
@@ -105,18 +123,7 @@
> >
<Button>上传图片</Button> <Button>上传图片</Button>
</Upload> </Upload>
<Dropdown @on-click="handleDropdown" v-if="!isComponent"> <Button v-if="!isComponent && showType == 'list'" @click="removeAll">批量删除</Button>
<Button>
更多操作
<Icon type="md-arrow-dropdown"/>
</Button>
<DropdownMenu slot="list">
<DropdownItem name="refresh">刷新</DropdownItem>
<DropdownItem v-show="showType == 'list'" name="removeAll"
>批量删除
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div> </div>
</div> </div>
</div> </div>
@@ -126,20 +133,33 @@
<div v-for="(item, index) in data" :key="index" class="img-item"> <div v-for="(item, index) in data" :key="index" class="img-item">
<Checkbox :label="item.id+','+item.url" class="check-box"> <Checkbox :label="item.id+','+item.url" class="check-box">
<template> <template>
<div class="card" @mouseenter.active="onMouseOver(item, index)" @mouseleave.active="onMouseOut(item, index)"> <div class="card" @mouseenter.active="onMouseOver(item, index)"
@mouseleave.active="onMouseOut(item, index)">
<!--<div class="custom-checkbox-card-mask"><div class="custom-checkbox-card-mask-dot"/></div>--> <!--<div class="custom-checkbox-card-mask"><div class="custom-checkbox-card-mask-dot"/></div>-->
<img :src="item.url" alt="" /> <img :src="item.url" alt=""/>
<div v-if="item.isShowPreview" class="preview"> <div v-if="item.isShowPreview" class="preview">
<div @click.prevent="download(item)"><Tooltip content="下载"><Icon type="md-cloud-download" size="18" /></Tooltip></div> <div @click.prevent="download(item)">
<div @click.prevent="remove(item)"><Tooltip content="删除"><Icon type="md-trash" size="18" /></Tooltip></div> <Tooltip content="下载">
<div @click.prevent="showPic(item)"><Tooltip content="预览"><Icon type="ios-eye" size="22" /></Tooltip></div> <Icon type="md-cloud-download" size="18"/>
</Tooltip>
</div>
<div @click.prevent="remove(item)">
<Tooltip content="删除">
<Icon type="md-trash" size="18"/>
</Tooltip>
</div>
<div @click.prevent="showPic(item)">
<Tooltip content="预览">
<Icon type="ios-eye" size="22"/>
</Tooltip>
</div>
</div> </div>
</div> </div>
</template> </template>
</Checkbox> </Checkbox>
<div> <div>
<Tooltip :content="item.name" placement="bottom"> <Tooltip :content="item.name" placement="bottom">
<div class="text">{{item.name}}</div> <div class="text">{{ item.name }}</div>
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
@@ -147,15 +167,14 @@
</CheckboxGroup> </CheckboxGroup>
</div> </div>
<div class="page-box"> <div class="page-box">
<Page :total="total" :page-size="searchForm.pageSize" show-elevator @on-change="pageChange" size="small" /> <Page :total="total" :page-size="searchForm.pageSize" show-elevator @on-change="pageChange"
size="small"/>
</div> </div>
</div> </div>
</Col> </Col>
</Row> </Row>
<!--<div class="oss-operation padding-row">--> <!--<div class="oss-operation padding-row">-->
<!--<div>--> <!--<div>-->
<!--<Upload--> <!--<Upload-->
@@ -416,7 +435,7 @@ import {
updateFileDirectory, updateFileDirectory,
} from "@/api/index"; } from "@/api/index";
import DPlayer from "dplayer"; import DPlayer from "dplayer";
import { commonUrl } from "@/libs/axios"; import {commonUrl} from "@/libs/axios";
const config = require("@/config/index"); const config = require("@/config/index");
@@ -478,12 +497,13 @@ export default {
picTitle: "", // 图片title picTitle: "", // 图片title
videoTitle: "", // 视频title videoTitle: "", // 视频title
modalTitle: "", // 添加或编辑标题 modalTitle: "", // 添加或编辑标题
activeRoleTab: "MANAGER",
searchForm: { searchForm: {
// 搜索框对应data对象 // 搜索框对应data对象
name: "", name: "",
fileKey: "", fileKey: "",
fileType: "", fileType: "",
ownerName: "", userEnums: "MANAGER",
pageNumber: 1, // 当前页数 pageNumber: 1, // 当前页数
pageSize: 20, // 页面大小 pageSize: 20, // 页面大小
sort: "createTime", // 默认排序字段 sort: "createTime", // 默认排序字段
@@ -618,9 +638,11 @@ export default {
if (params.row.userEnums == "MANAGER") { if (params.row.userEnums == "MANAGER") {
m = "[管理员]"; m = "[管理员]";
} else if (params.row.userEnums == "STORE") { } else if (params.row.userEnums == "STORE") {
m = "[店铺]"; m = "[商家]";
} else if (params.row.userEnums == "CUSTOMER") {
m = "[客服]";
} else { } else {
m = "[会员]"; m = "[用户]";
} }
m += params.row.createBy; m += params.row.createBy;
return h("span", m); return h("span", m);
@@ -800,9 +822,11 @@ export default {
if (params.row.userEnums == "MANAGER") { if (params.row.userEnums == "MANAGER") {
m = "[管理员]"; m = "[管理员]";
} else if (params.row.userEnums == "STORE") { } else if (params.row.userEnums == "STORE") {
m = "[店铺]"; m = "[商家]";
} else if (params.row.userEnums == "CUSTOMER") {
m = "[客服]";
} else { } else {
m = "[会员]"; m = "[用户]";
} }
m += params.row.createBy; m += params.row.createBy;
return h("span", m); return h("span", m);
@@ -856,17 +880,33 @@ export default {
{ {
title: 'parent 1', title: 'parent 1',
expand: true, expand: true,
render: (h, { root, node, data }) => { render: (h, {root, node, data}) => {
return h('span', {style: {display: 'inline-block', width: '100%'}}, [ return h('span', {style: {display: 'inline-block', width: '100%'}}, [
h('span', [h("Icon", {type: 'ios-folder-outline', style: {marginRight: '8px'}}), h('span', data.title)]), h('span', [h("Icon", {type: 'ios-folder-outline', style: {marginRight: '8px'}}), h('span', data.title)]),
h('span', {style: {display: 'inline-block', float: 'right', marginRight: '32px'}}, h('span', {style: {display: 'inline-block', float: 'right', marginRight: '32px'}},
[h("Button", {...this.buttonProps, icon: 'ios-add', type: 'primary', style: {width: '64px'}, onClick: () => { this.append(data) }}) [h("Button", {
...this.buttonProps,
icon: 'ios-add',
type: 'primary',
style: {width: '64px'},
onClick: () => {
this.append(data)
}
})
]) ])
]); ]);
}, },
children: [ children: [
{title: 'child 1-1', expand: true, children: [{title: 'leaf 1-1-1', expand: true}, {title: 'leaf 1-1-2', expand: true}]}, {
{title: 'child 1-2', expand: true, children: [{title: 'leaf 1-2-1', expand: true}, {title: 'leaf 1-2-1', expand: true}]} title: 'child 1-1',
expand: true,
children: [{title: 'leaf 1-1-1', expand: true}, {title: 'leaf 1-1-2', expand: true}]
},
{
title: 'child 1-2',
expand: true,
children: [{title: 'leaf 1-2-1', expand: true}, {title: 'leaf 1-2-1', expand: true}]
}
] ]
} }
], ],
@@ -882,7 +922,7 @@ export default {
}, },
selectedOss(val) { selectedOss(val) {
if (val && val.length) { if (val && val.length) {
this.$emit("callback", {url: val[val.length-1].split(',')[1]}); this.$emit("callback", {url: val[val.length - 1].split(',')[1]});
} }
}, },
// 初始化监听 是否清空所选图片 // 初始化监听 是否清空所选图片
@@ -897,8 +937,30 @@ export default {
} }
}, },
}, },
computed: {
canShowGroups() {
return this.activeRoleTab === "MANAGER";
},
canUploadImages() {
return this.activeRoleTab === "MANAGER";
},
uploadData() {
return {
directoryPath: this.searchForm.fileDirectoryId,
userEnums: this.activeRoleTab,
directoryType: this.activeRoleTab,
};
},
},
methods: { methods: {
beforeUpload() {
if (!this.canUploadImages) {
this.$Message.warning("仅管理员模块支持上传图片");
return false;
}
return true;
},
onMouseOver(item, index) { onMouseOver(item, index) {
this.$set(this.data[index], 'isShowPreview', true); this.$set(this.data[index], 'isShowPreview', true);
this.$forceUpdate(); this.$forceUpdate();
@@ -906,10 +968,53 @@ export default {
onMouseOut(item, index) { onMouseOut(item, index) {
this.$set(this.data[index], 'isShowPreview', false); this.$set(this.data[index], 'isShowPreview', false);
}, },
handleRoleTabChange(name) {
this.activeRoleTab = name;
this.searchForm.pageNumber = 1;
this.searchForm.userEnums = name;
delete this.searchForm.fileDirectoryId;
this.selectedGroupData = "";
this.defaultValue = [];
this.groupFormValidate.id = [];
this.groupFormValidate.level = 0;
this.$set(this, "selectedOss", []);
this.updateTreeDataByRole();
this.getDataList();
},
updateTreeDataByRole() {
const currentTree = this.filterTreeByRole(this.treeDataDefault, this.activeRoleTab);
this.treeData = [
{
title: "全部图片",
label: "全部分类",
value: "0",
type: this.activeRoleTab,
level: 0,
children: currentTree,
id: "0",
categoryId: 0,
},
];
},
filterTreeByRole(tree = [], role) {
if (!tree || !tree.length) return [];
return tree.reduce((arr, item) => {
const children = this.filterTreeByRole(item.children || [], role);
if (item.type === role || children.length) {
arr.push({
...item,
children,
});
}
return arr;
}, []);
},
// 复选框值改变时触发 // 复选框值改变时触发
selectOssChange(e) { selectOssChange(e) {
if (e) { if (e) {
this.selectList = e.map(item => {return { id: item.split(',')[0]}}); this.selectList = e.map(item => {
return {id: item.split(',')[0]}
});
this.selectCount = e.length; this.selectCount = e.length;
// let size = 0; // let size = 0;
// e.forEach((item) => {size += item.fileSize * 1.0;}); // e.forEach((item) => {size += item.fileSize * 1.0;});
@@ -924,7 +1029,7 @@ export default {
this.getDataList(); this.getDataList();
}, },
// 自定义tree节点显示内容和交互 // 自定义tree节点显示内容和交互
renderContent (h, { root, node, data }) { renderContent(h, {root, node, data}) {
if (data.value === '0') { if (data.value === '0') {
return h('span', {style: {display: 'inline-block', width: '100%'}}, return h('span', {style: {display: 'inline-block', width: '100%'}},
[ [
@@ -940,11 +1045,31 @@ export default {
[ [
h("Dropdown", {style: {marginLeft: "4px"}}, h("Dropdown", {style: {marginLeft: "4px"}},
[ [
h("Icon", {props: {type: 'ios-more', size: "20",}, style: {display: 'inline-block'}, on:{click: () => {}}}), h("Icon", {
h("DropdownMenu", {slot: "list" props: {type: 'ios-more', size: "20",},
style: {display: 'inline-block'},
on: {
click: () => {
}
}
}),
h("DropdownMenu", {
slot: "list"
}, [ }, [
h("DropdownItem", { nativeOn:{click: () => {this.handleContextMenuEdit(root, node, data)}} }, "编辑"), h("DropdownItem", {
h("DropdownItem", { nativeOn:{click: () => {this.handleContextMenuDelete()}} }, "删除"), nativeOn: {
click: () => {
this.handleContextMenuEdit(root, node, data)
}
}
}, "编辑"),
h("DropdownItem", {
nativeOn: {
click: () => {
this.handleContextMenuDelete()
}
}
}, "删除"),
]) ])
]), ]),
]) ])
@@ -955,8 +1080,6 @@ export default {
}, },
handleContextMenu(val) { handleContextMenu(val) {
console.log('handleContextMenu', val); console.log('handleContextMenu', val);
this.selectedGroupData = val; this.selectedGroupData = val;
@@ -988,10 +1111,10 @@ export default {
}, },
treeDataChange(value, selectedData) { treeDataChange(value, selectedData) {
if (value && value.length) { if (value && value.length) {
if (value[value.length -1] == '0') { if (value[value.length - 1] == '0') {
this.groupFormValidate.level = 0; this.groupFormValidate.level = 0;
} else { } else {
this.groupFormValidate.level = Number(selectedData[selectedData.length -1].level) + 1; this.groupFormValidate.level = Number(selectedData[selectedData.length - 1].level) + 1;
} }
} }
}, },
@@ -1000,7 +1123,8 @@ export default {
this.$refs["formValidate"].validate(async (valid) => { this.$refs["formValidate"].validate(async (valid) => {
if (valid) { if (valid) {
let res let res
const params = { ...this.groupFormValidate }; const params = {...this.groupFormValidate};
params.directoryType = (this.selectedGroupData && this.selectedGroupData.type) || this.activeRoleTab;
if (this.insertOrUpdate === "insert") { if (this.insertOrUpdate === "insert") {
params.parentId = params.id[params.id.length - 1]; params.parentId = params.id[params.id.length - 1];
delete params.id; delete params.id;
@@ -1054,17 +1178,8 @@ export default {
getFileDirectory(parent_id).then((res) => { getFileDirectory(parent_id).then((res) => {
this.loading = false; this.loading = false;
if (res.success) { if (res.success) {
this.treeData = this.getTree(res.result);
this.treeDataDefault = this.getTree(res.result); this.treeDataDefault = this.getTree(res.result);
this.treeData.unshift({ this.updateTreeDataByRole();
title: "全部图片",
label: "全部分类",
value: "0",
level: 0,
children: [],
id: "0",
categoryId: 0,
});
} }
}); });
}, },
@@ -1103,7 +1218,7 @@ export default {
} else { } else {
this.groupFormValidate.level = Number(level) + 1; this.groupFormValidate.level = Number(level) + 1;
} }
this.searchForm.userEnums = type; this.searchForm.userEnums = type || this.activeRoleTab;
this.getDataList(); this.getDataList();
this.$set(this, 'selectedOss', []); this.$set(this, 'selectedOss', []);
} }
@@ -1114,19 +1229,12 @@ export default {
selectedParams(val) { selectedParams(val) {
this.$emit("callback", val); this.$emit("callback", val);
}, },
// 更多操作
handleDropdown(name) {
if (name === "refresh") {
this.getDataList('refresh');
} else if (name === "removeAll") {
this.removeAll();
}
},
// 初始化数据 // 初始化数据
init() { init() {
this.accessToken = { this.accessToken = {
accessToken: this.getStore("accessToken"), accessToken: this.getStore("accessToken"),
}; };
this.searchForm.userEnums = this.activeRoleTab;
this.getDataList(); this.getDataList();
this.getAllList(); this.getAllList();
}, },
@@ -1207,9 +1315,6 @@ export default {
this.data = res.result.records; this.data = res.result.records;
this.total = res.result.total; this.total = res.result.total;
if (type === 'refresh') {
this.$Message.success('刷新成功!');
}
}); });
}, },
// 搜索 // 搜索
@@ -1244,6 +1349,10 @@ export default {
}, },
// 上传成功回调 // 上传成功回调
handleSuccess(res, file) { handleSuccess(res, file) {
if (!this.canUploadImages) {
this.$Message.warning("仅管理员模块支持上传图片");
return;
}
if (res.success) { if (res.success) {
this.$Message.success("上传文件 " + file.name + " 成功"); this.$Message.success("上传文件 " + file.name + " 成功");
this.getDataList(); this.getDataList();
@@ -1281,7 +1390,9 @@ export default {
loading: true, loading: true,
onOk: () => { onOk: () => {
let ids = ""; let ids = "";
this.selectList.forEach(function (e) {ids += e.id + ",";}); this.selectList.forEach(function (e) {
ids += e.id + ",";
});
ids = ids.substring(0, ids.length - 1); ids = ids.substring(0, ids.length - 1);
deleteFile(ids).then((res) => { deleteFile(ids).then((res) => {
this.$Modal.remove(); this.$Modal.remove();
@@ -1374,7 +1485,7 @@ export default {
// 是组件的话,初始化不调用接口 // 是组件的话,初始化不调用接口
this.init(); this.init();
} else { } else {
this.searchForm.pageSize =18; // 页面大小 this.searchForm.pageSize = 18; // 页面大小
} }
}, },
}; };

View File

@@ -147,9 +147,14 @@ export const getReceiptPage = params => {
return getRequest(`/trade/receipt`, params); return getRequest(`/trade/receipt`, params);
}; };
//获取发票列表 //获取发票详情
export const invoicing = id => { export const getReceiptDetail = id => {
return postRequest(`/trade/receipt/${id}/invoicing`); return getRequest(`/trade/receipt/get/${id}`);
};
//开发票
export const invoicing = (id, params) => {
return postRequest(`/trade/receipt/${id}/invoicing`, params);
}; };
//查询包裹列表 //查询包裹列表

View File

@@ -21,7 +21,11 @@
<Button @click="handleReset" class="search-btn">重置</Button> <Button @click="handleReset" class="search-btn">重置</Button>
</Form> </Form>
</Card> </Card>
<Card> <Card>
<div class="receipt-tip">
订单状态为已发货/已完成可开票
</div>
<Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table"> <Table class="mt_10" :loading="loading" border :columns="columns" :data="data" ref="table">
<!-- 订单详情格式化 --> <!-- 订单详情格式化 -->
<template slot="orderSlot" slot-scope="scope"> <template slot="orderSlot" slot-scope="scope">
@@ -33,17 +37,135 @@
show-total show-elevator show-sizer></Page> show-total show-elevator show-sizer></Page>
</Row> </Row>
</Card> </Card>
<Modal
v-model="receiptModalVisible"
title="发票信息"
:mask-closable="false"
width="680"
>
<div v-if="receiptDetailLoading" class="receipt-modal-loading">
<Spin size="large" />
</div>
<div v-else class="receipt-modal-content">
<div v-if="hasValue(currentReceipt.orderSn)" class="receipt-item">
<span class="receipt-label">订单号</span>
<span class="receipt-value">{{ currentReceipt.orderSn }}</span>
</div>
<div v-if="hasValue(currentReceipt.memberName)" class="receipt-item">
<span class="receipt-label">会员名称</span>
<span class="receipt-value">{{ currentReceipt.memberName }}</span>
</div>
<div v-if="hasValue(currentReceipt.receiptType) || hasValue(currentReceipt.invoiceKind)" class="receipt-item">
<span class="receipt-label">发票类型</span>
<span class="receipt-value">{{ formatReceiptType(currentReceipt) }}</span>
</div>
<div v-if="hasValue(currentReceipt.receiptTitle)" class="receipt-item">
<span class="receipt-label">发票抬头</span>
<span class="receipt-value">{{ currentReceipt.receiptTitle }}</span>
</div>
<div v-if="hasValue(currentReceipt.companyName)" class="receipt-item">
<span class="receipt-label">单位名称</span>
<span class="receipt-value">{{ currentReceipt.companyName }}</span>
</div>
<div v-if="hasValue(currentReceipt.personalName)" class="receipt-item">
<span class="receipt-label">个人名称</span>
<span class="receipt-value">{{ currentReceipt.personalName }}</span>
</div>
<div v-if="hasValue(currentReceipt.taxpayerId)" class="receipt-item">
<span class="receipt-label">纳税人识别号</span>
<span class="receipt-value">{{ currentReceipt.taxpayerId }}</span>
</div>
<div v-if="hasValue(currentReceipt.companyAddress)" class="receipt-item">
<span class="receipt-label">单位地址</span>
<span class="receipt-value">{{ currentReceipt.companyAddress }}</span>
</div>
<div v-if="hasValue(currentReceipt.companyPhone)" class="receipt-item">
<span class="receipt-label">单位电话</span>
<span class="receipt-value">{{ currentReceipt.companyPhone }}</span>
</div>
<div v-if="hasValue(currentReceipt.bankName)" class="receipt-item">
<span class="receipt-label">开户银行</span>
<span class="receipt-value">{{ currentReceipt.bankName }}</span>
</div>
<div v-if="hasValue(currentReceipt.bankAccount)" class="receipt-item">
<span class="receipt-label">银行账号</span>
<span class="receipt-value">{{ currentReceipt.bankAccount }}</span>
</div>
<div v-if="hasValue(currentReceipt.receiptContent)" class="receipt-item">
<span class="receipt-label">发票内容</span>
<span class="receipt-value">{{ currentReceipt.receiptContent }}</span>
</div>
<div v-if="hasPrice(currentReceipt.receiptPrice)" class="receipt-item">
<span class="receipt-label">发票金额</span>
<span class="receipt-value">{{ formatPrice(currentReceipt.receiptPrice) }}</span>
</div>
<div v-if="hasValue(currentReceipt.receiptPhone)" class="receipt-item">
<span class="receipt-label">收票人手机</span>
<span class="receipt-value">{{ currentReceipt.receiptPhone }}</span>
</div>
<div v-if="hasValue(currentReceipt.receiptEmail)" class="receipt-item">
<span class="receipt-label">收票人邮箱</span>
<span class="receipt-value">{{ currentReceipt.receiptEmail }}</span>
</div>
<div v-if="hasValue(getInvoiceAddress(currentReceipt))" class="receipt-item">
<span class="receipt-label">发票附件</span>
<span class="receipt-value">
<a @click="viewInvoiceFile(getInvoiceAddress(currentReceipt))">查看附件</a>
</span>
</div>
</div>
<div slot="footer">
<template v-if="receiptModalMode === 'invoicing'">
<Upload
:action="uploadFileUrl"
:data="receiptUploadData"
:headers="{ ...accessToken }"
:format="['jpg', 'jpeg', 'png', 'pdf']"
:max-size="10240"
:on-success="handleInvoiceUploadSuccess"
:on-error="handleInvoiceUploadError"
:on-format-error="handleInvoiceFormatError"
:on-exceeded-size="handleInvoiceMaxSize"
:show-upload-list="false"
style="display: inline-block; margin-right: 8px"
>
<Button :disabled="receiptDetailLoading">上传发票</Button>
</Upload>
<Button @click="receiptModalVisible = false">取消</Button>
<Button
type="primary"
:loading="invoiceSubmitting"
@click="submitInvoicing"
>
确认开票
</Button>
</template>
<Button v-else @click="receiptModalVisible = false">关闭</Button>
</div>
</Modal>
</div> </div>
</template> </template>
<script> <script>
import * as API_Order from "@/api/order"; import * as API_Order from "@/api/order";
import { uploadFile } from "@/libs/axios";
export default { export default {
name: "receipt", name: "receipt",
data() { data() {
return { return {
loading: true, // 表单加载状态 loading: true, // 表单加载状态
receiptModalVisible: false,
receiptDetailLoading: false,
invoiceSubmitting: false,
receiptModalMode: "detail",
uploadFileUrl: uploadFile,
accessToken: {},
receiptUploadData: {
directoryPath: "receipt"
},
currentReceipt: {},
selectedReceiptRow: null,
searchForm: { searchForm: {
// 搜索框初始化对象 // 搜索框初始化对象
pageNumber: 1, // 当前页数 pageNumber: 1, // 当前页数
@@ -107,7 +229,7 @@ export default {
width: 100, width: 100,
tooltip: true, tooltip: true,
render: (h, params) => { render: (h, params) => {
if (params.row.receiptStatus === 0) { if (Number(params.row.receiptStatus) === 0) {
return h("div", [ return h("div", [
h("tag", { props: { color: "volcano" } }, "未开票"), h("tag", { props: { color: "volcano" } }, "未开票"),
]); ]);
@@ -162,12 +284,21 @@ export default {
fixed: 'right', fixed: 'right',
width: 200, width: 200,
render: (h, params) => { render: (h, params) => {
const disabled = !(((params.row.orderStatus === "COMPLETED" || params.row.orderStatus === "DELIVERED")) && params.row.receiptStatus === 0); const disabled = !this.canInvoicing(params.row);
const detailStyle = { color: "#2d8cf0", cursor: "pointer", textDecoration: "none", marginRight: "12px" };
const style = disabled const style = disabled
? { color: "#c5c8ce", cursor: "not-allowed", textDecoration: "none" } ? { color: "#c5c8ce", cursor: "not-allowed", textDecoration: "none" }
: { color: "#2d8cf0", cursor: "pointer", textDecoration: "none" }; : { color: "#2d8cf0", cursor: "pointer", textDecoration: "none" };
const on = disabled ? {} : { click: () => { this.invoicing(params.row); } }; const on = disabled ? {} : { click: () => { this.openReceiptModal(params.row, "invoicing"); } };
return h("div", [ return h("div", [
h(
"a",
{
style: detailStyle,
on: { click: () => { this.openReceiptModal(params.row, "detail"); } },
},
"详情"
),
h( h(
"a", "a",
{ {
@@ -185,6 +316,102 @@ export default {
}; };
}, },
methods: { methods: {
canInvoicing(row) {
if (!row) return false;
const orderStatus = row.orderStatus;
const receiptStatus = Number(row.receiptStatus);
return (orderStatus === "COMPLETED" || orderStatus === "DELIVERED") && receiptStatus === 0;
},
initUploadAccessToken() {
this.accessToken = {
accessToken: this.getStore("accessToken")
};
},
hasValue(value) {
if (value === null || value === undefined) return false;
return String(value).trim() !== "";
},
hasPrice(value) {
return value !== null && value !== undefined && value !== "";
},
formatValue(value) {
if (value === null || value === undefined) return "暂无";
const text = String(value).trim();
return text ? text : "暂无";
},
formatPrice(value) {
if (value === null || value === undefined || value === "") return "暂无";
return `${value}`;
},
isVatSpecialReceipt(receipt) {
if (!receipt) return false;
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
return receiptType === "2" || receiptType === "增值税专用发票" || receipt.invoiceKind === "VAT_SPECIAL";
},
formatReceiptType(receipt) {
if (!receipt || (!receipt.receiptType && !receipt.invoiceKind)) return "电子普通发票";
const receiptType = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
if (receiptType === "电子普通发票" || receiptType === "增值税专用发票") return receiptType;
return this.isVatSpecialReceipt(receipt) ? "增值税专用发票" : "电子普通发票";
},
formatReceiptHeaderType(receipt) {
if (!receipt) return "暂无";
if (this.isVatSpecialReceipt(receipt)) return "单位";
if (receipt.companyName) return "单位";
if (receipt.personalName) return "个人";
const receiptTitle = receipt.receiptTitle != null ? String(receipt.receiptTitle).trim() : "";
if (receiptTitle === "单位" || receiptTitle === "个人") return receiptTitle;
return receipt.taxpayerId ? "单位" : "个人";
},
getReceiptTitleLabel(receipt) {
return this.formatReceiptHeaderType(receipt) === "单位" ? "单位名称" : "个人名称";
},
getReceiptTitleName(receipt) {
if (!receipt) return "";
if (receipt.companyName) return receipt.companyName;
if (receipt.personalName) return receipt.personalName;
const receiptTitle = receipt.receiptTitle != null ? String(receipt.receiptTitle).trim() : "";
if (receiptTitle === "单位" || receiptTitle === "个人") return "";
return receiptTitle;
},
getInvoiceAddress(receipt) {
if (!receipt) return "";
return receipt.invoiceAddress || receipt.invoiceFileUrl || "";
},
buildInvoicingPayload() {
const invoiceAddress = this.getInvoiceAddress(this.currentReceipt);
return invoiceAddress ? { invoiceAddress } : {};
},
handleInvoiceUploadSuccess(res) {
if (res && res.success && res.result) {
this.$set(this.currentReceipt, "invoiceAddress", res.result);
if (this.selectedReceiptRow) {
this.$set(this.selectedReceiptRow, "invoiceAddress", res.result);
}
this.$Message.success("发票上传成功");
} else {
this.$Message.error((res && res.message) || "发票上传失败");
}
},
handleInvoiceUploadError() {
this.$Message.error("发票上传失败");
},
handleInvoiceFormatError() {
this.$Notice.warning({
title: "文件格式不正确",
desc: "请上传 jpg、jpeg、png 或 pdf 格式文件"
});
},
handleInvoiceMaxSize() {
this.$Notice.warning({
title: "超过文件大小限制",
desc: "发票附件不能超过 10MB"
});
},
viewInvoiceFile(url) {
if (!url) return;
window.open(url, "_blank");
},
// 初始化数据 // 初始化数据
init() { init() {
this.getData(); this.getData();
@@ -232,25 +459,54 @@ export default {
this.total = this.data.length; this.total = this.data.length;
this.loading = false; this.loading = false;
}, },
//开发票 async openReceiptModal(row, mode = "detail") {
invoicing(params) { if (!row) return;
this.$Modal.confirm({ this.receiptModalMode = mode;
title: "确认开票", this.selectedReceiptRow = row;
content: "您确认已经开具发票 ?", this.currentReceipt = { ...row };
loading: true, this.receiptModalVisible = true;
onOk: () => { this.receiptDetailLoading = true;
API_Order.invoicing(params.id).then((res) => { try {
const res = await API_Order.getReceiptDetail(row.id);
if (res.success && res.result) {
this.currentReceipt = { ...row, ...res.result };
} else {
this.$Message.warning("发票详情获取失败,已展示列表中的发票信息");
}
} catch (e) {
this.$Message.error("发票详情获取失败");
} finally {
this.receiptDetailLoading = false;
}
},
async submitInvoicing() {
if (!this.selectedReceiptRow) return;
if (!this.canInvoicing(this.selectedReceiptRow)) {
this.$Message.warning("当前订单状态不支持开票");
return;
}
const params = this.buildInvoicingPayload();
if (!params.invoiceAddress) {
this.$Message.warning("请先上传发票");
return;
}
this.invoiceSubmitting = true;
try {
const res = await API_Order.invoicing(this.selectedReceiptRow.id, params);
if (res.success) { if (res.success) {
this.$Message.success("开票成功"); this.$Message.success("开票成功");
} this.receiptModalVisible = false;
this.$Modal.remove();
this.getData(); this.getData();
}); }
}, } catch (e) {
}); this.$Message.error("开票失败");
} finally {
this.invoiceSubmitting = false;
}
}, },
}, },
mounted() { mounted() {
this.initUploadAccessToken();
this.init(); this.init();
}, },
}; };
@@ -258,4 +514,44 @@ export default {
<style lang="scss"> <style lang="scss">
// 建议引入通用样式 可删除下面样式代码 // 建议引入通用样式 可删除下面样式代码
@import "@/styles/table-common.scss"; @import "@/styles/table-common.scss";
.receipt-modal-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 240px;
}
.receipt-modal-content {
max-height: 460px;
overflow-y: auto;
}
.receipt-item {
display: flex;
margin-bottom: 12px;
line-height: 22px;
}
.receipt-tip {
margin-bottom: 16px;
padding: 8px 12px;
color: #ff9900;
background: #fff7e6;
border: 1px solid #ffd591;
border-radius: 4px;
}
.receipt-label {
width: 110px;
color: #515a6e;
flex-shrink: 0;
text-align: right;
}
.receipt-value {
flex: 1;
color: #17233d;
word-break: break-all;
}
</style> </style>