Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
杨炀
/
ztx_wx_gzt
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
f05cc2b8
authored
2026-04-24 11:10:11 +0800
by
lttnew
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
开票
1 parent
8a216ef1
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
822 additions
and
109 deletions
common/api.js
common/login.js
myCenter/examPointApplyList.vue
myCenter/order.vue
pages/index/home.vue
pages/invoice/apply.vue
common/api.js
View file @
f05cc2b
...
...
@@ -2106,3 +2106,38 @@ export function memberAuditList(params) {
params
:
params
})
}
// 不再显示考点弹框
export
function
noDisplay
(
data
)
{
return
request
({
url
:
'/system/user/noDisplay'
,
method
:
'put'
,
params
:
data
})
}
// 修改手机号
export
function
editPhone
(
data
)
{
return
request
({
url
:
'/system/user/editPhone'
,
method
:
'post'
,
params
:
data
})
}
// 图形验证码
export
function
getCodeImg
()
{
return
request
({
url
:
'/captchaImage'
,
method
:
'get'
})
}
// 短信验证码
export
function
getSmsCode
(
data
)
{
return
request
({
url
:
'/captchaSmsWithCaptchaImageForMiniApp'
,
method
:
'post'
,
params
:
data
})
}
...
...
common/login.js
View file @
f05cc2b
...
...
@@ -111,6 +111,7 @@ function getInfo() {
uni
.
removeStorageSync
(
'webUserName'
)
userStore
.
setUser
(
user
)
app
.
globalData
.
userInfo
=
user
app
.
globalData
.
deptType
=
user
.
dept
.
deptType
app
.
globalData
.
genFlag
=
user
.
dept
.
genFlag
app
.
globalData
.
changePassFlag
=
user
.
changePassFlag
...
...
myCenter/examPointApplyList.vue
View file @
f05cc2b
...
...
@@ -2,7 +2,7 @@
<view
class=
"exam-point-list"
>
<!-- 顶部申请按钮 -->
<view
class=
"apply-btn-box"
>
<button
class=
"apply-btn"
:disabled=
"memberInfo.isPoints==0
&&formInfo.auditStatus==2
"
@
click=
"goApply"
>
申请考点
</button>
<button
class=
"apply-btn"
:disabled=
"memberInfo.isPoints==0
||formInfo.auditStatus==2||formInfo.auditStatus==1
"
@
click=
"goApply"
>
申请考点
</button>
</view>
<!-- 列表 -->
...
...
@@ -51,7 +51,7 @@
<
script
setup
>
import
{
ref
}
from
'vue'
import
{
onLoad
,
onReachBottom
}
from
'@dcloudio/uni-app'
import
{
getMyRecent
}
from
'@/common/api'
import
{
getMyRecent
Exam
}
from
'@/common/api'
const
app
=
getApp
()
const
list
=
ref
([])
const
loading
=
ref
(
false
)
...
...
@@ -68,7 +68,7 @@ function loadData() {
if
(
loading
.
value
)
return
loading
.
value
=
true
getMyRecent
().
then
(
res
=>
{
getMyRecent
Exam
().
then
(
res
=>
{
formInfo
.
value
=
res
.
data
if
(
res
.
data
&&
res
.
data
.
auditLogs
)
{
try
{
...
...
@@ -101,7 +101,7 @@ function goApply() {
function
getStatusClass
(
status
)
{
return
{
'status-1'
:
status
==
0
,
'status-2'
:
status
==
1
,
'status-2'
:
status
==
9
,
'status-3'
:
status
==
3
}
}
...
...
myCenter/order.vue
View file @
f05cc2b
...
...
@@ -84,7 +84,7 @@
<!-- 级位/段位考试(仅人数合计) -->
<view
v-if=
"currentTab === '2' || currentTab === '3' || currentTab === '4'"
class=
"single-info"
>
<view
class=
"label"
>
人数合计
</view>
<view
class=
"value"
>
{{
item
.
content
.
allP
ersonCount
||
0
}}
</view>
<view
class=
"value"
>
{{
item
.
content
.
p
ersonCount
||
0
}}
</view>
</view>
<view
class=
"line"
v-if=
"currentTab === '2' || currentTab === '3' || currentTab === '4'"
></view>
<view
class=
"single-info"
>
...
...
@@ -111,10 +111,14 @@
<view
class=
"btn-group"
>
<!-- 已缴费:申请开票/已开票(需要审核通过才能开票) -->
<template
v-if=
"item.payStatus == 1 && item.invoiceStatus != 1&& item.auditStatus == 2"
>
<button
class=
"btn btn-invoice"
@
click=
"makeInvoiceFN(item)"
:disabled=
"item.invoiceStatus === 1"
>
<button
class=
"btn btn-
view-
invoice"
@
click=
"makeInvoiceFN(item)"
:disabled=
"item.invoiceStatus === 1"
>
开票
</button>
</
template
>
<!-- 已开票:查看发票 -->
<
template
v-if=
"item.invoiceStatus == 1"
>
<button
class=
"btn btn-invoice"
@
click
.
stop=
"viewInvoice(item)"
>
查看发票
</button>
</
template
>
<!-- 未缴费:去缴费 + 取消订单 -->
<!-- <template v-if="item.payStatus == 0">
<button class="btn btn-cancel" @click="handleCancel(item)">取消订单</button>
...
...
@@ -136,6 +140,40 @@
</view>
</scroll-view>
<!-- 发票查看弹窗 -->
<view
v-if=
"showInvoicePopup"
class=
"invoice-popup-mask"
@
click=
"closeInvoicePopup"
>
<view
class=
"invoice-popup-content"
@
click
.
stop
>
<view
class=
"invoice-popup-header"
>
<text
class=
"invoice-popup-title"
>
发票信息
</text>
<view
class=
"invoice-popup-close"
@
click=
"closeInvoicePopup"
>
✕
</view>
</view>
<view
class=
"invoice-popup-body"
>
<view
class=
"invoice-info-list"
>
<view
class=
"invoice-info-row"
>
<view
class=
"invoice-info-label"
>
发票类型
</view>
<view
class=
"invoice-type-badge"
:class=
"{ 'vat-type': invoiceData.invoiceType == 2 }"
>
{{ invoiceData.invoiceType == 1 ? '普通发票' : '增值税专用发票' }}
</view>
</view>
<view
class=
"invoice-info-row"
>
<text
class=
"invoice-info-label"
>
发票抬头
</text>
<text
class=
"invoice-info-value"
>
{{ invoiceData.invoiceBuyerName || '—' }}
</text>
</view>
<view
class=
"invoice-info-row"
v-if=
"invoiceData.invoiceBuyerTaxno"
>
<text
class=
"invoice-info-label"
>
纳税人识别号
</text>
<text
class=
"invoice-info-value"
>
{{ invoiceData.invoiceBuyerTaxno }}
</text>
</view>
<view
class=
"invoice-info-row"
>
<text
class=
"invoice-info-label"
>
接收邮箱
</text>
<text
class=
"invoice-info-value"
>
{{ invoiceData.invoicePushPhone || '—' }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 自定义删除确认弹窗 -->
<view
v-if=
"showDelPopup"
class=
"popup-mask"
@
touchmove
.
stop
.
prevent
@
click
.
stop=
"closeDelPopup"
>
<view
class=
"custom-modal"
@
click
.
stop
>
...
...
@@ -228,6 +266,8 @@ const queryParams = reactive({
const
showDelPopup
=
ref
(
false
);
const
showCancelPopup
=
ref
(
false
);
const
isPopupOpen
=
ref
(
false
);
const
showInvoicePopup
=
ref
(
false
);
const
invoiceData
=
ref
({});
// 弹窗内容
const
delModalContent
=
ref
(
''
);
...
...
@@ -397,6 +437,24 @@ const makeInvoiceFN = (item) => {
});
};
// 查看发票
const
viewInvoice
=
(
item
)
=>
{
invoiceData
.
value
=
{
invoiceType
:
item
.
invoiceType
||
1
,
invoiceBuyerName
:
item
.
invoiceTitle
||
item
.
invoiceBuyerName
||
'—'
,
invoiceBuyerTaxno
:
item
.
invoiceTaxno
||
item
.
invoiceBuyerTaxno
||
''
,
invoicePushPhone
:
item
.
invoiceEmail
||
item
.
invoicePushPhone
||
'—'
};
showInvoicePopup
.
value
=
true
;
isPopupOpen
.
value
=
true
;
};
// 关闭发票弹窗
const
closeInvoicePopup
=
()
=>
{
showInvoicePopup
.
value
=
false
;
isPopupOpen
.
value
=
false
;
};
// 取消订单
const
handleCancel
=
(
item
)
=>
{
currentOrder
.
value
=
item
;
...
...
@@ -492,6 +550,8 @@ const closeCancelPopup = () => {
padding
:
20
rpx
;
box-shadow
:
0
2
rpx
8
rpx
rgba
(
0
,
0
,
0
,
0.04
);
border-radius
:
12
rpx
;
display
:
flex
;
flex-direction
:
column
;
//
border-top
:
6
rpx
solid
transparent
;
//
background-clip
:
padding-box
,
border-box
;
//
background-origin
:
padding-box
,
border-box
;
...
...
@@ -671,23 +731,25 @@ const closeCancelPopup = () => {
align-items
:
center
;
gap
:
16
rpx
;
width
:
100%
;
margin-top
:
20
rpx
;
.btn
{
padding
:
12
rpx
32
rpx
;
//
固定宽度,所有按钮一样大
width
:
160
rpx
;
height
:
70
rpx
;
line-height
:
70
rpx
;
padding
:
0
;
border-radius
:
40
rpx
;
font-size
:
24
rpx
;
line-height
:
1.5
;
white-space
:
nowrap
;
display
:
inline-block
;
margin
:
0
;
border
:
none
;
width
:
80px
;
background
:
transparent
;
text-align
:
center
;
margin
:
0
;
&::after
{
border
:
none
;
display
:
none
;
//
关键:隐藏伪元素
}
&
.btn-delete
{
background
:
#fff
;
color
:
#e4393c
;
...
...
@@ -700,6 +762,12 @@ const closeCancelPopup = () => {
border
:
1
rpx
solid
#e4393c
;
}
&
.btn-view-invoice
{
background
:
linear-gradient
(
90deg
,
#FF755A
,
#F51722
);
color
:
#fff
;
border
:
none
;
}
&
.btn-cancel
{
background
:
#fff
;
color
:
#666
;
...
...
@@ -720,7 +788,6 @@ const closeCancelPopup = () => {
}
//
加载
/
无更多提示
.loading-tip
,
.no-more
{
text-align
:
center
;
...
...
@@ -814,4 +881,97 @@ const closeCancelPopup = () => {
color
:
#e8341d
;
letter-spacing
:
1
rpx
;
}
//
发票弹窗样式
.invoice-popup-mask
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background-color
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
999
;
}
.invoice-popup-content
{
width
:
600
rpx
;
background
:
#fff
;
border-radius
:
20
rpx
;
overflow
:
hidden
;
box-shadow
:
0
10
rpx
30
rpx
rgba
(
0
,
0
,
0
,
0.2
);
}
.invoice-popup-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
30
rpx
;
background
:
linear-gradient
(
135deg
,
#AD181F
0%
,
#E4393C
100%
);
.invoice-popup-title
{
font-size
:
32
rpx
;
font-weight
:
600
;
color
:
#fff
;
}
.invoice-popup-close
{
width
:
44
rpx
;
height
:
44
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
28
rpx
;
color
:
rgba
(
255
,
255
,
255
,
0.8
);
}
}
.invoice-popup-body
{
padding
:
30
rpx
;
}
.invoice-type-badge
{
display
:
inline-flex
;
align-items
:
center
;
padding
:
8
rpx
24
rpx
;
background
:
linear-gradient
(
135deg
,
#FF755A
0%
,
#F51722
100%
);
color
:
#fff
;
border-radius
:
30
rpx
;
font-size
:
24
rpx
;
font-weight
:
500
;
&.vat-type
{
background
:
linear-gradient
(
135deg
,
#6aaaf2
0%
,
#178cd7
100%
);
}
}
.invoice-info-list
{
.invoice-info-row
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
flex-start
;
padding
:
24
rpx
0
;
border-bottom
:
1
rpx
dashed
#eee
;
&:last-child
{
border-bottom
:
none
;
}
.invoice-info-label
{
font-size
:
26
rpx
;
color
:
#999
;
flex-shrink
:
0
;
}
.invoice-info-value
{
font-size
:
26
rpx
;
color
:
#333
;
text-align
:
right
;
word-break
:
break-all
;
max-width
:
340
rpx
;
}
}
}
</
style
>
\ No newline at end of file
...
...
pages/index/home.vue
View file @
f05cc2b
...
...
@@ -7,7 +7,7 @@
<view
class=
"loginOutIcon2"
@
click=
"goPath('/myCenter/index')"
>
<image
:src=
"config.loginImage_api + '/fs/static/dg/icon01@3x.png'"
class=
"switch-icon"
></image>
</view>
<view
class=
"welcome1"
@
click=
"goPath('/myCenter/index')"
>
<view
class=
"welcome1
mt30
"
@
click=
"goPath('/myCenter/index')"
>
<view
class=
"flex f-a-c"
>
<!--
<view>
-->
<text
class=
"title-border"
></text>
...
...
@@ -16,7 +16,7 @@
<!--
<image
:src=
"config.loginImage_api + '/fs/static/dg/icon013@x.png'"
class=
"switch-icon"
></image>
-->
</view>
<view
class=
"sub-title"
>
<view
class=
"mt10"
>
会员名称
会员所属道馆
</view>
<view
class=
"mt10"
>
您好!
{{
memberInfo
.
name
}}
</view>
<view
class=
"mt10"
>
欢迎使用中跆协会员管理系统!
</view>
</view>
</view>
...
...
@@ -488,6 +488,58 @@
</view>
</uni-section>
</view>
<!-- 绑定手机号弹框 -->
<uni-popup
ref=
"bindingPhonePopup"
type=
"center"
:mask-click=
"false"
>
<view
class=
"dialog-wrapper"
>
<view
class=
"dialog-close"
@
click=
"closeBindingPhoneDialog"
>
✕
</view>
<view
class=
"dialog-icon"
>
<uni-icons
type=
"phone"
size=
"48"
color=
"#AD181F"
></uni-icons>
</view>
<view
class=
"dialog-title"
>
绑定手机号
</view>
<view
class=
"dialog-content"
>
<view
class=
"form-item"
>
<input
class=
"form-input"
v-model=
"bindingForm.phone"
placeholder=
"请输入手机号"
maxlength=
"11"
type=
"number"
/>
</view>
<view
class=
"form-item captcha-row"
>
<input
class=
"form-input captcha-input"
v-model=
"bindingForm.captcha"
placeholder=
"图形验证码"
maxlength=
"4"
/>
<image
class=
"captcha-img"
:src=
"captchaUrl"
@
click=
"refreshCaptcha"
mode=
"aspectFit"
></image>
</view>
<view
class=
"form-item captcha-row"
>
<input
class=
"form-input sms-input"
v-model=
"bindingForm.code"
placeholder=
"短信验证码"
maxlength=
"6"
type=
"number"
/>
<button
class=
"sms-btn"
:disabled=
"smsCountdown > 0"
@
click=
"sendSmsCode"
>
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
</button>
</view>
</view>
<view
class=
"dialog-footer"
>
<button
class=
"dialog-btn cancel"
@
click=
"closeBindingPhoneDialog"
>
取消
</button>
<button
class=
"dialog-btn confirm"
@
click=
"submitBindingPhone"
>
确定
</button>
</view>
</view>
</uni-popup>
<!-- 申请成为考点弹框 -->
<uni-popup
ref=
"examPointPopup"
type=
"center"
:mask-click=
"false"
>
<view
class=
"dialog-wrapper exam-dialog"
>
<view
class=
"dialog-close"
@
click=
"closeExamPointDialog"
>
✕
</view>
<view
class=
"dialog-icon success-icon"
>
<uni-icons
type=
"checkmark"
size=
"48"
color=
"#29c490"
></uni-icons>
</view>
<view
class=
"dialog-title"
>
申请成为考点
</view>
<view
class=
"dialog-message"
>
<text>
恭喜您成为中国跆拳道协会团体会员!
</text>
<text>
根据协会考点管理办法,需成为考点单位才能进行考级业务的办理。
</text>
</view>
<view
class=
"dialog-footer"
>
<button
class=
"dialog-btn cancel"
@
click=
"closeExamPointDialog"
>
取消
</button>
<button
class=
"dialog-btn confirm"
@
click=
"goExamPointApply"
>
去申请
</button>
</view>
<view
class=
"no-display"
>
<text
@
click=
"handleNoDisplay"
>
不再显示
</text>
</view>
</view>
</uni-popup>
</view>
</template>
...
...
@@ -531,6 +583,21 @@ const newsList = ref([])
const
isInit
=
ref
(
false
)
const
isBlack
=
ref
(
0
)
// 绑定手机号弹框相关
const
bindingPhonePopup
=
ref
(
null
)
const
bindingForm
=
ref
({
phone
:
''
,
captcha
:
''
,
code
:
''
,
uuid
:
''
})
const
captchaUrl
=
ref
(
''
)
const
smsCountdown
=
ref
(
0
)
let
smsTimer
=
null
// 申请考点弹框相关
const
examPointPopup
=
ref
(
null
)
onShow
(()
=>
{
if
(
app
.
globalData
.
isLogin
)
{
init
()
...
...
@@ -723,7 +790,7 @@ function init() {
})
}
checkDialogs
()
uni
.
hideLoading
();
})
...
...
@@ -752,6 +819,116 @@ function goNewsDetail(n) {
url
:
`/pages/index/newsDetail?noteId=
${
n
.
noteId
}
`
});
}
// 绑定手机号弹框方法
function
refreshCaptcha
()
{
bindingForm
.
value
.
uuid
=
'uuid-'
+
Date
.
now
()
captchaUrl
.
value
=
config
.
baseUrl_api
+
'/captchaImage?uuid='
+
bindingForm
.
value
.
uuid
+
'&t='
+
Date
.
now
()
}
function
sendSmsCode
()
{
const
phone
=
bindingForm
.
value
.
phone
const
strTemp
=
/^1
[
2|3|4|5|6|7|8|9
][
0-9
]{9}
$/
if
(
!
phone
)
{
uni
.
showToast
({
title
:
'请输入手机号'
,
icon
:
'none'
})
return
}
if
(
!
strTemp
.
test
(
phone
))
{
uni
.
showToast
({
title
:
'请输入正确的手机号'
,
icon
:
'none'
})
return
}
if
(
!
bindingForm
.
value
.
captcha
)
{
uni
.
showToast
({
title
:
'请输入图形验证码'
,
icon
:
'none'
})
return
}
api
.
getSmsCode
({
uuid
:
bindingForm
.
value
.
uuid
,
telNo
:
phone
,
captcha
:
bindingForm
.
value
.
captcha
}).
then
(
res
=>
{
uni
.
showToast
({
title
:
'发送成功'
,
icon
:
'success'
})
smsCountdown
.
value
=
60
smsTimer
=
setInterval
(()
=>
{
smsCountdown
.
value
--
if
(
smsCountdown
.
value
<=
0
)
{
clearInterval
(
smsTimer
)
smsTimer
=
null
}
},
1000
)
}).
catch
(()
=>
{
refreshCaptcha
()
})
}
function
closeBindingPhoneDialog
()
{
bindingPhonePopup
.
value
.
close
()
}
async
function
submitBindingPhone
()
{
const
{
phone
,
captcha
,
code
,
uuid
}
=
bindingForm
.
value
if
(
!
phone
)
{
uni
.
showToast
({
title
:
'请输入手机号'
,
icon
:
'none'
})
return
}
if
(
!
captcha
)
{
uni
.
showToast
({
title
:
'请输入图形验证码'
,
icon
:
'none'
})
return
}
if
(
!
code
)
{
uni
.
showToast
({
title
:
'请输入短信验证码'
,
icon
:
'none'
})
return
}
try
{
await
api
.
editPhone
({
phone
,
captcha
,
code
,
uuid
})
uni
.
showToast
({
title
:
'修改成功'
,
icon
:
'success'
})
closeBindingPhoneDialog
()
setTimeout
(()
=>
{
uni
.
reLaunch
({
url
:
'/pages/index/home'
})
},
1500
)
}
catch
(
e
)
{
// 错误已由 request.js 处理
}
}
// 申请考点弹框方法
function
closeExamPointDialog
()
{
examPointPopup
.
value
.
close
()
}
function
goExamPointApply
()
{
closeExamPointDialog
()
uni
.
navigateTo
({
url
:
'/myCenter/examPointApplyList'
})
}
async
function
handleNoDisplay
()
{
await
api
.
noDisplay
()
closeExamPointDialog
()
}
// 检查弹框显示条件
function
checkDialogs
()
{
const
user
=
app
.
globalData
.
userInfo
||
{}
const
memberInfoData
=
app
.
globalData
.
memberInfo
||
{}
// 绑定手机号条件: changePassFlag='1' && activeStatus=1 && authenticationStatus=2 && phonenumber为空 && checkFlag=1
if
(
app
.
globalData
.
changePassFlag
===
'1'
&&
memberInfoData
.
activeStatus
==
1
&&
app
.
globalData
.
authenticationStatus
==
2
&&
!
user
.
phonenumber
&&
user
.
checkFlag
==
1
)
{
refreshCaptcha
()
bindingPhonePopup
.
value
.
open
()
}
// 申请考点条件: activeStatus=1 && authenticationStatus=2 && hintFlag=1 && deptType=6 && isPoints=1
if
(
memberInfoData
.
activeStatus
==
1
&&
app
.
globalData
.
authenticationStatus
==
2
&&
user
.
hintFlag
==
1
&&
app
.
globalData
.
deptType
==
6
&&
memberInfoData
.
isPoints
==
1
)
{
examPointPopup
.
value
.
open
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
:deep
(
.uni-section
)
{
...
...
@@ -1021,3 +1198,154 @@ function goNewsDetail(n) {
}
</
style
>
<
style
lang=
"scss"
scoped
>
/* 弹框样式 */
.dialog-wrapper
{
width
:
600
rpx
;
background
:
linear-gradient
(
135deg
,
#fff
0%
,
#f8f9fc
100%
);
border-radius
:
24
rpx
;
padding
:
48
rpx
40
rpx
40
rpx
;
position
:
relative
;
box-sizing
:
border-box
;
}
.dialog-close
{
position
:
absolute
;
top
:
20
rpx
;
right
:
24
rpx
;
font-size
:
32
rpx
;
color
:
#999
;
z-index
:
10
;
}
.dialog-icon
{
text-align
:
center
;
margin-bottom
:
24
rpx
;
}
.dialog-title
{
font-size
:
40
rpx
;
font-weight
:
600
;
color
:
#303133
;
text-align
:
center
;
margin-bottom
:
24
rpx
;
}
.dialog-content
{
padding
:
0
10
rpx
;
margin-bottom
:
32
rpx
;
}
.form-item
{
margin-bottom
:
24
rpx
;
}
.form-input
{
height
:
80
rpx
;
background
:
#f5f7fa
;
border-radius
:
16
rpx
;
padding
:
0
24
rpx
;
font-size
:
28
rpx
;
width
:
100%
;
box-sizing
:
border-box
;
}
.captcha-row
{
display
:
flex
;
align-items
:
center
;
gap
:
16
rpx
;
}
.captcha-input
{
flex
:
1
;
}
.captcha-img
{
width
:
180
rpx
;
height
:
72
rpx
;
border-radius
:
12
rpx
;
background
:
#f5f7fa
;
}
.sms-input
{
flex
:
1
;
}
.sms-btn
{
width
:
200
rpx
;
height
:
72
rpx
;
line-height
:
72
rpx
;
background
:
#AD181F
;
color
:
#fff
;
font-size
:
24
rpx
;
border-radius
:
16
rpx
;
padding
:
0
;
margin
:
0
;
&::after
{
border
:
none
;
}
}
.sms-btn
[
disabled
]
{
background
:
#c0c4cc
;
color
:
#fff
;
}
.dialog-footer
{
display
:
flex
;
justify-content
:
center
;
gap
:
40
rpx
;
}
.dialog-btn
{
width
:
220
rpx
;
height
:
80
rpx
;
line-height
:
80
rpx
;
border-radius
:
40
rpx
;
font-size
:
28
rpx
;
font-weight
:
500
;
padding
:
0
;
margin
:
0
;
&::after
{
border
:
none
;
}
}
.dialog-btn.cancel
{
background
:
#f5f7fa
;
color
:
#606266
;
border
:
1
rpx
solid
#dcdfe6
;
}
.dialog-btn.confirm
{
background
:
#AD181F
;
color
:
#fff
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
21
,
97
,
203
,
0.3
);
}
/* 考点弹框样式 */
.exam-dialog
{
.dialog-message
{
font-size
:
28
rpx
;
color
:
#606266
;
line-height
:
1.8
;
text-align
:
left
;
margin-bottom
:
40
rpx
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12
rpx
;
}
}
.no-display
{
text-align
:
right
;
margin-top
:
24
rpx
;
font-size
:
24
rpx
;
color
:
#909399
;
}
.success-icon
{
color
:
#29c490
;
}
</
style
>
...
...
pages/invoice/apply.vue
View file @
f05cc2b
...
...
@@ -4,61 +4,94 @@
<!-- 发票类型 -->
<view
class=
"form-item"
>
<text
class=
"label"
>
发票类型
</text>
<text
class=
"value"
>
{{
form
.
invoiceType
===
'2'
?
'普通发票(企业)'
:
'普通发票(个人)'
}}
</text>
<view
class=
"type-select"
>
<view
class=
"type-option"
:class=
"
{ active: form.invoiceType === '1' }"
@click="form.invoiceType = '1'"
>
<view
class=
"type-icon"
>
个
</view>
<view
class=
"type-info"
>
<text
class=
"type-name"
>
普通发票
</text>
<text
class=
"type-desc"
>
个人/非企业单位
</text>
</view>
</view>
<view
class=
"type-option"
:class=
"
{ active: form.invoiceType === '2' }"
@click="form.invoiceType = '2'"
>
<view
class=
"type-icon enterprise"
>
企
</view>
<view
class=
"type-info"
>
<text
class=
"type-name"
>
增值税发票
</text>
<text
class=
"type-desc"
>
企业/可抵扣
</text>
</view>
</view>
</view>
</view>
<!-- 发票抬头 -->
<view
class=
"form-item"
>
<view
class=
"form-item
column
"
>
<text
class=
"label"
>
发票抬头
</text>
<input
class=
"input"
v-model=
"form.name"
placeholder=
"请输入公司全称或个人姓名"
placeholder-style=
"color: #999;"
/>
<text
class=
"hint"
>
请确保发票抬头与公司营业执照或个人身份证上的名称一致。
</text>
</view>
<!-- 纳税人识别号(企业才显示) -->
<view
class=
"form-item"
v-if=
"form.invoiceType === '2'"
>
<view
class=
"form-item
column
"
v-if=
"form.invoiceType === '2'"
>
<text
class=
"label"
>
纳税人识别号
</text>
<input
class=
"input"
v-model=
"form.taxno"
placeholder=
"请输入纳税人识别号"
placeholder-style=
"color: #999;"
maxlength=
"20"
/>
</view>
<!-- 开票金额 -->
<view
class=
"form-item"
>
<text
class=
"label"
>
开票金额
</text>
<text
class=
"amount"
>
¥
{{
(
Number
(
form
.
amount
)).
toFixed
(
2
)
}}
</text>
<text
class=
"hint"
>
企业税务登记证上的号码,一般为 15、18 或 20 位
</text>
</view>
<!-- 接收方式 -->
<view
class=
"form-item"
>
<text
class=
"label"
>
接收方式
</text>
<text
class=
"value"
>
电子邮箱
</text>
<view
class=
"method-select"
>
<view
class=
"method-option active"
>
<view
class=
"method-icon"
>
邮
</view>
<view
class=
"method-info"
>
<text
class=
"method-name"
>
电子发票
</text>
<text
class=
"method-desc"
>
发送至邮箱
</text>
</view>
<view
class=
"method-tag"
>
推荐
</view>
</view>
</view>
</view>
<!-- 开票金额 -->
<!--
<view
class=
"form-item"
>
<text
class=
"label"
>
开票金额
</text>
<text
class=
"amount"
>
¥
{{
(
Number
(
form
.
amount
)).
toFixed
(
2
)
}}
</text>
</view>
-->
<!-- 接收邮箱 -->
<view
class=
"form-item"
>
<text
class=
"label"
>
接收邮箱
</text>
<view
class=
"form-item
column
"
>
<text
class=
"label"
>
接收邮箱
号码
</text>
<input
class=
"input"
v-model=
"form.
email"
placeholder=
"请输入接收发票的邮箱
(必填)
"
placeholder-style=
"color: #999;
"
v-model=
"form.
phone"
placeholder=
"请输入接收发票的邮箱
号码
"
type=
"text
"
/>
<text
class=
"hint"
>
电子发票将在 3-5 个工作日内发送至该邮箱
</text>
</view>
</view>
<view
class=
"hint"
>
电子发票将在3-5个工作日内发送至该邮箱
</view>
<!-- 提交按钮 -->
<view
class=
"btn-wrap"
@
click=
"submitInvoice"
>
<view
class=
"submit-btn"
>
提交申请
</view>
<view
class=
"btn-wrap"
>
<view
class=
"submit-btn"
:class=
"
{ loading: submitting }" @click="handleSubmit">
{{
submitting
?
'提交中...'
:
'提交申请'
}}
</view>
</view>
</view>
</
template
>
...
...
@@ -66,14 +99,17 @@
<
script
setup
>
import
{
ref
,
reactive
}
from
'vue'
;
import
{
onLoad
}
from
'@dcloudio/uni-app'
;
import
{
outputInvoiceNo
}
from
'@/common/api.js'
;
// 与PC端接口一致
import
{
outputInvoiceNo
}
from
'@/common/api.js'
;
const
submitting
=
ref
(
false
);
// 表单数据(与PC端字段完全对齐)
const
form
=
reactive
({
invoiceType
:
'1'
,
// 1=个人 2=企业
deliveryMethod
:
'1'
,
// 接收方式:1=电子发票
name
:
''
,
// 发票抬头
taxno
:
''
,
// 纳税人识别号
email
:
''
,
// 邮箱
phone
:
''
,
// 邮箱
amount
:
0
,
// 金额
id
:
''
// 订单ID
});
...
...
@@ -81,73 +117,76 @@ const form = reactive({
// 页面加载(接收PC端传来的参数)
onLoad
((
options
)
=>
{
if
(
options
.
id
||
options
.
orderId
)
{
form
.
id
=
options
.
id
||
options
.
orderId
;
form
.
id
=
options
.
id
||
options
.
orderId
;
form
.
amount
=
options
.
amount
;
console
.
log
(
33
,
form
.
amount
);
}
if
(
options
.
invoiceType
)
{
form
.
invoiceType
=
options
.
invoiceType
;
}
// getOrderInfo();
});
// 获取订单金额
// const getOrderInfo = async () => {
// try {
// // 这里替换成你真实获取订单金额的接口
// = 1500;
// } catch (error) {
// uni.showToast({ title: '获取订单信息失败', icon: 'none' });
// }
// };
// 提交发票申请(与PC逻辑完全一致)
const
submitInvoice
=
async
()
=>
{
// 1. PC端逻辑:个人不允许开票
if
(
form
.
invoiceType
===
'1'
)
{
return
uni
.
showToast
({
title
:
'暂不支持个人开票'
,
icon
:
'none'
});
}
// 2. 抬头校验
// 表单验证
const
validateForm
=
()
=>
{
// 发票抬头校验
if
(
!
form
.
name
)
{
return
uni
.
showToast
({
title
:
'请输入发票抬头'
,
icon
:
'none'
});
uni
.
showToast
({
title
:
'请输入发票抬头'
,
icon
:
'none'
});
return
false
;
}
if
(
form
.
name
.
length
<
2
||
form
.
name
.
length
>
100
)
{
uni
.
showToast
({
title
:
'发票抬头长度在2-100个字符之间'
,
icon
:
'none'
});
return
false
;
}
//
3.
企业必须填纳税人识别号
// 企业必须填纳税人识别号
if
(
form
.
invoiceType
===
'2'
&&
!
form
.
taxno
)
{
return
uni
.
showToast
({
title
:
'请输入纳税人识别号'
,
icon
:
'none'
});
uni
.
showToast
({
title
:
'请输入纳税人识别号'
,
icon
:
'none'
});
return
false
;
}
// 4. 纳税人识别号格式校验(同PC)
// 纳税人识别号格式校验(同PC)
if
(
form
.
invoiceType
===
'2'
)
{
const
taxReg
=
/^
[
A-Z0-9
]{15}
$|^
[
A-Z0-9
]{18}
$|^
[
A-Z0-9
]{20}
$/
;
if
(
form
.
invoiceType
===
'2'
&&
!
taxReg
.
test
(
form
.
taxno
))
{
return
uni
.
showToast
({
title
:
'纳税人识别号格式不正确'
,
icon
:
'none'
});
if
(
!
taxReg
.
test
(
form
.
taxno
))
{
uni
.
showToast
({
title
:
'纳税人识别号格式不正确'
,
icon
:
'none'
});
return
false
;
}
}
//
5.
邮箱校验
const
emailReg
=
/^
[
a-zA-Z0-9._%+-
]
+@
[
a-zA-Z0-9.-
]
+
\.[
a-zA-Z
]{2,}
$/
;
if
(
!
form
.
email
)
{
return
uni
.
showToast
({
title
:
'请输入接收邮箱'
,
icon
:
'none'
})
;
// 邮箱校验
if
(
!
form
.
phone
)
{
uni
.
showToast
({
title
:
'请输入接收邮箱'
,
icon
:
'none'
});
return
false
;
}
if
(
!
emailReg
.
test
(
form
.
email
))
{
return
uni
.
showToast
({
title
:
'请输入正确的邮箱地址'
,
icon
:
'none'
});
const
phoneReg
=
/^
[
a-zA-Z0-9._%+-
]
+@
[
a-zA-Z0-9.-
]
+
\.[
a-zA-Z
]{2,}
$/
;
if
(
!
phoneReg
.
test
(
form
.
phone
))
{
uni
.
showToast
({
title
:
'请输入正确的邮箱地址'
,
icon
:
'none'
});
return
false
;
}
return
true
;
};
// 提交发票申请(与PC逻辑完全一致)
const
handleSubmit
=
async
()
=>
{
if
(
submitting
.
value
)
return
;
if
(
!
validateForm
())
return
;
submitting
.
value
=
true
;
console
.
log
(
'提交表单数据:'
,
form
);
try
{
// 调用PC端同一个接口:outputInvoiceNo
await
outputInvoiceNo
(
form
);
uni
.
showToast
({
title
:
'发票申请提交成功!'
,
icon
:
'success'
,
duration
:
2000
title
:
'开票申请提交成功'
,
icon
:
'success'
});
setTimeout
(()
=>
{
uni
.
navigateBack
();
},
20
00
);
},
15
00
);
}
catch
(
error
)
{
uni
.
showToast
({
title
:
'提交失败,请检查信息'
,
icon
:
'none'
});
submitting
.
value
=
false
;
// 错误已由 request.js 处理
}
finally
{
submitting
.
value
=
false
;
}
};
</
script
>
...
...
@@ -156,52 +195,198 @@ const submitInvoice = async () => {
.invoice-apply
{
min-height
:
100vh
;
background
:
#f5f7fa
;
}
.content
{
.content
{
padding
:
20
rpx
;
}
.form-item
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
.form-item
{
background
:
#fff
;
padding
:
24
rpx
;
border-bottom
:
1
rpx
solid
#eee
;
margin-bottom
:
20
rpx
;
border-radius
:
16
rpx
;
&.column
{
display
:
flex
;
flex-direction
:
column
;
}
.label
{
font-size
:
28
rpx
;
color
:
#333
;
font-weight
:
500
;
margin-bottom
:
16
rpx
;
}
.input
{
width
:
8
0%
;
width
:
10
0%
;
font-size
:
28
rpx
;
color
:
#333
;
padding
:
16
rpx
0
;
text-align
:
right
;
}
padding
:
0
24
rpx
;
box-sizing
:
border-box
;
background
:
transparent
;
min-width
:
0
;
text-align
:
left
;
border
:
1
rpx
solid
#ccc
;
border-radius
:
8
rpx
;
height
:
80
rpx
;
line-height
:
80
rpx
;
.value
{
&::placeholder
{
color
:
#999
;
font-size
:
28
rpx
;
color
:
#333
;
line-height
:
80
rpx
;
}
}
.form-item.column
.input
{
width
:
100%
;
display
:
block
;
}
.hint
{
font-size
:
24
rpx
;
color
:
#909399
;
margin-top
:
8
rpx
;
}
.amount
{
font-size
:
32
rpx
;
color
:
#AD181F
;
font-weight
:
600
;
}
}
/* 发票类型选择 */
.type-select
{
display
:
flex
;
gap
:
20
rpx
;
margin-top
:
10
rpx
;
}
.type-option
{
flex
:
1
;
display
:
flex
;
align-items
:
center
;
padding
:
20
rpx
;
border
:
2
rpx
solid
#e4e7ed
;
border-radius
:
12
rpx
;
background
:
#fafafa
;
&.active
{
border-color
:
#AD181F
;
background
:
#FFF5F5
;
}
.type-icon
{
width
:
60
rpx
;
height
:
60
rpx
;
background
:
#f5f7ff
;
border-radius
:
8
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
24
rpx
;
color
:
#409eff
;
font-weight
:
600
;
margin-right
:
16
rpx
;
&.enterprise
{
background
:
#f0f6ff
;
color
:
#1561CB
;
}
}
&
.active
.type-icon
{
background
:
#AD181F
;
color
:
#fff
;
}
.type-info
{
display
:
flex
;
flex-direction
:
column
;
.type-name
{
font-size
:
28
rpx
;
color
:
#e4393c
;
font-weight
:
500
;
font-weight
:
600
;
color
:
#303133
;
}
.type-desc
{
font-size
:
22
rpx
;
color
:
#909399
;
margin-top
:
4
rpx
;
}
}
}
.hint
{
font-size
:
26
rpx
;
color
:
#B6BCC0
;
margin-top
:
20
rpx
;
text-align
:
center
;
/* 接收方式选择 */
.method-select
{
margin-top
:
10
rpx
;
}
.method-option
{
display
:
flex
;
align-items
:
center
;
padding
:
20
rpx
;
border
:
2
rpx
solid
#e4e7ed
;
border-radius
:
12
rpx
;
background
:
#fafafa
;
&.active
{
border-color
:
#AD181F
;
background
:
#FFF5F5
;
}
.method-icon
{
width
:
60
rpx
;
height
:
60
rpx
;
background
:
#f5f7ff
;
border-radius
:
12
rpx
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
24
rpx
;
color
:
#409eff
;
font-weight
:
600
;
margin-right
:
16
rpx
;
}
&
.active
.method-icon
{
background
:
#AD181F
;
color
:
#fff
;
}
.method-info
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
.method-name
{
font-size
:
28
rpx
;
font-weight
:
600
;
color
:
#303133
;
}
.method-desc
{
font-size
:
22
rpx
;
color
:
#909399
;
margin-top
:
4
rpx
;
}
}
.method-tag
{
font-size
:
20
rpx
;
color
:
#67C23A
;
background
:
#f0f9f0
;
padding
:
4
rpx
12
rpx
;
border-radius
:
6
rpx
;
}
}
.btn-wrap
{
/* 提交按钮 */
.btn-wrap
{
width
:
100%
;
background-color
:
#fff
;
padding
:
30
rpx
;
...
...
@@ -209,18 +394,22 @@ const submitInvoice = async () => {
bottom
:
0
;
left
:
0
;
right
:
0
;
}
box-sizing
:
border-box
;
}
.submit-btn
{
height
:
70
rpx
;
line-height
:
70
rpx
;
border-radius
:
35
rpx
;
width
:
90%
;
margin
:
0
auto
;
.submit-btn
{
height
:
88
rpx
;
line-height
:
88
rpx
;
border-radius
:
44
rpx
;
width
:
100%
;
background
:
#AD181F
;
color
:
#fff
;
font-size
:
28
rpx
;
font-size
:
32
rpx
;
text-align
:
center
;
font-weight
:
500
;
&.loading
{
background
:
#c0c4cc
;
}
}
</
style
>
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment