提交 062dd248 authored 作者: pengxiaohui's avatar pengxiaohui

update: 项目第一版

上级 cbca1d2d
VITE_LOGIN_URL=https://login.ezijing.com/auth/login/index
VITE_SHARE_URL=https://account2.ezijing.com
\ No newline at end of file
VITE_LOGIN_URL=https://login2.ezijing.com/auth/login/index
VITE_SHARE_URL=https://dev.ezijing.com:3000/h5/payment/invoice
\ No newline at end of file
VITE_LOGIN_URL=https://login2.ezijing.com/auth/login/index
VITE_SHARE_URL=https://account2.ezijing.com
\ No newline at end of file
......@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<title>紫荆教育-账户管理系统</title>
</head>
<body>
<div id="app"></div>
......
......@@ -10,6 +10,7 @@
"axios": "^0.21.1",
"element-ui": "^2.15.5",
"query-string": "^7.0.1",
"vant": "^2.12.26",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuex": "^3.6.2"
......@@ -591,6 +592,22 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/@babel/template": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz",
......@@ -780,6 +797,15 @@
"integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
"dev": true
},
"node_modules/@popperjs/core": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz",
"integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/plugin-eslint": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-eslint/-/plugin-eslint-8.0.1.tgz",
......@@ -825,11 +851,23 @@
"dev": true,
"peer": true
},
"node_modules/@vant/icons": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@vant/icons/-/icons-1.7.0.tgz",
"integrity": "sha512-sqKvtYcSgSd6+AU1nBPaZARn2Nzf8hi0ErLhfXVR6f+Y7R0gojGZVoxuB83yUI6+0LwbitW5IfN3E6qzEsu21Q=="
},
"node_modules/@vant/popperjs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.1.0.tgz",
"integrity": "sha512-8MD1gz146awV/uPxYjz4pet22f7a9YVKqk7T+gFkWFwT9mEcrIUEg/xPrdOnWKLP9puXyYtm7oVfSDSefZ/p/w==",
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@vue/babel-helper-vue-jsx-merge-props": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
"integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==",
"dev": true
"integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA=="
},
"node_modules/@vue/babel-plugin-transform-vue-jsx": {
"version": "1.2.1",
......@@ -5906,6 +5944,21 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vant": {
"version": "2.12.26",
"resolved": "https://registry.npmjs.org/vant/-/vant-2.12.26.tgz",
"integrity": "sha512-1JsFLt088joJgYbnIeunAIt2mySrypvIcwlpFlrcqEDs2mlb+HyGqpNjUL1bI2RhH9z3Qk7fJxY/1zs5VuhlKQ==",
"dependencies": {
"@babel/runtime": "7.x",
"@vant/icons": "^1.5.3",
"@vant/popperjs": "^1.0.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
"vue-lazyload": "1.2.3"
},
"peerDependencies": {
"vue": ">= 2.6.0"
}
},
"node_modules/vite": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.5.1.tgz",
......@@ -6112,6 +6165,11 @@
"semver": "bin/semver.js"
}
},
"node_modules/vue-lazyload": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/vue-lazyload/-/vue-lazyload-1.2.3.tgz",
"integrity": "sha512-DC0ZwxanbRhx79tlA3zY5OYJkH8FYp3WBAnAJbrcuoS8eye1P73rcgAZhyxFSPUluJUTelMB+i/+VkNU/qVm7g=="
},
"node_modules/vue-router": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.2.tgz",
......@@ -6669,6 +6727,21 @@
"@babel/plugin-syntax-typescript": "^7.14.5"
}
},
"@babel/runtime": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"requires": {
"regenerator-runtime": "^0.13.4"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
}
}
},
"@babel/template": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz",
......@@ -6818,6 +6891,11 @@
"integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
"dev": true
},
"@popperjs/core": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz",
"integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ=="
},
"@rollup/plugin-eslint": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-eslint/-/plugin-eslint-8.0.1.tgz",
......@@ -6851,11 +6929,23 @@
"dev": true,
"peer": true
},
"@vant/icons": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@vant/icons/-/icons-1.7.0.tgz",
"integrity": "sha512-sqKvtYcSgSd6+AU1nBPaZARn2Nzf8hi0ErLhfXVR6f+Y7R0gojGZVoxuB83yUI6+0LwbitW5IfN3E6qzEsu21Q=="
},
"@vant/popperjs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@vant/popperjs/-/popperjs-1.1.0.tgz",
"integrity": "sha512-8MD1gz146awV/uPxYjz4pet22f7a9YVKqk7T+gFkWFwT9mEcrIUEg/xPrdOnWKLP9puXyYtm7oVfSDSefZ/p/w==",
"requires": {
"@popperjs/core": "^2.9.2"
}
},
"@vue/babel-helper-vue-jsx-merge-props": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz",
"integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==",
"dev": true
"integrity": "sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA=="
},
"@vue/babel-plugin-transform-vue-jsx": {
"version": "1.2.1",
......@@ -10912,6 +11002,18 @@
"spdx-expression-parse": "^3.0.0"
}
},
"vant": {
"version": "2.12.26",
"resolved": "https://registry.npmjs.org/vant/-/vant-2.12.26.tgz",
"integrity": "sha512-1JsFLt088joJgYbnIeunAIt2mySrypvIcwlpFlrcqEDs2mlb+HyGqpNjUL1bI2RhH9z3Qk7fJxY/1zs5VuhlKQ==",
"requires": {
"@babel/runtime": "7.x",
"@vant/icons": "^1.5.3",
"@vant/popperjs": "^1.0.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
"vue-lazyload": "1.2.3"
}
},
"vite": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.5.1.tgz",
......@@ -11065,6 +11167,11 @@
}
}
},
"vue-lazyload": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/vue-lazyload/-/vue-lazyload-1.2.3.tgz",
"integrity": "sha512-DC0ZwxanbRhx79tlA3zY5OYJkH8FYp3WBAnAJbrcuoS8eye1P73rcgAZhyxFSPUluJUTelMB+i/+VkNU/qVm7g=="
},
"vue-router": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.2.tgz",
......
......@@ -13,8 +13,11 @@
},
"dependencies": {
"axios": "^0.21.1",
"clipboard": "^2.0.8",
"element-ui": "^2.15.5",
"qrcode.vue": "^1.7.0",
"query-string": "^7.0.1",
"vant": "^2.12.26",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuex": "^3.6.2"
......@@ -22,10 +25,14 @@
"devDependencies": {
"@rollup/plugin-eslint": "^8.0.1",
"ali-oss": "^6.16.0",
"babel-plugin-import": "^1.13.3",
"chalk": "^4.1.2",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-vue": "^7.16.0",
"sass": "^1.38.0",
"vite": "^2.5.0",
......
......@@ -2,3 +2,37 @@
margin: 0;
padding: 0;
}
body {
background-color: #f9f9f9;
color:#666;
}
/* element-ui input,textarea font-family reset */
.el-input__inner, .el-textarea__inner{
font-family: 'PingFang SC', 'PingFangSC-Regular', 'Source Han Sans CN', -apple-system, 'Microsoft YaHei', 'Helvetica', 'Arial', Verdana,
'Hiragino Sans GB', 'Wenquanyi Micro Hei', sans-serif;
}
/* element-ui drawer reset */
.el-drawer__header{
padding:14px 16px;
margin:0;
border-bottom:1px solid #DCDFE6;
}
.el-drawer__header>h5{
font-size:16px;
color:#666;
font-weight:600;
}
.el-drawer__body{
height:calc(100% - 82px);
overflow-y: auto;
}
/* element-ui dialog reset */
.el-dialog .el-dialog__header{
padding:20px 16px 10px;
}
.el-dialog .el-dialog__body{
padding:10px 0;
}
.el-dialog__footer{
text-align: center;
}
\ No newline at end of file
<template>
<div class="table-list">
<div class="table-list-hd">
<!-- 筛选 -->
<div class="table-list-filter" v-if="filters.length">
<el-form :inline="true" :model="params" size="mini" ref="filterForm">
<template v-for="item in filters">
<el-form-item :prop="item.prop" :key="item.prop">
<!-- input -->
<el-input v-model="params[item.prop]" v-bind="item" clearable v-if="item.type === 'input'" style="width:200px;"/>
<!-- select -->
<el-select
v-model="params[item.prop]"
clearable
v-bind="item"
v-if="item.type === 'select'"
@change="search"
style="width:200px;"
>
<template v-for="option in item.options">
<el-option
:label="option[item.labelKey] || option[item.alternateLabelKey] || option.label"
:value="option[item.valueKey] || option.value"
:key="option.value"
>
<template v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="option" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
</template>
</el-option>
</template>
</el-select>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="search">查询</el-button>
<el-button icon="el-icon-refresh-left" @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-list-hd-aside"><slot name="header-aside" /></div>
</div>
<slot></slot>
<div class="table-list-bd">
<el-table ref="table" :data="dataList" size="mini" v-loading="loading" v-bind="$attrs" v-on="$listeners" height="100%">
<template v-for="item in columns">
<el-table-column v-bind="item" :key="item.prop" v-if="visible(item)">
<template v-slot:default="scope" v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="scope" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
</template>
</el-table-column>
</template>
</el-table>
</div>
<div class="table-list-ft">
<slot name="footer"></slot>
<el-pagination
class="table-list-pagination"
layout="total, prev, pager, next, sizes, jumper"
:current-page.sync="page.currentPage"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size="page.size"
:total="page.total"
@size-change="pageSizeChange"
@current-change="fetchList"
v-if="hasPagination"
>
</el-pagination>
</div>
</div>
</template>
<script>
export default {
name: 'TableList',
props: {
// 接口请求
remote: { type: Object, default: () => ({}) },
// 筛选
filters: { type: Array, default: () => [] },
// 列表项
columns: { type: Array, default: () => [] },
// 列表数据
data: { type: Array, default: () => [] },
// 是否含有翻页
hasPagination: { type: Boolean, default: true },
// 每页多少条数据
limit: { type: Number, default: 20 }
},
data() {
return {
loading: false,
params: this.remote.params || {},
dataList: this.data,
page: { total: 0, size: this.limit, currentPage: 1 },
selection: []
}
},
watch: {
'remote.params': {
immediate: true,
handler(data) {
this.params = data || {}
}
},
data: {
immediate: true,
handler(data) {
this.dataList = data
}
}
},
methods: {
fetchList() {
/**
* @param function httpRequest api接口
* @param function beforeRequest 接口请求之前
* @param function callback 接口请求成功回调
*/
const { httpRequest, beforeRequest, callback } = this.remote
if (!httpRequest) {
return
}
// 参数设置
let params = this.params
// 翻页参数设置
if (this.hasPagination) {
params.page = this.page.currentPage
params['per-page'] = this.page.size
}
// 接口请求之前
if (beforeRequest) {
params = beforeRequest(params)
}
for (const key in params) {
if (params[key] === '' || params[key] === undefined || params[key] === undefined) {
delete params[key]
}
}
this.loading = true
httpRequest(params)
.then(res => {
const { list = [], total = 0 } = res.data || {}
this.page.total = total
this.dataList = callback ? callback(list) : list
})
.finally(() => {
this.loading = false
})
},
// 搜索
search() {
this.page.currentPage = 1
this.fetchList()
},
// 重置
reset() {
// 清空筛选条件
this.$refs.filterForm && this.$refs.filterForm.resetFields()
// 初始化页码
this.page.currentPage = 1
// 刷新列表
this.fetchList()
},
// 刷新
refetch(isForce) {
isForce ? this.reset() : this.fetchList()
},
// 页数改变
pageSizeChange(value) {
this.page.currentPage = 1
this.page.size = value
this.fetchList()
},
visible(item) {
return Object.prototype.hasOwnProperty.call(item, 'visible') ? item.visible : true
},
tableReview(arr, list) { // 默认选中
list.forEach(item => {
if (arr.indexOf(item.id) > -1) {
this.$refs.table.toggleRowSelection(item, true)
}
})
}
},
beforeMount() {
this.fetchList()
}
}
</script>
<style lang="scss">
.table-list {
padding: 10px;
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.table-list-hd {
display: flex;
}
.table-list-filter {
flex: 1;
}
.table-list-bd {
flex: 1;
height: 0;
}
.table-list-bd .el-table th:first-child .cell{
padding-left:14px;
}
.table-list-ft {
display: flex;
align-items: center;
justify-content: space-between;
}
.table-list-pagination {
padding: 10px 0;
text-align: right;
}
</style>
<template>
<el-dialog
:title="title"
width="700px"
:close-on-click-modal="false"
@opened="handleOpened"
v-bind="$attrs"
v-on="$listeners"
>
<div class="qrcode">
<div class="qrcode-left">
<qrcode-vue :value="value" size="180" ref="qrcode" />
</div>
<div class="qrcode-right">
<h4>分享链接</h4>
<el-input readonly :value="value" id="qrcodeValue">
<el-button slot="append" data-clipboard-target="#qrcodeValue" ref="copy">复制</el-button>
</el-input>
<p>可推广至微信、支付宝、QQ等任意渠道</p>
<a :href="qrcodeUrl" download="下载二维码">
<el-button type="text">下载二维码</el-button>
</a>
</div>
</div>
</el-dialog>
</template>
<script>
import QrcodeVue from 'qrcode.vue'
import Clipboard from 'clipboard'
export default {
components: { QrcodeVue },
props: {
title: { type: String, default: '推广' },
value: { type: String, default: '' }
},
data() {
return {
qrcodeUrl: '',
clipboard: null
}
},
methods: {
getQrcodeUrl() {
this.qrcodeUrl = this.$refs.qrcode.$el
.querySelector('canvas')
.toDataURL('image/png')
.replace('image/png', 'image/octet-stream')
},
initClipboard() {
if (this.clipboard) {
return
}
this.clipboard = new Clipboard(this.$refs.copy.$el)
this.clipboard.on('success', () => {
this.$message({ message: '复制成功!', type: 'success' })
})
this.clipboard.on('error', () => {
this.$message({ message: '复制失败,请手动选择复制!', type: 'error' })
})
},
handleOpened() {
this.$nextTick(function () {
this.initClipboard()
this.getQrcodeUrl()
})
}
},
destroyed() {
this.clipboard && this.clipboard.destroy()
}
}
</script>
<style lang="scss" scoped>
.qrcode {
margin: 0 10px 20px;
display: flex;
}
.qrcode-left {
background-color: #f7f8fa;
box-sizing: border-box;
width: 292px;
height: 292px;
padding: 56px;
}
.qrcode-right {
flex: 1;
margin-left: 20px;
h4 {
font-weight: normal;
margin-bottom: 12px;
}
p {
font-size: 14px;
color: #969799;
line-height: 20px;
height: 16px;
margin: 8px 0;
}
}
</style>
......@@ -3,14 +3,20 @@ import App from './App.vue'
import router from './router'
import store from '@/store'
import modules from './modules'
// 公共css
import '@/assets/css/base.css'
import beforeEnter from '@/utils/beforeEnter'
// Element-UI
import ElementUI from 'element-ui'
import '@/assets/theme/index.css'
// vant
import Vant from 'vant'
import 'vant/lib/index.css'
// 公共css
import '@/assets/css/base.css'
Vue.use(ElementUI)
Vue.use(Vant)
// 注册模块
modules({ router, store })
......
<template>
<div>Home</div>
<div>
紫荆账户管理系统
</div>
</template>
<script>
......
import httpRequest from '@/utils/axios'
// 获取用户信息
export function getUser() {
return httpRequest.get('/api/passport/account/get-user-info')
// 获取缴费记录
export function getPayList() {
return httpRequest.get('/api/finance/v1/payments/list')
}
// 开具发票
export function createInvoice(data) {
return httpRequest.post('/api/finance/v1/invoices/create', data)
}
// 开票历史
export function invoiceHistory(params) {
return httpRequest.get('/api/finance/v1/invoices/history', { params })
}
<template>
<div class="history">
<div class="search" v-if="hasSearch">
<el-input
placeholder="请输入内容回车搜索"
prefix-icon="el-icon-search"
v-model="taxpayer_name"
size="small"
@change="fetchHistory"
/>
</div>
<ul v-if="list.length">
<li
class="invoice-item van-hairline--bottom"
v-for="(item, index) in list"
:key="index"
@click="handleSelect(item)"
>
纳税人名称:{{ item.taxpayer_name }}
</li>
</ul>
<van-empty v-else description="暂无记录" />
</div>
</template>
<script>
import { invoiceHistory } from '../api'
export default {
props: {
hasSearch: {
type: Boolean,
default: false
}
},
data() {
return {
taxpayer_name: '',
list: [
// {
// id: '6838359004133982208',
// payment_id: '6741622324350418944',
// sales_rep_user_id: null,
// invoice_status: '3',
// invoice_application_time: '2021-08-31 14:37:17',
// invoice_issuing_time: null,
// invoice_remark: '09D103:发票已用完',
// invoice_color_type: '0',
// invoice_type: '2',
// invoice_haoma: null,
// invoice_daima: null,
// invoice_total_amount: '600.00',
// invoice_tax_rate: '0.06',
// invoice_tax_rate_amount: '33.96',
// invoice_excluding_tax_amount: '566.04',
// taxpayer_type: '1',
// taxpayer_name: '陈双喜',
// taxpayer_identifier: null,
// taxpayer_address: null,
// taxpayer_mobile: null,
// taxpayer_bank_name: null,
// taxpayer_bank_account: null,
// taxpayer_remark: '刘鸿雁',
// recipient_address: null,
// recipient_name: null,
// recipient_mobile: null,
// recipient_email: '1025519992@qq.com',
// created_operator: '6592683672086773760',
// updated_operator: '6592683672086773760',
// created_time: '2021-08-31 14:37:17',
// updated_time: '2021-08-31 14:37:17',
// old_id: null
// }
]
}
},
created() {
this.fetchHistory()
},
methods: {
fetchHistory() {
const params = {
taxpayer_name: this.taxpayer_name || undefined
}
invoiceHistory(params).then(res => {
if (res.code === 0 && res.data && res.data.list) {
this.list = res.data.list
}
})
},
handleSelect(item) {
this.$emit('select', item)
}
}
}
</script>
<style scoped>
.history {
min-height: 360px;
max-height: 500px;
}
.search {
padding: 0 20px;
}
ul {
min-height: 320px;
max-height: 450px;
padding: 0 20px;
overflow-y: auto;
}
li {
font-size: 15px;
line-height: 44px;
cursor: pointer;
transition: 0.3s;
}
li:hover {
color: #3276fc;
}
</style>
<template>
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="120px">
<!-- <el-form-item class="form-item-border" label="开票类型" prop="type">
<el-radio-group v-model="form.type">
<el-radio :label="1">红字发票</el-radio>
<el-radio :label="2">篮字发票</el-radio>
</el-radio-group>
</el-form-item> -->
<el-form-item class="form-item-border" label="开票人类型" prop="taxpayer_type">
<el-radio-group v-model="form.taxpayer_type">
<el-radio label="2">企业</el-radio>
<el-radio label="3">非营利性单位</el-radio>
<el-radio label="1">个人</el-radio>
</el-radio-group>
</el-form-item>
<div class="form-content">
<h5>发票信息
<el-button style="float:right;" type="text" @click="$emit('history')">历史开票信息</el-button>
</h5>
<el-form-item label="发票类型" prop="invoice_type">
<el-select v-model="form.invoice_type" placeholder="请选择发票类型" size="small" style="width:100%;">
<el-option label="普通发票(纸质)" value="1"></el-option>
<el-option v-if="form.taxpayer_type === '2'" label="专用发票(纸质)" value="3"></el-option>
<el-option label="普通发票(电子)" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="纳税人名称" prop="taxpayer_name">
<el-input v-model="form.taxpayer_name" size="small" placeholder="请输入纳税人名称"/>
</el-form-item>
<el-form-item label="纳税人识别号" prop="taxpayer_identifier">
<el-input v-model="form.taxpayer_identifier" size="small" placeholder="请输入纳税人识别号"/>
</el-form-item>
<template v-if="form.invoice_type === '3'">
<el-form-item label="地址" prop="taxpayer_address" :rules="{required: true, message: '请输入地址', trigger: 'blur'}">
<el-input v-model="form.taxpayer_address" size="small" placeholder="请输入地址"/>
</el-form-item>
<el-form-item label="电话" prop="taxpayer_mobile" :rules="{required: true, message: '请输入电话', trigger: 'blur'}">
<el-input v-model="form.taxpayer_mobile" size="small" placeholder="请输入电话"/>
</el-form-item>
<el-form-item label="开户行" prop="taxpayer_bank_name" :rules="{required: true, message: '请输入开户行', trigger: 'blur'}">
<el-input v-model="form.taxpayer_bank_name" size="small" placeholder="请输入开户行"/>
</el-form-item>
<el-form-item label="银行账号" prop="taxpayer_bank_account" :rules="{required: true, message: '请输入银行账号', trigger: 'blur'}">
<el-input v-model="form.taxpayer_bank_account" size="small" placeholder="请输入银行账号"/>
</el-form-item>
</template>
<el-form-item label="备注" prop="remarks" style="margin-bottom:0;">
<el-input v-model="form.remarks" size="small" placeholder="请输入备注"/>
</el-form-item>
<h5>收票信息</h5>
<el-form-item v-if="form.invoice_type === '2'" label="邮箱地址" prop="recipient_email">
<el-input v-model="form.recipient_email" size="small" placeholder="请输入邮箱地址"/>
</el-form-item>
<template v-else>
<el-form-item label="邮寄地址" prop="recipient_address">
<el-input v-model="form.recipient_address" size="small" placeholder="请输入邮寄地址"/>
</el-form-item>
<el-form-item label="收件人名称" prop="recipient_name">
<el-input v-model="form.recipient_name" size="small" placeholder="请输入收件人名称"/>
</el-form-item>
<el-form-item label="收件人手机号" prop="recipient_mobile">
<el-input v-model="form.recipient_mobile" size="small" placeholder="请输入收件人手机号"/>
</el-form-item>
<el-form-item label="票据跟进人" prop="follower">
<el-input v-model="form.follower" size="small" placeholder="请输入票据跟进人"/>
</el-form-item>
</template>
</div>
<el-form-item>
<el-button size="mini" @click="$emit('dialogClose')">取消</el-button>
<el-button type="primary" @click="handleSubmit" size="mini">提交</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
history: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
form: {
// type: 1,
taxpayer_type: '2',
invoice_type: '',
taxpayer_name: '',
taxpayer_identifier: '',
remarks: '',
recipient_email: '',
recipient_address: '',
recipient_name: '',
recipient_mobile: '',
follower: '',
taxpayer_address: '',
taxpayer_mobile: '',
taxpayer_bank_name: '',
taxpayer_bank_account: ''
},
rules: {
// type: { required: true, message: '请选择开票类型', trigger: 'change' },
taxpayer_type: { required: true, message: '请选择开票人类型', trigger: 'change' },
invoice_type: { required: true, message: '请选择发票类型', trigger: 'change' },
taxpayer_name: { required: true, message: '请输入纳税人名称', trigger: 'blur' },
taxpayer_identifier: { required: true, message: '请输入纳税人识别号', trigger: 'blur' },
recipient_email: { required: true, message: '请输入邮箱地址', trigger: 'blur' },
recipient_name: { required: true, message: '请输入收件人名称', trigger: 'blur' },
recipient_address: { required: true, message: '请输入邮寄地址', trigger: 'blur' },
recipient_mobile: { required: true, message: '请输入收件人手机号', trigger: 'blur' }
}
}
},
watch: {
history: {
handler(nv) {
if (nv) {
console.log(nv)
Object.keys(this.form).forEach(key => {
this.form[key] = this.history[key]
})
}
}
}
},
methods: {
handleSubmit() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.$emit('submit', this.form)
}
})
}
}
}
</script>
<style scoped>
.el-form{
padding-top:20px;
}
.form-item-border{
border:1px solid #eee;
margin:0 20px 20px;
}
.form-content{
border:1px solid #eee;
margin:0 20px 14px;
padding-right:20px;
}
.form-content h5{
font-size:14px;
line-height:40px;
color:#666;
text-indent:20px;
padding-top:10px;
}
</style>
// import AppLayout from '@/components/layout/index.vue'
const routes = [
{
path: '/payment',
......
<template>
<div>开具发票</div>
<div class="invoice-container">
<van-tabs v-model="form.taxpayer_type" type="card" color="#3276fc" sticky>
<van-tab name="2" title="企业"></van-tab>
<van-tab name="1" title="个人"></van-tab>
<van-tab name="3" title="非营利性单位"></van-tab>
</van-tabs>
<van-form :show-error="false" @submit="fetchCreateInvoice">
<div class="form-group">
<h5>发票信息</h5>
<van-field readonly clickable name="picker" :value="invoice_type_label" label="发票类型" placeholder="请选择发票类型" @click="showPicker = true" :rules="[{ required: true, message: '请选择发票类型', trigger: 'onChange' }]" required />
<van-field v-model="form.taxpayer_name" name="pattern" label="纳税人名称" placeholder="请输入纳税人名称" :rules="[{ required: true, message: '请输入纳税人名称' }]" required right-icon="apps-o" @click-right-icon="handleHistory"/>
<van-field v-model="form.taxpayer_identifier" name="pattern" label="纳税人识别号" placeholder="请输入纳税人识别号" :rules="[{ required: true, message: '请输入纳税人识别号' }]" required/>
<template v-if="form.invoice_type === '3'">
<van-field v-model="form.taxpayer_address" name="pattern" label="地址" placeholder="请输入地址" :rules="[{ required: true, message: '请输入地址' }]" required/>
<van-field v-model="form.taxpayer_mobile" type="tel" name="pattern" label="电话" placeholder="请输入电话" :rules="[{ required: true, message: '请输入电话' }]" required/>
<van-field v-model="form.taxpayer_bank_name" name="pattern" label="开户行" placeholder="请输入开户行" :rules="[{ required: true, message: '请输入开户行' }]" required/>
<van-field v-model="form.taxpayer_bank_account" name="pattern" label="银行账号" placeholder="请输入银行账号" :rules="[{ required: true, message: '请输入银行账号' }]" required/>
</template>
<van-field v-model="form.remarks" name="pattern" label="备注" placeholder="请输入备注" />
</div>
<div class="form-group">
<h5>收票信息</h5>
<van-field v-if="form.invoice_type === '2'" v-model="form.recipient_email" name="pattern" label="邮箱地址" placeholder="请输入邮箱地址" :rules="[{ required: true, message: '请输入邮箱地址' }]" required/>
<template v-else>
<van-field v-model="form.recipient_address" name="pattern" label="邮寄地址" placeholder="请输入邮寄地址" :rules="[{ required: true, message: '请输入邮寄地址' }]" required/>
<van-field v-model="form.recipient_name" name="pattern" label="收件人名称" placeholder="请输入收件人名称" :rules="[{ required: true, message: '请输入收件人名称' }]" required/>
<van-field v-model="form.recipient_mobile" type="tel" name="pattern" label="收件人手机号" placeholder="请输入收件人手机号" :rules="[{ required: true, message: '请输入收件人手机号' }]" required/>
<van-field v-model="form.follower" name="pattern" label="票据跟进人" placeholder="请输入票据跟进人" :rules="[{ required: true, message: '请输入票据跟进人' }]" required/>
</template>
</div>
<div style="margin: 16px;">
<van-button round block type="info" native-type="submit" size="small">确认提交</van-button>
</div>
</van-form>
<van-popup v-model="showPicker" position="bottom">
<van-picker
show-toolbar
:columns="invoiceTypeList"
value-key="label"
@confirm="onConfirm"
@cancel="showPicker = false"
/>
</van-popup>
<van-popup v-model="showPopup" position="bottom" :style="{ height: '60%' }">
<p class="popup-title van-hairline--bottom"><span>选择历史开票信息</span><van-icon name="cross" @click="showPopup = false" /></p>
<history-invoice-list v-if="showPopup" @select="handleSelect"/>
</van-popup>
</div>
</template>
<script>
export default {}
import HistoryInvoiceList from '../../components/HistoryInvoiceList.vue'
import { createInvoice } from '../../api'
export default {
components: { HistoryInvoiceList },
data() {
return {
invoice_type_label: '',
form: {
taxpayer_type: '2',
invoice_type: '',
taxpayer_name: '',
taxpayer_identifier: '',
remarks: '',
recipient_email: '',
recipient_address: '',
recipient_name: '',
recipient_mobile: '',
follower: '',
taxpayer_address: '',
taxpayer_mobile: '',
taxpayer_bank_name: '',
taxpayer_bank_account: ''
},
showPicker: false,
showPopup: false
}
},
computed: {
invoiceTypeList() {
const list = [
{ label: '普通发票(纸质)', value: '1' },
{ label: '专用发票(纸质)', value: '3' },
{ label: '普通发票(电子)', value: '2' }
]
if (this.form.taxpayer_type !== '2') {
list.splice(1, 1)
}
return list
},
id() {
const query = this.$route.query
return query.id
}
},
methods: {
onConfirm(val) {
this.invoice_type_label = val.label
this.form.invoice_type = val.value
this.showPicker = false
},
handleHistory() {
this.showPopup = true
},
handleSelect(val) {
Object.keys(this.form).forEach(key => {
this.form[key] = val[key]
})
if (this.form.invoice_type) {
const item = this.invoiceTypeList.find(item => item.value === this.form.invoice_type)
this.invoice_type_label = item.label
}
this.showPopup = false
},
fetchCreateInvoice() {
const params = Object.assign({}, this.form)
params.id = this.id
createInvoice(params).then(res => {
if (res.code === 0) {
this.$message.success('开具发票成功')
} else {
this.$message.error(res.message || '开具发票失败')
}
}).catch(() => {
this.$message.error('开具发票失败')
})
}
}
}
</script>
<style></style>
<style scoped>
.invoice-container{
padding-top:20px;
}
.form-group{
margin:10px 15px 0;
}
.form-group h5{
font-size:16px;
line-height:36px;
font-weight:normal;
color:#aaa;
}
.popup-title{
font-size:16px;
display:flex;
height: 44px;
line-height:44px;
}
.popup-title span{
flex:1;
text-indent:20px;
}
.popup-title i{
height:44px;
line-height:44px;
padding:0 14px;
}
</style>
<template>
<div>支付记录</div>
<div class="h5-pay-list">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model="loading"
class="pay-list"
:finished="finished"
finished-text="没有更多了"
@load="fetchList"
>
<div class="pay-item" v-for="(item, index) in list" :key="index">
<div class="left">
<h5 class="project">{{item.project_name}}</h5>
<p class="status">{{item.invoice_status | statusFilter}}</p>
<p class="status">{{item.final_payment_time}}</p>
</div>
<div class="right">
<p><span></span>{{item.amount_received}}</p>
</div>
<van-button v-if="['2', '6'].includes(item.invoice_status)" class="invoice-btn" plain round type="primary" size="mini" color="#C01540" @click.stop="handleInvoice(item)">开具发票</van-button>
</div>
<template slot="finished">{{list.length > 8 ? '没有更多了': ''}}</template>
</van-list>
</van-pull-refresh>
<van-empty v-if="list.length === 0" description="暂无记录" />
</div>
</template>
<script>
export default {}
import { getPayList } from '../../api'
const statusMap = {
1: '不可开具',
2: '未开具',
3: '开具中',
4: '已开具',
5: '开票失败',
6: '已作废'
}
export default {
data() {
return {
list: [],
loading: false,
finished: false,
refreshing: false,
page_size: 10,
page: 1,
total: 0,
index: 1
}
},
filters: {
statusFilter(val) {
return statusMap[val]
}
},
methods: {
handleInvoice(val) {
this.$router.push({ path: '/h5/payment/invoice', query: { id: val.id } })
},
initParams() {
this.list = []
this.loading = true
this.finished = false
// this.refreshing = false
this.page_size = 10
this.page = 1
this.total = 0
},
onRefresh() {
this.initParams()
this.finished = true // 防止下拉刷新时触发上拉加载
this.fetchList()
},
fetchList() {
const params = {
page_size: this.page_size,
page: this.page
}
getPayList(params).then(res => {
if (this.refreshing) {
this.list = []
this.finished = false
this.refreshing = false
}
if (res.code === 0 && res.data && res.data.list) {
this.list = this.list.concat(res.data.list)
this.total = res.data.total
if (this.page * this.page_size >= this.total) {
this.finished = true
} else {
this.page++
}
} else {
this.list = []
this.finished = true
}
this.loading = false
})
}
}
}
</script>
<style></style>
<style scoped lang="scss">
.pay-item{
background:#fff;
border-bottom:1px solid #f0f0f0;
display:flex;
position:relative;
padding:0 12px;
.left{
width:65%;
.project{
font-size:16px;
line-height:32px;
color: #464646;
font-weight:400;
}
p{
font-size:13px;
line-height:26px;
color:#999;
}
}
.right{
width:35%;
p{
font-size:18px;
line-height:36px;
text-align: right;
span{
font-size:14px;
color:#C01540;
}
}
}
.invoice-btn{
position:absolute;
right:12px;
bottom: 5px;
padding:0 15px;
}
}
</style>
<template>
<div>支付记录</div>
<div class="pc-pay-list">
<app-list v-bind="tableOptions" ref="appList" @selection-change="handleSelectionChange">
<template #footer>
<div class="selection_bar"></div>
</template>
<!-- 操作 -->
<template v-slot:table-operate="{ row }">
<el-button v-if="['2', '6'].includes(row.invoice_status)" type="text" @click="handleInvoice(row)" size="mini">开具发票</el-button>
<el-button type="text" @click="handleInvoiceQR(row)" size="mini">开票二维码</el-button>
</template>
</app-list>
<el-drawer title="开具发票" :visible.sync="drawerVisible" :close-on-click-modal="false" :destroy-on-close="true" size="600px" top="15px" @close="handleClose">
<invoice-form :history="history" @history="handleHistory" @dialogClose="drawerVisible = false" @submit="fetchCreateInvoice"/>
<el-dialog title="选择历史开票信息" :visible.sync="dialogVisible" width="460px" top="15px" append-to-body :destroy-on-close="true" center>
<history-invoice-list v-if="dialogVisible" :hasSearch="true" @select="handleSelect"/>
</el-dialog>
</el-drawer>
<share-qrcode :visible.sync="shareDialogVisible" :value="shareUrl" />
</div>
</template>
<script>
export default {}
import AppList from '@/components/base/AppList.vue'
import InvoiceForm from '../../components/InvoiceForm.vue'
import HistoryInvoiceList from '../../components/HistoryInvoiceList.vue'
import ShareQrcode from '@/components/base/ShareQrcode.vue'
import { getPayList, createInvoice } from '../../api'
const statusMap = {
1: '不可开具',
2: '未开具',
3: '开具中',
4: '已开具',
5: '开票失败',
6: '已作废'
}
export default {
components: { AppList, InvoiceForm, HistoryInvoiceList, ShareQrcode },
data() {
return {
multipleSelection: [],
drawerVisible: false,
dialogVisible: false,
payment_id: '',
history: {},
shareDialogVisible: false,
shareUrl: ''
}
},
computed: {
tableOptions() {
return {
remote: {
httpRequest: getPayList,
params: { sso_id: '' }
},
filters: [],
columns: [
{ type: 'selection', minWidth: '40px' },
{ prop: 'project_name', label: '项目', minWidth: '120px' },
{ prop: 'bill_total', label: '应缴金额', minWidth: '80px' },
{ prop: 'amount_received', label: '实缴金额', minWidth: '80px' },
{ prop: 'final_payment_time', label: '缴费时间', minWidth: '150px' },
{
prop: 'invoice_status',
label: '发票状态',
minWidth: '100px',
computed({ row }) {
return statusMap[row.invoice_status]
}
},
{ prop: 'invoice_application_time', label: '发票申请时间', minWidth: '150px' },
{ prop: 'invoice_remark', label: '备注', minWidth: '140px' },
{ label: '操作', minWidth: '140px', slots: 'table-operate' }
]
}
}
},
methods: {
handleInvoice(row) {
this.drawerVisible = true
this.payment_id = row.id
},
handleInvoiceQR(row) {
this.shareUrl = `${import.meta.env.VITE_SHARE_URL}?id=${row.id}`
this.shareDialogVisible = true
},
handleSelectionChange() {},
handleClose() {},
handleHistory() {
this.dialogVisible = true
},
handleSelect(val) {
this.history = val
this.dialogVisible = false
},
fetchCreateInvoice(val) {
val.payment_id = this.payment_id
createInvoice(val).then(res => {
if (res.code === 0) {
this.$message.success('开具发票成功')
} else {
this.$message.error(res.message || '开具发票失败')
}
}).catch(() => {
this.$message.error('开具发票失败')
})
}
}
}
</script>
<style></style>
<style scoped>
.pc-pay-list{
padding:14px;
background:#f7f7f7;
box-sizing:border-box;
height:100vh;
}
.table-list{
background:#fff;
border-radius:6px;
}
</style>
const getters = {
user: state => state.user.user,
roles: state => state.user.roles,
sidebar: state => state.app.sidebar
}
export default getters
import Vue from 'vue'
import Vuex from 'vuex'
import { getUser, logout } from '@/api/base'
import app from './modules/app'
import user from './modules/user'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {}
const store = new Vuex.Store({
namespaced: true,
modules: {
app,
user
},
mutations: {
setUser(state, user) {
state.user = user
}
},
actions: {
// 获取用户信息
getUser({ commit }) {
getUser().then(response => {
commit('setUser', response)
})
},
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
return response
})
},
async checkLogin({ commit }) {
const isLogin = await getUser()
.then(response => {
if (response.code === 0) {
commit('setUser', response.data)
return true
} else {
commit('setUser', {})
return false
}
})
.catch(() => {
commit('setUser', {})
return false
})
return isLogin
}
}
getters
})
export default store
const sidebarStatus = window.localStorage.getItem('sidebarStatus')
const state = {
sidebar: {
opened: sidebarStatus ? !!+sidebarStatus : true,
withoutAnimation: false
},
typeList: []
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
window.localStorage.setItem('sidebarStatus', 1)
} else {
window.localStorage.setItem('sidebarStatus', 0)
}
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { getUser, logout } from '@/api/base'
const user = {
state: {
user: {},
isLogin: false
},
mutations: {
setUser(state, user) {
state.user = user
},
setIsLogin(state, isLogin) {
state.isLogin = isLogin
}
},
actions: {
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
commit('setIsLogin', false)
// resetRouter()
return response
})
},
// 检测登录状态
async checkLogin({ commit }) {
const isLogin = await getUser()
.then(response => {
commit('setUser', response.data)
return true
})
.catch(() => {
commit('setUser', {})
return false
})
commit('setIsLogin', isLogin)
return isLogin
}
}
}
export default user
......@@ -4,7 +4,8 @@ import { Message } from 'element-ui'
const httpRequest = axios.create({
timeout: 60000,
withCredentials: true
withCredentials: true,
headers: { 'Content-Type': 'application/x-www-form-urlencoded', apikey: 'McS1WsnMwMhsfc7cJ7Y0' }
})
// 请求拦截
......
import store from '@/store'
export default async function (to, from, next) {
if (to.meta.requiredLogin) {
const isLogin = store.state.user.id || (await store.dispatch('checkLogin'))
if (!isLogin) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
}
} else {
next()
const isLogin = store.state.user.id || (await store.dispatch('checkLogin'))
if (!isLogin) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
}
next()
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论