第二次更新

This commit is contained in:
2026-09-10 20:56:46 +08:00
parent 4d6a7e7aac
commit caeb4d6760
310 changed files with 2427 additions and 382 deletions
+65
View File
@@ -0,0 +1,65 @@
import { axios } from '@/utils/request'
// api接口列表
const api = {
list: '/qyt.enterprise/list',
options: '/qyt.enterprise/options',
detail: '/qyt.enterprise/detail',
add: '/qyt.enterprise/add',
edit: '/qyt.enterprise/edit',
delete: '/qyt.enterprise/delete',
setDefault: '/qyt.enterprise/setDefault',
apply: '/qyt.enterprise/apply',
syncAccount: '/qyt.enterprise/syncAccount',
syncList: '/qyt.enterprise/syncList'
}
// 企业列表
export function list (params) {
return axios({ url: api.list, method: 'get', params })
}
// 启用中的企业(下拉选择)
export function options (params) {
return axios({ url: api.options, method: 'get', params })
}
// 企业详情
export function detail (params) {
return axios({ url: api.detail, method: 'get', params })
}
// 新增企业
export function add (data) {
return axios({ url: api.add, method: 'post', data })
}
// 编辑企业
export function edit (data) {
return axios({ url: api.edit, method: 'post', data })
}
// 删除企业
export function deleted (data) {
return axios({ url: api.delete, method: 'post', data })
}
// 设为默认企业
export function setDefault (data) {
return axios({ url: api.setDefault, method: 'post', data })
}
// 7.2 提交企油通开户申请
export function apply (data) {
return axios({ url: api.apply, method: 'post', data })
}
// 7.5 同步企业账户信息
export function syncAccount (data) {
return axios({ url: api.syncAccount, method: 'post', data })
}
// 7.22 从企油通拉取本 appId 名下企业
export function syncList (data) {
return axios({ url: api.syncList, method: 'post', data })
}
+10
View File
@@ -813,6 +813,16 @@ export const asyncRouterMap = [
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/Trade'),
meta: { title: '参数设置', keepAlive: false, permission: ['/setting/trade'] }
},
{
path: '/qyt/enterprise/index',
component: () => import(/* webpackChunkName: "setting" */ '@/views/qyt/enterprise/Index'),
meta: { title: '企业管理', keepAlive: false, permission: ['/qyt/enterprise/index'] }
},
{
path: '/setting/qyt',
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/Qyt'),
meta: { title: '企油通对接', keepAlive: false, permission: ['/setting/qyt'] }
},
{
path: '/setting/promotion',
component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/Promotion'),
+30 -1
View File
@@ -39,6 +39,24 @@
v-decorator="['callback', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="企油通企业" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
placeholder="不选则使用默认企业"
allowClear
v-decorator="['qyt_enterprise_id']"
>
<a-select-option
v-for="item in enterpriseList"
:key="item.enterprise_id"
:value="item.enterprise_id"
>
{{ item.company_name }}({{ item.passageway === 'pingan' ? '平安银行' : '支付宝企业码' }}){{ item.is_default ? ' · 默认' : '' }}
</a-select-option>
</a-select>
<div class="form-item-help">
<p class="extra">该合作商下单时使用哪个企业的对接参数与支付通道。留空则走默认企业。</p>
</div>
</a-form-item>
</div>
</div>
@@ -52,6 +70,7 @@
<script>
import * as PartnersApi from '@/api/partners'
import * as EnterpriseApi from '@/api/qyt/enterprise'
import PartnersModel from '@/common/model/partners/Index'
export default {
@@ -69,11 +88,14 @@ export default {
// 当前表单元素
form: this.$form.createForm(this),
// 表单数据
formData: PartnersModel.formData
formData: PartnersModel.formData,
// 企油通企业下拉
enterpriseList: []
}
},
// 初始化数据
created () {
this.getEnterpriseList()
this.isLoading = true
// 获取form所需的数据
PartnersModel.getFromData()
@@ -83,6 +105,13 @@ export default {
},
methods: {
// 获取企油通企业下拉(合作商下单时用哪个企业的参数)
getEnterpriseList () {
EnterpriseApi.options().then(result => {
this.enterpriseList = result.data.list
})
},
// 手动强制更新页面
onForceUpdate (bool = false) {
this.$forceUpdate()
+30 -1
View File
@@ -39,6 +39,24 @@
v-decorator="['callback', {rules: [{required: true, min: 2, message: '请输入至少2个字符'}]}]"
/>
</a-form-item>
<a-form-item label="企油通企业" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-select
placeholder="不选则使用默认企业"
allowClear
v-decorator="['qyt_enterprise_id']"
>
<a-select-option
v-for="item in enterpriseList"
:key="item.enterprise_id"
:value="item.enterprise_id"
>
{{ item.company_name }}({{ item.passageway === 'pingan' ? '平安银行' : '支付宝企业码' }}){{ item.is_default ? ' · 默认' : '' }}
</a-select-option>
</a-select>
<div class="form-item-help">
<p class="extra">该合作商下单时使用哪个企业的对接参数与支付通道。留空则走默认企业。</p>
</div>
</a-form-item>
</div>
</div>
@@ -52,6 +70,7 @@
<script>
import * as PartnersApi from '@/api/partners'
import * as EnterpriseApi from '@/api/qyt/enterprise'
import { InputNumberGroup } from '@/components'
import PartnersModel from '@/common/model/partners/Index'
import { isEmptyObject } from '@/utils/util'
@@ -76,10 +95,13 @@ export default {
// 合作商ID
partnersId: null,
// 表单数据
formData: PartnersModel.formData
formData: PartnersModel.formData,
// 企油通企业下拉
enterpriseList: []
}
},
created () {
this.getEnterpriseList()
// 初始化数据
this.initData()
},
@@ -89,6 +111,13 @@ export default {
},
methods: {
// 获取企油通企业下拉(合作商下单时用哪个企业的参数)
getEnterpriseList () {
EnterpriseApi.options().then(result => {
this.enterpriseList = result.data.list
})
},
// 初始化数据
initData () {
// 记录合作商ID
+3 -2
View File
@@ -28,6 +28,7 @@
<a-select v-decorator="['oil_status_zsy', { initialValue: -1 }]">
<a-select-option :value="-1">全部</a-select-option>
<a-select-option :value="0">未提交</a-select-option>
<a-select-option :value="1">发放中</a-select-option>
<a-select-option :value="2">发放成功</a-select-option>
<a-select-option :value="3">发放失败</a-select-option>
</a-select>
@@ -275,11 +276,11 @@ export default {
// 企油通发放态文案
qytText (v) {
return { 0: '未提交', 1: '处理中', 2: '发放成功', 3: '发放失败' }[v] || '未知'
return { 0: '未提交', 1: '发放中', 2: '发放成功', 3: '发放失败' }[v] || '未知'
},
// 企油通发放态颜色
qytColor (v) {
return { 2: 'green', 3: 'red' }[v] || ''
return { 1: 'blue', 2: 'green', 3: 'red' }[v] || ''
},
// 重新发放
@@ -0,0 +1,354 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-alert type="info" show-icon class="tips-alert">
<div slot="message">
一个接入方 appId 下可挂多个企业,业务接口靠<b>统一社会信用代码</b>区分。
下游下单时按「合作商绑定的企业 → 默认企业」的顺序决定走哪个企业的参数;
企业与通道在下单那一刻固化到订单上,之后改绑定不影响在途订单。
<b>appId 与 RSA 私钥属于接入方级别</b>,在「设置 → 企油通对接」和服务器 .env 里维护,不在本页。
</div>
</a-alert>
<div class="table-operator">
<a-button type="primary" icon="plus" @click="handleCreate">新增企业</a-button>
<a-button icon="cloud-download" :loading="isSyncing" @click="handleSyncList">从企油通拉取企业</a-button>
<a-form layout="inline" :form="searchForm" @submit="handleSearch" class="search-form">
<a-form-item label="企业">
<a-input v-decorator="['name']" placeholder="企业名称 / 信用代码" allowClear />
</a-form-item>
<a-form-item label="状态">
<a-select v-decorator="['status', { initialValue: 'all' }]" style="width: 130px">
<a-select-option value="all">全部</a-select-option>
<a-select-option v-for="(v, k) in StatusText" :key="k" :value="k">{{ v }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="通道">
<a-select v-decorator="['passageway', { initialValue: 'all' }]" style="width: 140px">
<a-select-option value="all">全部</a-select-option>
<a-select-option value="alipay">支付宝企业码</a-select-option>
<a-select-option value="pingan">平安银行</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">搜索</a-button>
</a-form-item>
</a-form>
</div>
<s-table ref="table" rowKey="enterprise_id" :columns="columns" :data="loadData">
<template slot="company" slot-scope="item">
<div>
<b>{{ item.company_name }}</b>
<a-tag v-if="item.is_default" color="green" class="ml-6">默认</a-tag>
<a-tag v-if="!item.is_enable" class="ml-6">已停用</a-tag>
</div>
<div class="sub">{{ item.business_license }}</div>
<div v-if="item.enterprise_number" class="sub">企业编号:{{ item.enterprise_number }}</div>
</template>
<template slot="passageway" slot-scope="item">
<a-tag :color="item.passageway === 'pingan' ? 'blue' : 'orange'">
{{ item.passageway === 'pingan' ? '平安银行' : '支付宝企业码' }}
</a-tag>
<div v-if="item.passageway === 'pingan'" class="sub">要求实名,下单须传 idCard</div>
<div v-else class="sub">
机构号:<span :class="{ 'text-red': !item.institution_id }">{{ item.institution_id || '未填,无法下发' }}</span>
</div>
<div v-if="item.bound_passageway" class="sub">已开通通道:{{ item.bound_passageway }}</div>
</template>
<template slot="status" slot-scope="item">
<a-badge :status="statusBadge(item.status)" :text="StatusText[item.status] || item.status" />
<div v-if="item.account" class="sub">{{ item.account_bank }} {{ item.account }}</div>
<div v-if="item.sync_text" class="sub">{{ item.sync_text }}</div>
</template>
<template slot="action" slot-scope="item">
<a @click="handleUpdate(item)">编辑</a>
<a-divider type="vertical" />
<a @click="handleApply(item)">提交开户</a>
<a-divider type="vertical" />
<a @click="handleSyncAccount(item)">同步账户</a>
<a-divider type="vertical" />
<a v-if="!item.is_default" @click="handleSetDefault(item)">设为默认</a>
<span v-else class="text-grey">设为默认</span>
<a-divider type="vertical" />
<a class="text-red" @click="handleDelete(item)">删除</a>
</template>
</s-table>
<a-modal
:title="record.enterprise_id ? '编辑企业' : '新增企业'"
:visible="visible"
:confirmLoading="isSaving"
:width="640"
@ok="handleSubmit"
@cancel="visible = false"
>
<a-form :form="form">
<a-form-item label="企业名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['company_name', { rules: [{ required: true, message: '请填写企业名称' }] }]" />
</a-form-item>
<a-form-item label="统一社会信用代码" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input
placeholder="18 位,企油通的 business_license"
v-decorator="['business_license', { rules: [{ required: true, message: '请填写统一社会信用代码' }] }]"
/>
<div class="form-item-help"><p class="extra">几乎所有企油通接口都会校验它是否归属当前 appId,不属于会直接 403。</p></div>
</a-form-item>
<a-form-item label="营业执照照片地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="https://xxx.png" v-decorator="['business_license_url']" />
<div class="form-item-help"><p class="extra">7.2 提交开户申请时必填。</p></div>
</a-form-item>
<a-form-item label="联系人" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input-group compact>
<a-input style="width: 40%" placeholder="姓名" v-decorator="['contact_name']" />
<a-input style="width: 60%" placeholder="手机号" v-decorator="['contact_mobile']" />
</a-input-group>
</a-form-item>
<a-form-item label="支付通道" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-radio-group v-decorator="['passageway', { initialValue: 'alipay' }]">
<a-radio-button value="alipay">支付宝企业码</a-radio-button>
<a-radio-button value="pingan">平安银行</a-radio-button>
</a-radio-group>
<div class="form-item-help">
<p class="extra">平安银行通道要求实名,下单接口的 idCard(司机身份证号)变为必填。</p>
</div>
</a-form-item>
<a-form-item label="机构号" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="institution_id" v-decorator="['institution_id']" />
<div class="form-item-help"><p class="extra">支付宝企业码通道必填,不填每一单都会下发失败并原路退回余额。</p></div>
</a-form-item>
<a-form-item label="已开通通道" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="如 1,2" v-decorator="['bound_passageway']" />
<div class="form-item-help"><p class="extra">企油通侧已绑定的通道编码,逗号分隔(1 支付宝企业码 / 2 平安银行)。提交开户申请时按此申请。</p></div>
</a-form-item>
<a-form-item label="编号前缀" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input style="width: 160px" placeholder="留空用全局默认" v-decorator="['no_prefix']" />
<div class="form-item-help">
<p class="extra">batch_no / waybill_no 是<b>全平台唯一</b>而非接入方内唯一,各企业建议用不同前缀,避免撞号。</p>
</div>
</a-form-item>
<a-form-item label="凭证名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input style="width: 160px" placeholder="留空用全局默认" v-decorator="['voucher_name']" />
</a-form-item>
<a-form-item label="appId 覆盖" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="留空用全局 appId" v-decorator="['app_id']" />
<div class="form-item-help">
<p class="extra">仅当该企业使用独立的接入方账号时才填。注意私钥仍在 .env,两者必须配套。</p>
</div>
</a-form-item>
<a-form-item label="设为默认" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-switch v-decorator="['is_default', { valuePropName: 'checked' }]" />
<span class="ml-6 text-grey">合作商未绑定企业时使用该企业</span>
</a-form-item>
<a-form-item label="启用" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-switch v-decorator="['is_enable', { valuePropName: 'checked', initialValue: true }]" />
</a-form-item>
<a-form-item label="备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['remark']" />
</a-form-item>
</a-form>
</a-modal>
</a-card>
</template>
<script>
import * as Api from '@/api/qyt/enterprise'
import { STable } from '@/components/Table'
const StatusText = {
pending: '待完善',
submitted: '待审核',
approved: '审核通过',
rejected: '审核拒绝',
frozen: '已冻结'
}
export default {
name: 'QytEnterpriseIndex',
components: { STable },
data () {
return {
StatusText,
searchForm: this.$form.createForm(this),
form: this.$form.createForm(this),
labelCol: { span: 6 },
wrapperCol: { span: 16 },
queryParam: {},
visible: false,
isSaving: false,
isSyncing: false,
record: {},
columns: [
{ title: 'ID', dataIndex: 'enterprise_id', width: 70 },
{ title: '企业', scopedSlots: { customRender: 'company' } },
{ title: '通道与机构号', scopedSlots: { customRender: 'passageway' } },
{ title: '企油通状态', scopedSlots: { customRender: 'status' } },
{ title: '操作', width: 260, scopedSlots: { customRender: 'action' } }
],
loadData: param => {
return Api.list({ ...param, ...this.queryParam }).then(result => result.data.list)
}
}
},
methods: {
statusBadge (status) {
if (status === 'approved') return 'success'
if (status === 'rejected' || status === 'frozen') return 'error'
if (status === 'submitted') return 'processing'
return 'default'
},
handleSearch (e) {
e.preventDefault()
this.searchForm.validateFields((errors, values) => {
if (errors) return
this.queryParam = { ...this.queryParam, ...values }
this.refresh()
})
},
handleCreate () {
this.record = {}
this.visible = true
this.$nextTick(() => {
this.form.resetFields()
})
},
handleUpdate (item) {
this.record = item
this.visible = true
this.$nextTick(() => {
this.form.setFieldsValue({
company_name: item.company_name,
business_license: item.business_license,
business_license_url: item.business_license_url,
contact_name: item.contact_name,
contact_mobile: item.contact_mobile,
passageway: item.passageway,
institution_id: item.institution_id,
bound_passageway: item.bound_passageway,
no_prefix: item.no_prefix,
voucher_name: item.voucher_name,
app_id: item.app_id,
is_default: !!item.is_default,
is_enable: !!item.is_enable,
remark: item.remark
})
})
},
handleSubmit () {
this.form.validateFields((errors, values) => {
if (errors) return
const form = {
...values,
is_default: values.is_default ? 1 : 0,
is_enable: values.is_enable ? 1 : 0
}
this.isSaving = true
const request = this.record.enterprise_id
? Api.edit({ enterpriseId: this.record.enterprise_id, form })
: Api.add({ form })
request
.then(result => {
this.$message.success(result.message, 1.5)
this.visible = false
this.refresh()
})
.finally(() => {
this.isSaving = false
})
})
},
handleSetDefault (item) {
Api.setDefault({ enterpriseId: item.enterprise_id }).then(result => {
this.$message.success(result.message, 1.5)
this.refresh()
})
},
handleDelete (item) {
this.$confirm({
title: '确认删除企业「' + item.company_name + '」?',
content: '已产生订单或已被合作商绑定的企业无法删除,可改为停用。',
onOk: () => {
return Api.deleted({ enterpriseId: item.enterprise_id }).then(result => {
this.$message.success(result.message, 1.5)
this.refresh()
})
}
})
},
handleApply (item) {
this.$confirm({
title: '提交开户申请到企油通?',
content: '将调用 7.2 企业申请创建,提交后由企油通审核。',
onOk: () => {
return Api.apply({ enterpriseId: item.enterprise_id }).then(result => {
this.$message.success(result.message, 2)
this.refresh()
})
}
})
},
handleSyncAccount (item) {
Api.syncAccount({ enterpriseId: item.enterprise_id }).then(result => {
this.$message.success(result.message, 1.5)
this.refresh()
})
},
handleSyncList () {
this.isSyncing = true
Api.syncList({})
.then(result => {
this.$message.success(result.message, 2)
this.refresh()
})
.finally(() => {
this.isSyncing = false
})
},
refresh () {
this.$refs.table && this.$refs.table.refresh()
}
}
}
</script>
<style lang="less" scoped>
.tips-alert {
margin-bottom: 20px;
}
.search-form {
display: inline-block;
margin-left: 16px;
}
.sub {
color: #999;
font-size: 12px;
line-height: 1.7;
}
.ml-6 {
margin-left: 6px;
}
.text-red {
color: #ff4d4f;
}
.text-grey {
color: #bbb;
}
</style>
+180
View File
@@ -0,0 +1,180 @@
<template>
<a-card :bordered="false">
<div class="card-title">{{ $route.meta.title }}</div>
<a-spin :spinning="isLoading">
<a-alert type="warning" show-icon class="tips-alert">
<div slot="message">
出于安全考虑,<b>RSA 私钥与企油通平台公钥不在此处配置</b>,仍只从服务器的
<a-tag>.env</a-tag>文件读取。数据库备份、后台账号一旦外泄,也拿不到签名私钥。
</div>
</a-alert>
<a-alert type="info" show-icon class="tips-alert">
<div slot="message">
本页只放<b>接入方级</b>参数。企业的统一社会信用代码、<b>支付通道开关</b>、机构号属于<b>企业级</b>参数,
请到 <router-link to="/qyt/enterprise/index">设置 → 企业管理</router-link> 里按企业维护。
</div>
</a-alert>
<a-form :form="form" @submit="handleSubmit">
<a-divider orientation="left">接口环境</a-divider>
<a-form-item label="接口域名" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="https://xxx.worknoya.vip" v-decorator="['base_url']" />
<div class="form-item-help">
<p class="extra">不要以斜杠结尾。测试与正式环境地址不同。</p>
</div>
</a-form-item>
<a-form-item label="业务 appId" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['app_id']" />
</a-form-item>
<a-divider orientation="left">业务参数默认值</a-divider>
<a-form-item label="编号前缀" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input class="input-short" placeholder="HM" v-decorator="['no_prefix']" />
<div class="form-item-help">
<p class="extra">批次号/运单号的全平台唯一前缀,防止与其他接入方撞号。正式建议 HM,测试建议 HMTEST。</p>
<p class="extra">各企业可在企业管理里单独覆盖;已生成编号的订单沿用原编号,改前缀不影响在途订单。</p>
</div>
</a-form-item>
<a-form-item label="凭证名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input class="input-short" placeholder="运单卡" v-decorator="['voucher_name']" />
<div class="form-item-help"><p class="extra">各企业可在企业管理里单独覆盖。</p></div>
</a-form-item>
<a-divider orientation="left">H5 与回调</a-divider>
<a-form-item label="H5 落地页地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="https://paytaskpage.worknoya.vip/#/" v-decorator="['h5_base_url']" />
<div class="form-item-help">
<p class="extra">司机免登加油页面,后台「获取加油地址」生成的链接以此为前缀。</p>
</div>
</a-form-item>
<a-form-item label="H5 免登 appId" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input v-decorator="['h5_app_id']" />
<div class="form-item-help">
<p class="extra">可能与业务 appId 不同,需线下向对接人确认。</p>
</div>
</a-form-item>
<a-form-item label="消费回调地址" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-input placeholder="https://你的域名/qytconsume.php" v-decorator="['consume_notify_url']" />
<div class="form-item-help">
<p class="extra">此处仅作记录留档,实际生效需在企油通后台配置同一地址。</p>
</div>
</a-form-item>
<a-divider orientation="left">其他</a-divider>
<a-form-item label="详细日志" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-switch checked-children="开" un-checked-children="关" v-decorator="['log', { valuePropName: 'checked' }]" />
<div class="form-item-help">
<p class="extra">记录请求与响应报文到 runtime/storelog,手机号与身份证号已脱敏。联调期建议开启。</p>
</div>
</a-form-item>
<a-form-item :wrapperCol="{ span: 10, offset: 5 }">
<a-button type="primary" html-type="submit" :loading="isLoading">提交</a-button>
</a-form-item>
</a-form>
</a-spin>
</a-card>
</template>
<script>
import * as Api from '@/api/setting/store'
import { isEmpty } from '@/utils/util'
export default {
data () {
return {
// 当前设置项的key
key: 'qyt',
// 标签布局属性
labelCol: { span: 5 },
// 输入框布局属性
wrapperCol: { span: 10 },
// loading状态
isLoading: false,
// 当前表单元素
form: this.$form.createForm(this),
// 当前记录详情
record: {}
}
},
// 初始化数据
created () {
this.getDetail()
},
methods: {
// 获取当前详情记录
getDetail () {
this.isLoading = true
Api.detail(this.key)
.then(result => {
this.record = result.data.values
this.setFieldsValue()
})
.finally(() => {
this.isLoading = false
})
},
// 设置默认值
setFieldsValue () {
const { record, $nextTick, form } = this
!isEmpty(form.getFieldsValue()) && $nextTick(() => {
form.setFieldsValue({
'base_url': record.base_url,
'app_id': record.app_id,
'no_prefix': record.no_prefix,
'voucher_name': record.voucher_name,
'h5_base_url': record.h5_base_url,
'h5_app_id': record.h5_app_id,
'consume_notify_url': record.consume_notify_url,
'log': !!Number(record.log)
})
})
},
// 确认按钮
handleSubmit (e) {
e.preventDefault()
const { form: { validateFields } } = this
validateFields((errors, values) => {
!errors && this.onFormSubmit(values)
})
},
// 提交到后端api
onFormSubmit (values) {
this.isLoading = true
const form = { ...values, log: values.log ? 1 : 0 }
Api.update(this.key, { form })
.then(result => {
this.$message.success(result.message, 1.5)
})
.finally(() => {
this.isLoading = false
})
}
}
}
</script>
<style lang="less" scoped>
/deep/.ant-form-item-control {
padding-left: 10px;
.ant-form-item-control {
padding-left: 0;
}
}
.tips-alert {
margin-bottom: 28px;
}
.input-short {
width: 160px;
}
.input-code {
width: 30%;
}
.input-inst {
width: 70%;
}
</style>
+1 -1
View File
@@ -1 +1 @@
APP_DEBUG = false␍␍[APP]␍DEFAULT_TIMEZONE = Asia/Shanghai␍␍[DATABASE]␍TYPE = mysql␍HOSTNAME = 127.0.0.1␍DATABASE = oil␍USERNAME = oil␍PASSWORD = AxSZEJDp5nePyfMa␍HOSTPORT = 3306␍CHARSET = utf8␍DEBUG = true␍␍[CACHE]␍DRIVER = file␍␍[REDIS]␍HOSTNAME = 127.0.0.1␍HOSTPORT = 6379␍PASSWORD =␍SELECT = 0␍␍[LANG]␍default_lang = zh-cn␍␍; ===== 企油通对接配置(值不要加引号;RSA 密钥压成一行 base64,去掉 BEGIN/END 与换行)=====␍[QYT]␍; 接口域名,不含结尾斜杠,向企油通对接人索取(测试/正式不同)␍BASE_URL = http://dev.pbpayapi.worknoya.work␍; 平台分配的 appId(业务接口用)␍APP_ID = 37257338026e7a377165454689773882␍; 我方 RSA 私钥(一行 base64,无头尾无换行)␍PRIVATE_KEY = MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpi86NYP5sVj2FcLvSk3o6mmWAgEW4TiuMtzDqld1QjaKAVaEbbGN93ZvSaafawPeBMI5qi04NvFHnPlGuQiGxJ5ddzaE4P5/xAsd2NnUFY30o71n3Tq30jYQdblTa7xr9nAhZiBrd2CtKWfvnoU/RxsJORUhiJhKtTVjNQ6N8UICDyUVzlhIqKtishMLYDSIhRnONtrQuKnXDPAoaTxbxEHLHXbic7WPwtOUNln/7oKuLdGJyKn2ikFv1YBNzwVpaLRsu+zF617qE5HWVf5GtEs/IuD+p1t4p6Dnm0Bq1fChkqz3pQejiyuNzhZt3zJZWCz3aPOxKLXBhfm+V29yZAgMBAAECggEABRJmGOucYNBJodrH+5omz2b7/1aW/1bzTY/TWkkmaDj8cftdyOy6ozLEbQx1FYGtRcOWlxBYwudtyc2e8ugOivakvA0hNAYPOk37eolRgdpCjb1vQwsrAARVve2lgzEg7RJEW61LJHTJgr1jYD/+m8dm26GN80WxTFbif6100Ayp++qxP7/EAwsD+Nl9wNo1itSHIiwzt4ohCWgxoSgVwkoUdMVM3qERzBXzjQAzK/A8DuJDyNayu74YVBrxXGRbzeeOippu7d2dY6bHwyDah7ki5IW9MRmMH/d24sayb+kYYKXI9d+G9jqXlL9ZASb4/OPQ2s7WPftDQhIvpXJdwQKBgQDUmkdl+viy13PtmV00AfzDaOeOTOknzfW7xjylnYR63pI8dnMLTN+RIXYgWMbehrLjMwxhWjekOPKA9HZ2g1wI7sxYsHWBiq6StYOcS7RlGayyvFstbbUR3X5Ff7y/3+BmYzrzZKepwJ8bCLCoMaRPw4HiGVdNm0qOfmQAeuopMQKBgQDMJ5MWc9jAbvs1hb4JZ6oTPY1SMfA7QTas3EHq8CFCgvFJEtu1F2QD4w+L2G0m0zgVHDuFKR88mFIs+yGzfEhNy05ro2oAExW7MuwwEeQHWcq1m+r7iQMEchMCJbXEZLf9lUxNb3NKVG/W3gMDAkJfh+uFln/DAf/GOeFXttqP6QKBgBvI8e3VB+2vlLoHohOq8tLDAggp6wrFswjQVpq6ZFb+gZQfUte2Oz2nZt3BXHQ2nGhvAn4yoTT4MFFEIbtJXZzZQKGSpjjA3CHWURX7Gt9fpJ9TWIMuZEyqatdAY3WcF6Kp80ld7T+VKfYJg7Tnj/bQsUfQUgWVdv/6aq1ZYwLxAoGAVo80+yEVNsoXcuMI5x3v64Z+g+oujEEjOHmO7VjxES1JtozrYMXRHyDHt6iE5yBJOBInXldIKj3/IZk8+cfpNCrOTi8UQ4HUBfIaWGtNfpxgqU25fPrjr5/aVV+9yM5XgIortu1ZOZvBa53oMv9jOvP7NUHRW9JeKyLhW3ppAvkCgYEAlOC6viG8DSarMSMRL4I5vm4tpO0lwuU0bGeAEy+fTme+Ww3Hkg2Bgz/e3+jfkYbwAkkR24XO4qJJeNF11Uq4ghA/1dCRrfGkB0XK7bNrsXsgyZeaU8Ku1vLG/6LVNPgSuoZTYyQAcSaQP+Ipze3Sz49nUD6Jsdjt2zlaeMCCQVs=␍; 企油通平台公钥(一行 base64,用于验签其响应与回调)␍PLATFORM_PUBLIC_KEY = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqYvOjWD+bFY9hXC70pN6OpplgIBFuE4rjLcw6pXdUI2igFWhG2xjfd2b0mmn2sD3gTCOaotODbxR5z5RrkIhsSeXXc2hOD+f8QLHdjZ1BWN9KO9Z906t9I2EHW5U2u8a/ZwIWYga3dgrSln756FP0cbCTkVIYiYSrU1YzUOjfFCAg8lFc5YSKirYrITC2A0iIUZzjba0Lip1wzwKGk8W8RByx124nO1j8LTlDZZ/+6Cri3Ricip9opBb9WATc8FaWi0bLvsxete6hOR1lX+RrRLPyLg/qdbeKeg55tAatXwoZKs96UHo4srjc4Wbd8yWVgs92jzsSi1wYX5vldvcmQIDAQAB␍; 我方统一社会信用代码 / 营业执照编码␍BUSINESS_LICENSE = 91450103MAE2729EXJ (1-1)␍; 机构号:支付宝企业码通道 7.7 必填,由企油通分配␍INSTITUTION_ID = ␍; 全平台唯一编号前缀(batch_no/waybill_no/waybill_no_code),正式 HM、测试建议 HMTEST␍NO_PREFIX = HM␍; 7.15 H5 免登落地页地址(测试:http://dev.paytaskpage.worknoya.vip/#/)␍H5_BASE_URL = https://paytaskpage.worknoya.vip/#/␍; 7.15 H5 免登用的 appId(可能与业务 appId 不同,需线下确认)␍H5_APP_ID = 37257338026e7a377165454689773882␍; 我方 7.10 消费回调地址(仅作记录,实际需在企油通后台配置)␍CONSUME_NOTIFY_URL = http://47.104.73.7/qytconsume.php␍; 业务接口签名字段拼接方式,待对接人确认后可改␍SIGN_MODE = ksort_md5_sha1rsa␍; 是否记录详细请求/响应日志到 runtime/storelog␍LOG = true␍
APP_DEBUG = false␍␍[APP]␍DEFAULT_TIMEZONE = Asia/Shanghai␍␍[DATABASE]␍TYPE = mysql␍HOSTNAME = 127.0.0.1␍DATABASE = oil␍USERNAME = oil␍PASSWORD = AxSZEJDp5nePyfMa␍HOSTPORT = 3306␍CHARSET = utf8␍DEBUG = true␍␍[CACHE]␍DRIVER = file␍␍[REDIS]␍HOSTNAME = 127.0.0.1␍HOSTPORT = 6379␍PASSWORD =␍SELECT = 0␍␍[LANG]␍default_lang = zh-cn␍␍; ===== 企油通对接配置(值不要加引号;RSA 密钥压成一行 base64,去掉 BEGIN/END 与换行)=====␍[QYT]␍; 接口域名,不含结尾斜杠,向企油通对接人索取(测试/正式不同)␍BASE_URL = http://dev.pbpayapi.worknoya.work␍; 平台分配的 appId(业务接口用)␍APP_ID = 37257338026e7a377165454689773882␍; 我方 RSA 私钥(一行 base64,无头尾无换行)␍PRIVATE_KEY = MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpi86NYP5sVj2FcLvSk3o6mmWAgEW4TiuMtzDqld1QjaKAVaEbbGN93ZvSaafawPeBMI5qi04NvFHnPlGuQiGxJ5ddzaE4P5/xAsd2NnUFY30o71n3Tq30jYQdblTa7xr9nAhZiBrd2CtKWfvnoU/RxsJORUhiJhKtTVjNQ6N8UICDyUVzlhIqKtishMLYDSIhRnONtrQuKnXDPAoaTxbxEHLHXbic7WPwtOUNln/7oKuLdGJyKn2ikFv1YBNzwVpaLRsu+zF617qE5HWVf5GtEs/IuD+p1t4p6Dnm0Bq1fChkqz3pQejiyuNzhZt3zJZWCz3aPOxKLXBhfm+V29yZAgMBAAECggEABRJmGOucYNBJodrH+5omz2b7/1aW/1bzTY/TWkkmaDj8cftdyOy6ozLEbQx1FYGtRcOWlxBYwudtyc2e8ugOivakvA0hNAYPOk37eolRgdpCjb1vQwsrAARVve2lgzEg7RJEW61LJHTJgr1jYD/+m8dm26GN80WxTFbif6100Ayp++qxP7/EAwsD+Nl9wNo1itSHIiwzt4ohCWgxoSgVwkoUdMVM3qERzBXzjQAzK/A8DuJDyNayu74YVBrxXGRbzeeOippu7d2dY6bHwyDah7ki5IW9MRmMH/d24sayb+kYYKXI9d+G9jqXlL9ZASb4/OPQ2s7WPftDQhIvpXJdwQKBgQDUmkdl+viy13PtmV00AfzDaOeOTOknzfW7xjylnYR63pI8dnMLTN+RIXYgWMbehrLjMwxhWjekOPKA9HZ2g1wI7sxYsHWBiq6StYOcS7RlGayyvFstbbUR3X5Ff7y/3+BmYzrzZKepwJ8bCLCoMaRPw4HiGVdNm0qOfmQAeuopMQKBgQDMJ5MWc9jAbvs1hb4JZ6oTPY1SMfA7QTas3EHq8CFCgvFJEtu1F2QD4w+L2G0m0zgVHDuFKR88mFIs+yGzfEhNy05ro2oAExW7MuwwEeQHWcq1m+r7iQMEchMCJbXEZLf9lUxNb3NKVG/W3gMDAkJfh+uFln/DAf/GOeFXttqP6QKBgBvI8e3VB+2vlLoHohOq8tLDAggp6wrFswjQVpq6ZFb+gZQfUte2Oz2nZt3BXHQ2nGhvAn4yoTT4MFFEIbtJXZzZQKGSpjjA3CHWURX7Gt9fpJ9TWIMuZEyqatdAY3WcF6Kp80ld7T+VKfYJg7Tnj/bQsUfQUgWVdv/6aq1ZYwLxAoGAVo80+yEVNsoXcuMI5x3v64Z+g+oujEEjOHmO7VjxES1JtozrYMXRHyDHt6iE5yBJOBInXldIKj3/IZk8+cfpNCrOTi8UQ4HUBfIaWGtNfpxgqU25fPrjr5/aVV+9yM5XgIortu1ZOZvBa53oMv9jOvP7NUHRW9JeKyLhW3ppAvkCgYEAlOC6viG8DSarMSMRL4I5vm4tpO0lwuU0bGeAEy+fTme+Ww3Hkg2Bgz/e3+jfkYbwAkkR24XO4qJJeNF11Uq4ghA/1dCRrfGkB0XK7bNrsXsgyZeaU8Ku1vLG/6LVNPgSuoZTYyQAcSaQP+Ipze3Sz49nUD6Jsdjt2zlaeMCCQVs=␍; 企油通平台公钥(一行 base64,用于验签其响应与回调)␍PLATFORM_PUBLIC_KEY = MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqYvOjWD+bFY9hXC70pN6OpplgIBFuE4rjLcw6pXdUI2igFWhG2xjfd2b0mmn2sD3gTCOaotODbxR5z5RrkIhsSeXXc2hOD+f8QLHdjZ1BWN9KO9Z906t9I2EHW5U2u8a/ZwIWYga3dgrSln756FP0cbCTkVIYiYSrU1YzUOjfFCAg8lFc5YSKirYrITC2A0iIUZzjba0Lip1wzwKGk8W8RByx124nO1j8LTlDZZ/+6Cri3Ricip9opBb9WATc8FaWi0bLvsxete6hOR1lX+RrRLPyLg/qdbeKeg55tAatXwoZKs96UHo4srjc4Wbd8yWVgs92jzsSi1wYX5vldvcmQIDAQAB␍; 我方统一社会信用代码 / 营业执照编码␍BUSINESS_LICENSE = 91450103MAE2729EXJ (1-1)␍; ---- 支付通道开关:alipay=支付宝企业码 / pingan=平安银行 ----␍; 改完必须重启 partners:queue 守护进程才生效␍PASSAGEWAY = alipay␍; 支付宝企业码通道:7.7 的 passageway 入参值 + 该通道的机构号␍ALIPAY_CODE = 1␍ALIPAY_INSTITUTION_ID = ␍; 平安银行通道:PINGAN_CODE 待企油通对接人确认(暂按 2 预填)␍PINGAN_CODE = 2␍PINGAN_INSTITUTION_ID = ␍; ---- 通道开关结束 ----␍; 全平台唯一编号前缀(batch_no/waybill_no/waybill_no_code),正式 HM、测试建议 HMTEST␍NO_PREFIX = HM␍; 7.15 H5 免登落地页地址(测试:http://dev.paytaskpage.worknoya.vip/#/)␍H5_BASE_URL = https://paytaskpage.worknoya.vip/#/␍; 7.15 H5 免登用的 appId(可能与业务 appId 不同,需线下确认)␍H5_APP_ID = 37257338026e7a377165454689773882␍; 我方 7.10 消费回调地址(仅作记录,实际需在企油通后台配置)␍CONSUME_NOTIFY_URL = http://47.104.73.7/qytconsume.php␍; 业务接口签名字段拼接方式,待对接人确认后可改␍SIGN_MODE = ksort_md5_sha1rsa␍; 是否记录详细请求/响应日志到 runtime/storelog␍LOG = true␍
+26 -1
View File
@@ -17,6 +17,7 @@ use app\api\model\partners\Order as OrderModel;
use app\common\model\Partners as PartnersModel;
use app\common\crypto\RsaUtil;
use app\common\service\PartnersBalance;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\QytOil;
use app\store\model\order\QrCode as QrCodeModel;
use cores\exception\BaseException;
@@ -85,6 +86,8 @@ class OilCard extends Controller
$oilCardType = $data['oilCardType'] ?? '';
// 企油通:司机姓名(下单平台必传,用于运单油费下发)
$driverName = trim((string)($data['name'] ?? ''));
// 企油通:司机身份证号。平安银行通道要求实名必传,支付宝企业码通道可不传
$idCard = strtoupper(trim((string)($data['idCard'] ?? '')));
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $orgNo){
return $this->oilRenderError('12','0014');
@@ -95,6 +98,19 @@ class OilCard extends Controller
if($driverName === ''){
return $this->oilRenderError('12','0001', '', '缺少必填请求参数 name(司机姓名)');
}
// 解析本单使用的企油通企业:合作商绑定的企业 -> 默认企业
$enterprise = EnterpriseModel::resolve((int)$partners['qyt_enterprise_id']);
if(!$enterprise){
$this->saveLog('share 未匹配到可用的企油通企业,partners_id=' . $partners['partners_id']);
return $this->oilRenderError('99','9999', '', '系统未配置可用企业,请联系平台');
}
// 身份证号:传了就必须合法;该企业当前通道要求实名时不传直接拒单
if($idCard !== '' && !preg_match('/^\d{17}[\dX]$/', $idCard)){
return $this->oilRenderError('12','0013', '', 'idCard 身份证号格式不正确(18位)');
}
if($idCard === '' && $enterprise->requireIdCard()){
return $this->oilRenderError('12','0001', '', '缺少必填请求参数 idCard(司机身份证号,当前支付通道要求实名)');
}
// 金额:由下单平台直接传入(单位:元,最多两位小数)
if($amount === '' || !preg_match('/^\d{1,8}(\.\d{1,2})?$/', (string)$amount) || (float)$amount <= 0){
return $this->oilRenderError('12','0013', '', 'amount 金额格式不正确(单位:元,最多两位小数且大于0)');
@@ -122,6 +138,10 @@ class OilCard extends Controller
$created = $OrderModel->saveOrder([
'mobile' => $mobile,
'driver_name' => $driverName,
'id_card' => $idCard,
// 企业与通道随单固化:之后改绑定或切默认企业,在途单仍按下单时的企业发放
'qyt_enterprise_id' => $enterprise['enterprise_id'],
'passageway' => $enterprise['passageway'],
'oilCardType' => $oilCardType,
'oilCardNo' => $oilCardNo,
'partners_order_no' => $saleOrderNo,
@@ -237,7 +257,12 @@ class OilCard extends Controller
return $this->oilRenderError('12','0013', '', '未找到对应订单');
}
// 企油通:生成司机免登加油 H5 地址(7.15)。出参 qrCode 结构不变,值由二维码链接改为 H5 URL。
$h5Url = (new QytOil())->h5Url($order['mobile']);
// creditcode 取本单固化的企业,把登录固定到该企业;企业须已通过企油通审核
$orderEnterprise = EnterpriseModel::detail((int)$order['qyt_enterprise_id']);
if(!$orderEnterprise){
return $this->oilRenderError('12','0013', '', '本单绑定的企业不存在,无法生成加油链接');
}
$h5Url = QytOil::forEnterprise($orderEnterprise)->h5Url($order['mobile']);
if(!$h5Url){
return $this->oilRenderError('12','0013', '', '生成加油链接失败');
}
+1 -1
View File
@@ -26,7 +26,7 @@ class Qyt
public function __construct(array $conf = null)
{
$this->conf = $conf ?: (array) config('qyt');
$this->conf = $conf ?: \app\common\service\QytConfig::get();
$this->privateKey = $this->loadPrivateKey($this->conf['private_key'] ?? '');
$this->platformPublicKey = $this->loadPublicKey($this->conf['platform_public_key'] ?? '');
}
+7
View File
@@ -55,6 +55,9 @@ class Setting extends EnumBasics
// 分类页模板
const PAGE_CATEGORY_TEMPLATE = 'page_category_template';
// 企油通对接设置(非敏感项;RSA 密钥仍在 .env,不入库)
const QYT = 'qyt';
/**
* 获取订单类型值
* @return array
@@ -110,6 +113,10 @@ class Setting extends EnumBasics
'value' => self::PAGE_CATEGORY_TEMPLATE,
'describe' => '分类页模板',
],
self::QYT => [
'value' => self::QYT,
'describe' => '企油通对接设置',
],
];
}
}
@@ -0,0 +1,109 @@
<?php
// +----------------------------------------------------------------------
// | 企油通企业模型
// +----------------------------------------------------------------------
// | 一个接入方 appId 下可挂多个企业(见《企油通接口对接文档.md》7.22)。
// | 企业级参数:business_license / passageway / institution_id 等存本表;
// | 接入方级参数:appId、RSA 私钥、接口域名仍在 .env,私钥不入库。
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\model\qyt;
use cores\BaseModel;
class Enterprise extends BaseModel
{
protected $name = 'qyt_enterprise';
protected $pk = 'enterprise_id';
/** 通道:配置键 => 企油通 passageway 编码 */
const PASSAGEWAY_CODE = [
'alipay' => 1,
'pingan' => 2,
];
/** 通道名称 */
const PASSAGEWAY_NAME = [
'alipay' => '支付宝企业码',
'pingan' => '平安银行',
];
/** 企业状态(文档 2.6) */
const STATUS_TEXT = [
'pending' => '待完善',
'submitted' => '待审核',
'approved' => '审核通过',
'rejected' => '审核拒绝',
'frozen' => '已冻结',
];
/**
* 企业详情
* @param int|array $where
* @return static|null
*/
public static function detail($where)
{
!is_array($where) && $where = ['enterprise_id' => (int) $where];
return static::get($where);
}
/**
* 取默认企业(合作商未绑定时的兜底)
* @return static|null
*/
public static function getDefault()
{
return static::where('is_enable', 1)->where('is_default', 1)
->order('enterprise_id', 'asc')->find();
}
/**
* 解析某个合作商本次下单应使用的企业
* 优先级:合作商绑定的企业 -> 默认企业;都取不到返回 null,由调用方拒单
* @param int $enterpriseId 合作商绑定的企业ID,0 表示未绑定
* @return static|null
*/
public static function resolve(int $enterpriseId = 0)
{
if ($enterpriseId > 0) {
$one = static::where('enterprise_id', $enterpriseId)->where('is_enable', 1)->find();
if ($one) {
return $one;
}
}
return static::getDefault();
}
/** 当前通道的企油通 passageway 编码 */
public function passagewayCode(): int
{
return self::PASSAGEWAY_CODE[$this->getData('passageway')] ?? self::PASSAGEWAY_CODE['alipay'];
}
/** 当前通道中文名 */
public function passagewayName(): string
{
return self::PASSAGEWAY_NAME[$this->getData('passageway')] ?? '';
}
/** 当前通道是否要求司机身份证号(平安银行要求实名) */
public function requireIdCard(): bool
{
return $this->getData('passageway') === 'pingan';
}
/** 状态中文(访问器,后台列表直接可用) */
public function getStatusTextAttr($value, $data): string
{
return self::STATUS_TEXT[$data['status'] ?? ''] ?? ($data['status'] ?? '');
}
/** 通道中文(访问器) */
public function getPassagewayTextAttr($value, $data): string
{
return self::PASSAGEWAY_NAME[$data['passageway'] ?? ''] ?? '';
}
}
@@ -312,6 +312,25 @@ class Setting extends BaseModel
"d) 买家在完成该笔交易(订单状态为“已签收”)后才能得到此笔交易的相应积分,如购买商品参加店铺其他优惠,则优惠的金额部分不享受积分获取;",
],
],
// 企油通对接设置
// ⚠️ 只放非敏感项:RSA 私钥与平台公钥始终从 .env 读取,绝不入库,
// 避免数据库备份文件等同于资金级密钥副本。见 app\common\service\QytConfig
SettingEnum::QYT => [
'key' => SettingEnum::QYT,
'describe' => '企油通对接设置',
// 均为「接入方级」参数。企业级参数(执照号 / 通道 / 机构号)
// 在「后台 → 企业管理」里按企业维护,不在这里配。
'values' => [
'base_url' => '', // 接口域名,不含结尾斜杠
'app_id' => '', // 接入方 appId
'no_prefix' => 'HM', // 编号前缀默认值(企业可各自覆盖)
'voucher_name' => '运单卡', // 7.7 凭证名称默认值(企业可各自覆盖)
'h5_base_url' => '', // 7.15 落地页
'h5_app_id' => '', // 7.15 免登 appId
'consume_notify_url' => '', // 7.10 回调地址(仅记录,实际在企油通后台配)
'log' => 1, // 是否记录详细请求/响应日志
],
],
// 分类页模板
SettingEnum::PAGE_CATEGORY_TEMPLATE => [
'key' => SettingEnum::PAGE_CATEGORY_TEMPLATE,
+147
View File
@@ -0,0 +1,147 @@
<?php
// +----------------------------------------------------------------------
// | 企油通配置读取
// +----------------------------------------------------------------------
// | 三层来源,从低到高:
// | 1. config/qyt.php —— 读 .env,含【密钥】与接口域名(接入方级)
// | 2. 后台「企油通对接设置」—— yoshop_store_setting.key='qyt'(接入方级默认值)
// | 3. 企业信息 —— yoshop_qyt_enterprise(企业级:执照号 / 通道 / 机构号 …)
// |
// | 分级依据见《企油通接口对接文档.md》:appId 与 RSA 密钥属于「接入方(慧明)」,
// | 一个 appId 下可挂多个企业;business_license、passageway、institution_id 是企业级。
// |
// | 敏感项(private_key、platform_public_key)只从 .env 读,任何一层都不能覆盖,
// | 数据库备份 / SQL 注入 / 后台账号失窃都拿不到签名私钥。
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\service;
use app\common\enum\Setting as SettingEnum;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use think\facade\Db;
class QytConfig
{
/** 常驻进程内缓存有效期(秒):后台改完最迟这么久生效 */
const TTL = 10;
/** 后台设置可覆盖的接入方级标量项 */
const OVERRIDABLE = [
'base_url', 'app_id', 'no_prefix', 'voucher_name',
'h5_base_url', 'h5_app_id', 'consume_notify_url',
];
/** @var array|null */
protected static $cache = null;
/** @var int */
protected static $cacheAt = 0;
/**
* 接入方级配置(不含任何企业信息)
*/
public static function get(): array
{
if (self::$cache !== null && (time() - self::$cacheAt) < self::TTL) {
return self::$cache;
}
$conf = (array) config('qyt');
$db = static::dbValues();
foreach (self::OVERRIDABLE as $k) {
if (isset($db[$k]) && trim((string) $db[$k]) !== '') {
$conf[$k] = trim((string) $db[$k]);
}
}
// 日志开关是布尔,0 是合法值,不能用非空判断
if (isset($db['log'])) {
$conf['log'] = (bool) $db['log'];
}
self::$cache = $conf;
self::$cacheAt = time();
return $conf;
}
/**
* 叠加企业级参数,得到某个企业实际使用的完整配置
*
* @param EnterpriseModel|array $enterprise
* @return array 额外带 enterprise_id / enterprise_name / passageway_code / require_id_card
*/
public static function forEnterprise($enterprise): array
{
$conf = self::get();
$e = $enterprise instanceof EnterpriseModel ? $enterprise->toArray() : (array) $enterprise;
$passageway = (string) ($e['passageway'] ?? 'alipay');
!isset(EnterpriseModel::PASSAGEWAY_CODE[$passageway]) && $passageway = 'alipay';
$conf['enterprise_id'] = (int) ($e['enterprise_id'] ?? 0);
$conf['enterprise_name'] = (string) ($e['company_name'] ?? '');
$conf['business_license'] = (string) ($e['business_license'] ?? '');
$conf['passageway'] = $passageway;
$conf['passageway_code'] = EnterpriseModel::PASSAGEWAY_CODE[$passageway];
$conf['passageway_name'] = EnterpriseModel::PASSAGEWAY_NAME[$passageway];
$conf['institution_id'] = (string) ($e['institution_id'] ?? '');
// 平安银行通道要求实名
$conf['require_id_card'] = $passageway === 'pingan';
// 以下留空则回落接入方级默认值
foreach (['app_id', 'no_prefix', 'voucher_name'] as $k) {
$v = trim((string) ($e[$k] ?? ''));
$v !== '' && $conf[$k] = $v;
}
return $conf;
}
/**
* 取单项,支持点号:QytConfig::item('h5_base_url')
* @param mixed $default
* @return mixed
*/
public static function item(string $path, $default = null)
{
$node = self::get();
foreach (explode('.', $path) as $seg) {
if (!is_array($node) || !array_key_exists($seg, $node)) {
return $default;
}
$node = $node[$seg];
}
return $node;
}
/** 清掉进程内缓存(单元测试 / 后台保存后使用) */
public static function flush(): void
{
self::$cache = null;
self::$cacheAt = 0;
}
/**
* 读后台设置。直接查表而不用 Setting 模型,是因为常驻命令行进程里
* BaseModel::$storeId 不会被初始化(只有 store/api 模块才赋值),
* 走模型会查不到数据。本项目为单商城部署,取 store_id 最小的一条即可。
*/
protected static function dbValues(): array
{
try {
$json = Db::name('store_setting')
->where('key', SettingEnum::QYT)
->order('store_id', 'asc')
->value('values');
if (empty($json)) {
return [];
}
$arr = json_decode((string) $json, true);
return is_array($arr) ? $arr : [];
} catch (\Throwable $e) {
// 表不存在 / 库连不上时静默回落到 .env,不能因为读设置失败就中断发放
return [];
}
}
}
+289 -47
View File
@@ -1,95 +1,310 @@
<?php
// +----------------------------------------------------------------------
// | 企油通 · 上游接口客户端(支付宝企业码通道)
// | 企油通 · 上游接口客户端
// +----------------------------------------------------------------------
// | 本次实际对接仅 3 个接口:
// | 7.7 运单油费下发 createAndSendQuota —— 本类 createAndSendQuota()
// | 7.15 H5 跳转地址 (本地签名生成 URL) —— 本类 h5Url()
// | 7.10 运单加油明细推送(异步回调) —— app\api\controller\QytNotify
// | 通道固定为支付宝企业码(passageway=1),司机手机号 + 姓名必填。
// | 多企业:一个接入方 appId 下可挂多个企业,业务接口靠 business_license 区分。
// | 用法:QytOil::forEnterprise($enterprise)->createAndSendQuota([...])
// | 不带企业的接口(7.22 企业列表)用 new QytOil() 即可。
// |
// | 已实现:
// | 7.2 企业申请创建 openAnAccount
// | 7.5 企业账户查询 accountQuery
// | 7.7 运单油费下发 createAndSendQuota
// | 7.15 H5免登地址 h5Url(本地签名,不发请求)
// | 7.22 企业列表查询 enterpriseList
// | 7.10 消费回调由 app\api\controller\QytNotify 接收
// |
// | 通道:alipay=支付宝企业码(passageway=1) / pingan=平安银行(passageway=2),
// | 通道开关存在企业信息里。平安通道要求实名,7.7 必传 id_card。
// | 金额:对外「元」入参,7.7 内部换算为「分」。
// |
// | ⚠️ 7.7 是异步接口,返回成功只代表【受理】,真实发放结果需用 7.8
// | waybillBatch 按 batch_no 查询(见文档 §5 第 7 条)。当前状态机仍按
// | 「受理即完成」处理,7.8 轮询尚未接入。
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\service;
use app\common\crypto\Qyt as QytCrypto;
use app\common\model\qyt\Enterprise as EnterpriseModel;
class QytOil extends BaseService
{
// 支付宝企业码通道
const PASSAGEWAY_ALIPAY = 1;
// 通道标识
const CHANNEL_ALIPAY = 'alipay';
const CHANNEL_PINGAN = 'pingan';
/** @var array config('qyt') */
/** @var array 合并后的配置(接入方级 + 企业级) */
protected $conf;
/** @var QytCrypto */
protected $crypto;
/** @var EnterpriseModel|array|null 当前企业 */
protected $enterprise = null;
protected function initialize()
{
$this->conf = (array) config('qyt');
$this->conf = QytConfig::get();
$this->crypto = new QytCrypto($this->conf);
}
// ================================================================
// 企业上下文
// ================================================================
/**
* 绑定企业后返回实例(业务接口的常规入口)
* @param EnterpriseModel|array $enterprise
*/
public static function forEnterprise($enterprise): self
{
return (new static())->useEnterprise($enterprise);
}
/**
* 切换当前企业:执照号 / 通道 / 机构号 / 编号前缀等随之变化
* @param EnterpriseModel|array $enterprise
*/
public function useEnterprise($enterprise): self
{
$this->enterprise = $enterprise;
$this->conf = QytConfig::forEnterprise($enterprise);
$this->crypto = new QytCrypto($this->conf);
return $this;
}
public function enterpriseId(): int
{
return (int) ($this->conf['enterprise_id'] ?? 0);
}
public function enterpriseName(): string
{
return (string) ($this->conf['enterprise_name'] ?? '');
}
public function businessLicense(): string
{
return (string) ($this->conf['business_license'] ?? '');
}
/** 当前通道标识 alipay / pingan */
public function channelKey(): string
{
return (string) ($this->conf['passageway'] ?? self::CHANNEL_ALIPAY);
}
/** 当前通道中文名 */
public function channelName(): string
{
return (string) ($this->conf['passageway_name'] ?? '');
}
/** 当前通道的企油通 passageway 编码:1 支付宝企业码 / 2 平安银行 */
public function channelCode(): int
{
return (int) ($this->conf['passageway_code'] ?? 1);
}
/** 当前通道是否必须上送司机身份证号(平安银行=是) */
public function requireIdCard(): bool
{
return !empty($this->conf['require_id_card']);
}
/**
* 生成企油通全平台唯一编号(加企业前缀),确定性派生 -> 重试保持一致
* batch_no / waybill_no / waybill_no_code 都是【全平台唯一】而非接入方内唯一,
* 所以各企业建议用不同前缀,避免与其他接入方或兄弟企业撞号。
* @param string $type B=batch / W=waybill / C=waybill_no_code
* @param string $seed 订单号 / order_id 等业务种子
*/
public function no(string $type, string $seed): string
{
return ((string) ($this->conf['no_prefix'] ?: 'HM')) . $type . $seed;
}
// ================================================================
// 7.7 运单油费下发(一张订单 = 1 batch / 1 waybill / 1 employee)
// ================================================================
/**
* @param array $data ['batch_no','waybill_no','waybill_no_code','mobile','name','amount'(元)]
* @param array $data ['batch_no','waybill_no','waybill_no_code','mobile','name','amount'(元),'id_card'(平安通道必填)]
* @return array 归一化 ['code'=>'00'|其它, 'data'=>[], 'message'=>'', 'dup_batch'=>bool]
*/
public function createAndSendQuota(array $data): array
{
if (($err = $this->requireEnterprise()) !== null) {
return $err;
}
// 支付宝企业码通道下 institution_id 必填;缺了就地失败,不把空值发给企油通
if ($this->channelCode() === 1 && $this->conf['institution_id'] === '') {
return $this->fail("企业[{$this->enterpriseName()}]未配置机构号(institution_id),支付宝企业码通道无法下发");
}
$idCard = trim((string) ($data['id_card'] ?? ''));
if ($this->requireIdCard() && $idCard === '') {
return $this->fail("通道[{$this->channelName()}]要求实名,缺少司机身份证号(idCard)");
}
$employee = [
'mobile' => $data['mobile'],
'name' => $data['name'],
'quota_amount' => self::yuanToFen($data['amount']), // 分
'voucher_name' => $this->conf['voucher_name'],
'effective_start' => '', // 空=当日生效
'effective_end' => '', // 空=长期有效
'waybill_no_code' => $data['waybill_no_code'],
];
// 身份证号仅在要求实名的通道上送,避免向不需要的通道多传敏感信息
if ($this->requireIdCard()) {
$employee['id_card'] = $idCard;
}
$params = [
'batch_no' => $data['batch_no'],
'business_license' => $this->conf['business_license'],
'passageway' => (string) self::PASSAGEWAY_ALIPAY,
'institution_id' => $this->conf['institution_id'],
'business_license' => $this->businessLicense(),
'passageway' => (string) $this->channelCode(),
'institution_id' => (string) $this->conf['institution_id'],
'waybill_list' => [[
'waybill_no' => $data['waybill_no'],
'employee_list' => [[
'mobile' => $data['mobile'],
'name' => $data['name'],
'quota_amount' => self::yuanToFen($data['amount']), // 分
'voucher_name' => $this->conf['voucher_name'],
'effective_start' => '', // 空=当日生效
'effective_end' => '', // 空=长期有效
'waybill_no_code' => $data['waybill_no_code'],
]],
'employee_list' => [$employee],
]],
];
return $this->post('/enterprise/api/zhyk/createAndSendQuota', $params);
}
// ================================================================
// 7.8 运单发放结果查询(7.7 是异步的,真实结果只能从这里拿)
// ================================================================
/**
* @param string $batchNo 7.7 提交时用的批次号
* @return array data: batch_no / status(1待处理 2处理中 3全部成功 4部分成功 5处理失败) / list[]
* list[] 里每条含 waybill_no_code / handle_status(1待处理 2成功 3失败) / error_msg
*/
public function waybillBatch(string $batchNo): array
{
$params = ['batch_no' => $batchNo];
// business_license 可选;传了会校验归属,能更早发现串企业的问题
$this->businessLicense() !== '' && $params['business_license'] = $this->businessLicense();
return $this->post('/enterprise/api/zhyk/waybillBatch', $params);
}
/**
* 从 7.8 的返回里挑出某条运单流水的处理结果
* @return array|null ['handle_status'=>int, 'error_msg'=>string, 'remark'=>string]
*/
public static function pickWaybill(array $data, string $waybillNoCode): ?array
{
foreach ((array) ($data['list'] ?? []) as $row) {
if ((string) ($row['waybill_no_code'] ?? '') === $waybillNoCode) {
return [
'handle_status' => (int) ($row['handle_status'] ?? 0),
'error_msg' => (string) ($row['error_msg'] ?? ''),
'remark' => (string) ($row['remark'] ?? ''),
];
}
}
return null;
}
// ================================================================
// 7.2 企业申请创建
// ================================================================
/**
* @param array $data ['business_license','business_license_url','passageway'(如 "1,2"),'company_name','contact_mobile','contact_name']
*/
public function openAnAccount(array $data): array
{
return $this->post('/enterprise/api/zhyk/openAnAccount', [
'business_license' => $data['business_license'],
'business_license_url' => $data['business_license_url'],
'passageway' => (string) $data['passageway'],
'company_name' => $data['company_name'],
'contact_mobile' => $data['contact_mobile'],
'contact_name' => $data['contact_name'],
]);
}
// ================================================================
// 7.5 企业账户信息查询
// ================================================================
public function accountQuery(): array
{
if (($err = $this->requireEnterprise()) !== null) {
return $err;
}
return $this->post('/enterprise/api/zhyk/accountQuery', [
'business_license' => $this->businessLicense(),
'passageway' => (string) $this->channelCode(),
]);
}
// ================================================================
// 7.22 企业列表查询(枚举本 appId 名下企业,需先开通权限,未开通返回 403)
// ================================================================
public function enterpriseList(array $params = []): array
{
return $this->post('/enterprise/api/enterprise/list', [
'page' => $params['page'] ?? 1,
'page_size' => $params['page_size'] ?? 100,
]);
}
// ================================================================
// 7.15 H5 免登加油地址(本地签名生成 URL,不发起接口请求)
// 文档:本地址与支付通道无关,落地页由系统按企业实际通道自动判定
// ================================================================
public function h5Url(string $mobile): string
{
$creditcode = !empty($this->conf['h5_with_creditcode']) ? (string) $this->conf['business_license'] : '';
// creditcode 传了则本次登录固定到该企业,且必须一并入签
$creditcode = !empty($this->conf['h5_with_creditcode']) ? $this->businessLicense() : '';
return $this->crypto->h5Url($mobile, $creditcode);
}
// ================================================================
// 内部:统一请求
// 内部
// ================================================================
/** 未绑定企业 / 企业没有执照号时的统一拦截 */
protected function requireEnterprise(): ?array
{
if ($this->businessLicense() === '') {
return $this->fail('未指定企业或企业缺少统一社会信用代码(business_license)');
}
return null;
}
/** 构造一个失败返回,并记日志 */
protected function fail(string $message): array
{
$this->saveLog('[配置错误] ' . $message);
return ['code' => 'ERR', 'data' => [], 'message' => $message, 'dup_batch' => false, 'raw' => []];
}
/**
* @return array ['code' => '00'成功 | 其它, 'data' => array, 'message' => string, 'dup_batch' => bool, 'raw' => array]
*/
protected function post(string $path, array $bizParams): array
{
$url = rtrim((string) $this->conf['base_url'], '/') . $path;
$url = rtrim((string) $this->conf['base_url'], '/') . $path;
$body = $this->crypto->envelope($bizParams);
$this->saveLog("[请求] {$path} " . json_encode(['url' => $url, 'params' => $bizParams], JSON_UNESCAPED_UNICODE));
// 日志脱敏:手机号 / 身份证号不落明文
$this->saveLog("[请求] {$path} " . json_encode(
['企业' => $this->enterpriseName(), 'url' => $url, 'params' => self::maskDeep($bizParams)],
JSON_UNESCAPED_UNICODE));
$resp = $this->curlPost($url, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$this->saveLog("[响应] {$path} " . $resp);
$this->saveLog("[响应] {$path} " . self::maskText($resp));
$json = json_decode($resp, true);
if (!is_array($json)) {
@@ -97,15 +312,13 @@ class QytOil extends BaseService
}
// 通用响应结构:{ status: ok|error, data, message, serialNo, sign }
$ok = ($json['status'] ?? '') === 'ok';
// 【TODO 待对接人确认】是否所有响应都带 sign、验签串如何构造
// if ($ok && !empty($json['sign'])) { $ok = $this->crypto->verify($json['data'] ?? [], $json['sign']); }
$ok = ($json['status'] ?? '') === 'ok';
$message = (string) ($json['message'] ?? '');
// 幂等:batch_no 已存在 —— 视为「已受理」,供 7.7 重试判定
$dupBatch = false;
if (!$ok && $message !== '') {
foreach ((array) (config('qyt.dup_batch_hints') ?: ['已存在', '重复', 'duplicate', 'exist']) as $hint) {
foreach ((array) ($this->conf['dup_batch_hints'] ?: ['已存在', '重复', 'duplicate', 'exist']) as $hint) {
if (mb_stripos($message, $hint) !== false) {
$dupBatch = true;
break;
@@ -145,9 +358,49 @@ class QytOil extends BaseService
return (string) $resp;
}
// ================================================================
// 工具
// ================================================================
// ---------------- 敏感信息脱敏(日志 / 后台展示复用) ----------------
/** 手机号:138****8000 */
public static function maskMobile(string $s): string
{
return strlen($s) === 11 ? substr($s, 0, 3) . '****' . substr($s, -4) : $s;
}
/** 身份证号:4501**********1234 */
public static function maskIdCard(string $s): string
{
$len = strlen($s);
return $len >= 8 ? substr($s, 0, 4) . str_repeat('*', $len - 8) . substr($s, -4) : $s;
}
/** 递归脱敏数组(按键名识别) */
public static function maskDeep($data)
{
if (!is_array($data)) {
return $data;
}
foreach ($data as $k => $v) {
if (is_array($v)) {
$data[$k] = self::maskDeep($v);
} elseif ($k === 'mobile') {
$data[$k] = self::maskMobile((string) $v);
} elseif ($k === 'id_card' || $k === 'idCard') {
$data[$k] = self::maskIdCard((string) $v);
}
}
return $data;
}
/** 脱敏 JSON 文本里的手机号 / 身份证号(用于原始响应报文落日志) */
public static function maskText(string $s): string
{
$s = preg_replace_callback('/"(id_card|idCard)"\s*:\s*"([^"]+)"/i',
function ($m) { return '"' . $m[1] . '":"' . self::maskIdCard($m[2]) . '"'; }, $s);
return (string) preg_replace_callback('/"mobile"\s*:\s*"(\d{11})"/',
function ($m) { return '"mobile":"' . self::maskMobile($m[1]) . '"'; }, $s);
}
// ---------------- 工具 ----------------
/** 元 → 分 */
public static function yuanToFen($yuan): int
@@ -161,17 +414,6 @@ class QytOil extends BaseService
return bcdiv((string) (int) $fen, '100', 2);
}
/**
* 生成企油通全平台唯一编号(加企业前缀),确定性派生 -> 重试保持一致
* @param string $type B=batch / W=waybill / C=waybill_no_code
* @param string $seed 订单号 / order_id 等业务种子
*/
public static function genNo(string $type, string $seed): string
{
$prefix = (string) (config('qyt.no_prefix') ?: 'HM');
return $prefix . $type . $seed;
}
/** 记录日志到 runtime/storelog */
public function saveLog($data): void
{
@@ -5,12 +5,15 @@
// | OilCard::share 创建 yoshop_partners_order 后 push 'partners_queue',本命令消费:
// | 1. 每个 order_id 用 RedisLock 串行化,锁内重读订单状态
// | 2. 生成企油通全平台唯一编号(确定性派生,可安全重试)
// | 3. 调 7.7 createAndSendQuota;支付宝企业码通道下「受理成功即完成发放」
// | 4. 发放成功 -> oil_status=1 / oil_status_zsy=2,投 notify_queue 回调下单平台
// | 3. 调 7.7 createAndSendQuota
// | 4. 受理成功 -> oil_status_zsy=1「发放中」,交给 qyt:status 轮询 7.8 定结果
// |
// | ⚠️ 7.7 是异步接口,返回成功只代表【受理】(文档 §5 第 7 条)。本命令不再直接
// | 判定发放成功,也不再投 notify_queue —— 那是 QytStatusQueue 拿到 7.8 结果后的事。
// |
// | 并发/原子性:
// | · oil_status_zsy 只在 7.7 结果确定后离开 0;空闲时 reconcile() 补投丢失的队列消息
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才回调,天然幂等
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才继续,天然幂等
// | · batch_no 确定性派生 -> 崩溃重试用同一值,依赖企油通按 batch_no 幂等去重
// | 启动:php think partners:queue(可多进程;RedisLock 保证同一订单串行)
// +----------------------------------------------------------------------
@@ -18,6 +21,7 @@ namespace app\console\command;
use app\common\lock\RedisLock;
use app\common\model\partners\Order as OrderModel;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytOil;
@@ -82,25 +86,57 @@ class PartnersQueue extends Command
return;
}
// 1. 确定性派生编号(含企业前缀),重试时保持一致
$seed = $order['partners_order_no'] ?: (string) $order['order_id'];
$batchNo = QytOil::genNo('B', $seed);
$waybillNo = QytOil::genNo('W', $seed);
$waybillNoCode = QytOil::genNo('C', $seed);
// 用下单时固化在订单上的企业,而不是当前默认企业:
// 之后改了合作商绑定或切了默认企业,在途订单仍按原企业发放
$enterprise = EnterpriseModel::detail((int) $order['qyt_enterprise_id']);
if (!$enterprise) {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update(['oil_status_zsy' => 3, 'oil_text' => '本单绑定的企油通企业不存在,无法发放', 'update_time' => time()]);
$failed && $this->refundBalance($order);
return;
}
$qyt->useEnterprise($enterprise);
// 1. 编号:库里已生成过就直接沿用,绝不重算。
// no_prefix 现在可在后台修改,若重试时重新派生,batch_no 会变成另一个值,
// 上游会当成新批次 -> 同一订单被发放两次。
// 种子里带上重发次数:后台「重新发放」会清空编号并把 qyt_retry_num +1,
// 这样重发能拿到全新的 batch_no(否则企油通按老批次幂等,7.8 永远返回那条旧的失败记录)。
$retryNum = (int) $order['qyt_retry_num'];
$seed = ($order['partners_order_no'] ?: (string) $order['order_id'])
. ($retryNum > 0 ? 'R' . $retryNum : '');
$batchNo = $order['batch_no'] ?: $qyt->no('B', $seed);
$waybillNo = $order['waybill_no'] ?: $qyt->no('W', $seed);
$waybillNoCode = $order['waybill_no_code'] ?: $qyt->no('C', $seed);
$name = trim((string) $order['driver_name']);
$idCard = trim((string) $order['id_card']);
if ($name === '' || $order['mobile'] === '') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update(['oil_status_zsy' => 3, 'oil_text' => '缺司机姓名/手机号,无法发放', 'update_time' => time()]);
$failed && $this->refundBalance($order);
return;
}
// 通道切换后,下单时不需要身份证的老单可能落到实名通道上:
// 直接判失败退款,不做无意义重试(重试也永远补不到身份证号)
if ($qyt->requireIdCard() && $idCard === '') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update([
'oil_status_zsy' => 3,
'oil_text' => '当前通道(' . $qyt->channelName() . ')要求实名,该单缺司机身份证号',
'update_time' => time(),
]);
$failed && $this->refundBalance($order);
return;
}
// 2. 先落编号(状态不动,仍为 0)
// 2. 先落编号 + 本单实际使用的支付通道(状态不动,仍为 0)
// 通道随单落库:切换通道后老单仍能追溯当时走的是哪条通道
Db::name('partners_order')->where('order_id', $orderId)->update([
'batch_no' => $batchNo,
'waybill_no' => $waybillNo,
'waybill_no_code' => $waybillNoCode,
'oil_text' => '发放中',
'passageway' => $qyt->channelKey(),
'oil_text' => '发放中(' . $qyt->channelName() . ')',
'update_time' => time(),
]);
@@ -111,6 +147,7 @@ class PartnersQueue extends Command
'waybill_no_code' => $waybillNoCode,
'mobile' => $order['mobile'],
'name' => $name,
'id_card' => $idCard,
'amount' => $order['total_price'],
]);
@@ -137,24 +174,20 @@ class PartnersQueue extends Command
return;
}
// 4. 受理成功 = 发放成功(支付宝企业码通道);条件跃迁 0 -> 2,胜出者回调下单平台
// 4. 受理成功【不等于】发放成功:7.7 是异步接口,真实结果要用 7.8 按 batch_no 查。
// 这里只把状态推进到「发放中」,由 qyt:status 轮询 7.8 后才决定成功/失败并回调下游。
$affected = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
'oil_status' => 1,
'oil_status_zsy' => 2,
'oil_time' => time(),
'oil_status_zsy' => 1,
'shareNo' => $waybillNoCode,
'oil_text' => $res['data']['msg'] ?? '发放成功',
'oil_text' => '已受理,等待企油通发放结果'
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已受理处理)'),
'qyt_query_time' => 0,
'qyt_query_num' => 0,
'update_time' => time(),
]);
if ($affected) {
$this->queueService->push('notify_queue', json_encode([
'order_id' => $orderId,
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$qyt->saveLog("7.7 发放成功 order={$orderId} batch_no={$batchNo}"
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已发放处理)'));
$qyt->saveLog("7.7 已受理 order={$orderId} batch_no={$batchNo} 通道={$qyt->channelName()}"
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已受理处理)'));
}
} catch (\Throwable $e) {
// 不改状态(仍为 0),交由 reconcile 兜底重投
@@ -0,0 +1,258 @@
<?php
// +----------------------------------------------------------------------
// | 企油通「运单发放结果」轮询 (7.8 waybillBatch)
// +----------------------------------------------------------------------
// | 为什么需要它:7.7 运单油费下发是【异步】接口,返回成功只代表受理,
// | 真实发放结果必须按 batch_no 调 7.8 查(《企油通接口对接文档.md》§5 第 7 条)。
// |
// | 状态流转(oil_status_zsy):
// | 0 未提交 --PartnersQueue 调7.7受理--> 1 发放中 --本命令查7.8--> 2 成功 / 3 失败
// |
// | 7.8 单条 handle_status: 1 待处理 / 2 成功 / 3 失败
// | 7.8 整批 status: 1 待处理 / 2 处理中 / 3 全部成功 / 4 部分成功 / 5 处理失败
// |
// | 并发/原子性:
// | · 每个 order_id 用 RedisLock 串行化,可多进程运行
// | · 状态跃迁是条件 UPDATE(... WHERE oil_status_zsy=1),affected=1 才回调/退款,天然幂等
// |
// | ⚠️ 绝不因为「查不到结果」而自动判失败退款:
// | 钱可能已经发到司机手里,自动退款会造成资损。查询失败或长期待处理只会持续
// | 重试并在日志里告警,由人工介入处理。
// |
// | 启动:php think qyt:status
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\console\command;
use app\common\lock\RedisLock;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytOil;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class QytStatusQueue extends Command
{
/** 每轮扫描的最大订单数 */
const BATCH_LIMIT = 100;
/** 空转时的休眠秒数 */
const IDLE_SLEEP = 5;
/** 查询次数达到该值后仍未定论,视为异常滞留,写告警日志 */
const STUCK_NUM = 100;
/** @var QueueService */
protected $queueService;
protected function configure()
{
$this->setName('qyt:status')
->setDescription('轮询企油通 7.8 运单发放结果,确定订单最终发放态');
}
protected function execute(Input $input, Output $output)
{
$this->queueService = new QueueService();
while (true) {
$handled = 0;
try {
$handled = $this->scan();
} catch (\Throwable $e) {
$this->log('轮询主循环异常:' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
}
// 有活干就立刻进入下一轮,没有就歇一会儿
$handled === 0 && sleep(self::IDLE_SLEEP);
}
}
/**
* 扫描一批「发放中」的订单并查询结果
* @return int 本轮实际处理的订单数
*/
protected function scan(): int
{
$now = time();
// 按退避间隔挑出到点该查的单:qyt_query_time + interval(num) <= now
$ids = Db::name('partners_order')
->where('oil_status_zsy', 1)
->where('order_status', 10)
->whereRaw('qyt_query_time + ' . $this->intervalSql() . ' <= ' . $now)
->order('qyt_query_time', 'asc')
->limit(self::BATCH_LIMIT)
->column('order_id');
foreach ($ids as $orderId) {
$this->handleOne((int) $orderId);
}
return count($ids);
}
/**
* 退避间隔(秒)的 SQL 表达式:查得越久间隔越长,避免高频打上游
* 前 10 次 30s,10~30 次 120s,之后 600s
*/
protected function intervalSql(): string
{
return 'CASE WHEN qyt_query_num < 10 THEN 30 WHEN qyt_query_num < 30 THEN 120 ELSE 600 END';
}
/**
* 查询单个订单的发放结果
*/
protected function handleOne(int $orderId): void
{
$lock = new RedisLock('qyt:status:' . $orderId, 60);
if (!$lock->acquireLock()) {
return;
}
try {
$order = Db::name('partners_order')->where('order_id', $orderId)->find();
// 锁内重读:可能已被其它进程处理完
if (!$order || (int) $order['oil_status_zsy'] !== 1) {
return;
}
if (empty($order['batch_no']) || empty($order['waybill_no_code'])) {
$this->log("order={$orderId} 处于发放中但缺少 batch_no/waybill_no_code,无法查询,需人工核对");
$this->touch($orderId);
return;
}
$enterprise = EnterpriseModel::detail((int) $order['qyt_enterprise_id']);
if (!$enterprise) {
// 不判失败:单子可能真的已经发出去了,只是企业记录被删了
$this->log("order={$orderId} 绑定的企业不存在,无法查询 7.8,需人工核对");
$this->touch($orderId);
return;
}
$qyt = QytOil::forEnterprise($enterprise);
$res = $qyt->waybillBatch((string) $order['batch_no']);
// 记一次查询(无论成败都要更新时间,否则会死循环打上游)
$this->touch($orderId);
if ($res['code'] !== '00') {
$this->log("order={$orderId} 7.8 查询失败:{$res['message']}(保持发放中,稍后重试)");
$this->alertIfStuck($order, $qyt);
return;
}
$batchStatus = (int) ($res['data']['status'] ?? 0);
$row = QytOil::pickWaybill($res['data'], (string) $order['waybill_no_code']);
// 整批处理失败,且明细里没有本条 -> 判失败
if (!$row && $batchStatus === 5) {
$this->markFailed($order, '企油通返回整批处理失败', $qyt);
return;
}
// 明细里还没有本条:批次还在排队,继续等
if (!$row) {
$this->alertIfStuck($order, $qyt);
return;
}
switch ($row['handle_status']) {
case 2: // 成功
$this->markSuccess($order, $row['remark'] ?: '发放成功', $qyt);
break;
case 3: // 失败
$this->markFailed($order, $row['error_msg'] ?: '企油通发放失败', $qyt);
break;
default: // 1 待处理,或未知值 -> 继续等
$this->alertIfStuck($order, $qyt);
break;
}
} catch (\Throwable $e) {
$this->log("order={$orderId} 轮询异常:" . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
} finally {
$lock->releaseLock();
}
}
/**
* 发放成功:条件跃迁 1 -> 2,胜出者才回调下单平台
*/
protected function markSuccess(array $order, string $text, QytOil $qyt): void
{
$affected = Db::name('partners_order')
->where('order_id', $order['order_id'])
->where('oil_status_zsy', 1)
->update([
'oil_status' => 1,
'oil_status_zsy' => 2,
'oil_time' => time(),
'oil_text' => $text,
'update_time' => time(),
]);
if (!$affected) {
return;
}
$this->queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$qyt->saveLog("7.8 确认发放成功 order={$order['order_id']} batch_no={$order['batch_no']}"
. " 查询次数={$order['qyt_query_num']}");
}
/**
* 发放失败:条件跃迁 1 -> 3,胜出者才退款(保证只退一次)
*/
protected function markFailed(array $order, string $reason, QytOil $qyt): void
{
$affected = Db::name('partners_order')
->where('order_id', $order['order_id'])
->where('oil_status_zsy', 1)
->update([
'oil_status_zsy' => 3,
'oil_text' => '发放失败:' . $reason,
'update_time' => time(),
]);
if (!$affected) {
return;
}
PartnersBalance::refund(
(int) $order['partners_id'],
(float) $order['total_price'],
(string) $order['partners_order_no'],
(int) $order['order_id'],
(int) $order['store_id']
);
$qyt->saveLog("7.8 确认发放失败 order={$order['order_id']} 原因={$reason},已回补合作商余额 {$order['total_price']}");
}
/**
* 长期拿不到结果时告警。只写日志,不改状态、不退款
*/
protected function alertIfStuck(array $order, QytOil $qyt): void
{
$num = (int) $order['qyt_query_num'] + 1;
if ($num < self::STUCK_NUM || $num % 20 !== 0) {
return;
}
$qyt->saveLog("【告警】order={$order['order_id']} batch_no={$order['batch_no']} 已查询 {$num} 次"
. '仍未拿到 7.8 结果,长期处于「发放中」。不会自动判失败(款可能已发出),请人工到企油通后台核对。');
}
/** 记录一次查询:更新时间与次数,驱动退避 */
protected function touch(int $orderId): void
{
Db::name('partners_order')->where('order_id', $orderId)->update([
'qyt_query_time' => time(),
'qyt_query_num' => Db::raw('qyt_query_num + 1'),
]);
}
protected function log(string $text): void
{
(new QytOil())->saveLog('qyt:status ' . $text);
}
}
@@ -15,6 +15,7 @@ namespace app\store\controller\partners;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\QytOil;
use app\store\controller\Controller;
use app\store\model\order\QrCode as QrCodeModel;
@@ -71,7 +72,15 @@ class Order extends Controller
if ($order['oil_status'] != 1) {
return $this->renderError('该单尚未发放成功,无法获取加油地址');
}
$h5Url = (new QytOil())->h5Url((string) $order['mobile']);
// 免登链接要带本单企业的 creditcode(营业执照号),把登录固定到该企业
$enterprise = EnterpriseModel::detail((int) $order['qyt_enterprise_id']);
if (!$enterprise) {
return $this->renderError('本单绑定的企油通企业不存在,无法生成加油地址');
}
if ($enterprise['status'] !== 'approved') {
return $this->renderError('企业「' . $enterprise['company_name'] . '」尚未通过企油通审核,无法生成加油地址');
}
$h5Url = QytOil::forEnterprise($enterprise)->h5Url((string) $order['mobile']);
if (!$h5Url) {
return $this->renderError('生成加油地址失败,请检查企油通配置');
}
@@ -115,11 +124,23 @@ class Order extends Controller
if ($logId === false) {
return $this->renderError('合作商余额不足,请先充值后再重发');
}
// 仅允许从「发放失败(3)」重置为「未提交(0)」,避免与正在处理的单冲突
// 仅允许从「发放失败(3)」重置为「未提交(0)」,避免与「发放中(1)」的单冲突。
// 同时清空编号并把重发次数 +1:企油通按 batch_no 幂等,沿用老批次的话
// 7.8 会一直返回那条旧的失败记录,重发永远不可能成功。
$reset = Db::name('partners_order')->where('order_id', $orderId)
->where('oil_status', 0)
->where('oil_status_zsy', 3)
->update(['oil_status_zsy' => 0, 'oil_text' => '后台重新发放', 'update_time' => time()]);
->update([
'oil_status_zsy' => 0,
'batch_no' => '',
'waybill_no' => '',
'waybill_no_code' => '',
'qyt_retry_num' => Db::raw('qyt_retry_num + 1'),
'qyt_query_time' => 0,
'qyt_query_num' => 0,
'oil_text' => '后台重新发放',
'update_time' => time(),
]);
if (!$reset) {
// 状态已被其它进程改变 -> 回补刚扣的余额
PartnersBalance::refund(
@@ -0,0 +1,242 @@
<?php
// +----------------------------------------------------------------------
// | 后台 · 企油通企业管理
// +----------------------------------------------------------------------
// | list/detail/add/edit/delete/setDefault 本地维护
// | apply() 7.2 企业申请创建(提交到企油通)
// | syncAccount() 7.5 企业账户信息查询(回填开户状态)
// | syncList() 7.22 企业列表查询(拉取本 appId 名下企业,需先开通权限)
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\store\controller\qyt;
use app\common\model\qyt\Enterprise as CommonEnterprise;
use app\common\service\QytOil;
use app\store\controller\Controller;
use app\store\model\qyt\Enterprise as EnterpriseModel;
use think\facade\Db;
use think\response\Json;
class Enterprise extends Controller
{
/**
* 企业列表
*/
public function list(): Json
{
$model = new EnterpriseModel;
$list = $model->getList($this->request->param());
return $this->renderSuccess(compact('list'));
}
/**
* 下拉选项(合作商绑定企业时用)
*/
public function options(): Json
{
return $this->renderSuccess(['list' => EnterpriseModel::options()]);
}
/**
* 企业详情
*/
public function detail(int $enterpriseId): Json
{
$detail = EnterpriseModel::detail($enterpriseId);
if (!$detail) {
return $this->renderError('企业不存在');
}
return $this->renderSuccess(compact('detail'));
}
/**
* 新增企业
*/
public function add(): Json
{
$model = new EnterpriseModel;
if ($model->add($this->postForm())) {
return $this->renderSuccess('添加成功');
}
return $this->renderError($model->getError() ?: '添加失败');
}
/**
* 编辑企业
*/
public function edit(int $enterpriseId): Json
{
$model = EnterpriseModel::detail($enterpriseId);
if (!$model) {
return $this->renderError('企业不存在');
}
if ($model->edit($this->postForm())) {
return $this->renderSuccess('更新成功');
}
return $this->renderError($model->getError() ?: '更新失败');
}
/**
* 删除企业
*/
public function delete(int $enterpriseId): Json
{
$model = EnterpriseModel::detail($enterpriseId);
if (!$model) {
return $this->renderError('企业不存在');
}
if ($model->remove()) {
return $this->renderSuccess('删除成功');
}
return $this->renderError($model->getError() ?: '删除失败');
}
/**
* 设为默认企业(合作商未绑定企业时的兜底)
*/
public function setDefault(int $enterpriseId): Json
{
$model = EnterpriseModel::detail($enterpriseId);
if (!$model) {
return $this->renderError('企业不存在');
}
if ($model->setDefault()) {
return $this->renderSuccess('设置成功');
}
return $this->renderError($model->getError() ?: '设置失败');
}
/**
* 7.2 企业申请创建:把本地企业提交到企油通开户
*/
public function apply(int $enterpriseId): Json
{
$model = EnterpriseModel::detail($enterpriseId);
if (!$model) {
return $this->renderError('企业不存在');
}
if (empty($model['business_license_url'])) {
return $this->renderError('请先上传营业执照照片地址(7.2 必填)');
}
// 申请可同时开通多个通道,文档要求逗号分隔的编码,如 "1,2"
$codes = $model['bound_passageway'] !== ''
? $model['bound_passageway']
: (string) CommonEnterprise::PASSAGEWAY_CODE[$model['passageway']];
$res = QytOil::forEnterprise($model)->openAnAccount([
'business_license' => $model['business_license'],
'business_license_url' => $model['business_license_url'],
'passageway' => $codes,
'company_name' => $model['company_name'],
'contact_mobile' => $model['contact_mobile'],
'contact_name' => $model['contact_name'],
]);
if ($res['code'] !== '00') {
$this->saveSync($model, '申请失败:' . $res['message']);
return $this->renderError('企油通返回:' . ($res['message'] ?: '申请失败'));
}
// status: 0 失败 / 1 待审核 / 2 审核成功
$status = (int) ($res['data']['status'] ?? 0);
$map = [0 => 'rejected', 1 => 'submitted', 2 => 'approved'];
$model->save([
'enterprise_number' => (string) ($res['data']['enterprise_number'] ?? $model['enterprise_number']),
'status' => $map[$status] ?? 'submitted',
'bound_passageway' => $codes,
'sync_time' => time(),
'sync_text' => (string) ($res['data']['msg'] ?? '已提交'),
'update_time' => time(),
]);
return $this->renderSuccess((string) ($res['data']['msg'] ?? '已提交,等待企油通审核'));
}
/**
* 7.5 企业账户信息查询:回填开户行 / 账户 / 开户状态
*/
public function syncAccount(int $enterpriseId): Json
{
$model = EnterpriseModel::detail($enterpriseId);
if (!$model) {
return $this->renderError('企业不存在');
}
$res = QytOil::forEnterprise($model)->accountQuery();
if ($res['code'] !== '00') {
$this->saveSync($model, '账户查询失败:' . $res['message']);
return $this->renderError('企油通返回:' . ($res['message'] ?: '查询失败'));
}
$d = $res['data'];
$model->save([
'account' => (string) ($d['account'] ?? ''),
'account_bank' => (string) ($d['account_bank'] ?? ''),
'account_name' => (string) ($d['account_name'] ?? ''),
'account_status' => (int) ($d['status'] ?? 0),
'company_name' => $d['enterprise_name'] ?? $model['company_name'],
'sync_time' => time(),
'sync_text' => (int) ($d['status'] ?? 0) === 2 ? '开户成功' : '审核中',
'update_time' => time(),
]);
return $this->renderSuccess('同步成功');
}
/**
* 7.22 企业列表查询:拉取本 appId 名下企业,本地没有的自动建档
* 需先向企油通开通「企业列表查询权限」,未开通会返回 403
*/
public function syncList(): Json
{
$res = (new QytOil())->enterpriseList(['page' => 1, 'page_size' => 100]);
if ($res['code'] !== '00') {
return $this->renderError('企油通返回:' . ($res['message'] ?: '查询失败')
. '(该接口需先开通企业列表查询权限)');
}
$rows = (array) ($res['data']['list'] ?? []);
$added = $updated = 0;
foreach ($rows as $row) {
$license = trim((string) ($row['business_license'] ?? ''));
if ($license === '') {
continue;
}
// 已绑定通道数组如 [1,2],转成本地逗号串
$bound = implode(',', array_map('intval', (array) ($row['passageway'] ?? [])));
$exist = Db::name('qyt_enterprise')->where('business_license', $license)->find();
if ($exist) {
Db::name('qyt_enterprise')->where('enterprise_id', $exist['enterprise_id'])->update([
'company_name' => (string) ($row['enterprise_name'] ?? $exist['company_name']),
'status' => (string) ($row['status'] ?? $exist['status']),
'bound_passageway' => $bound ?: $exist['bound_passageway'],
'sync_time' => time(),
'sync_text' => '已从企业列表同步',
'update_time' => time(),
]);
$updated++;
continue;
}
// 新企业建档:默认走支付宝企业码,机构号需人工补填后才能下发
Db::name('qyt_enterprise')->insert([
'company_name' => (string) ($row['enterprise_name'] ?? ''),
'business_license' => $license,
'passageway' => in_array(2, (array) ($row['passageway'] ?? []), true) && !in_array(1, (array) ($row['passageway'] ?? []), true)
? 'pingan' : 'alipay',
'bound_passageway' => $bound,
'status' => (string) ($row['status'] ?? 'pending'),
'is_enable' => 1,
'is_default' => 0,
'sort' => 100,
'sync_time' => time(),
'sync_text' => '由企业列表自动建档,请补填机构号',
'store_id' => $this->storeId,
'create_time' => time(),
'update_time' => time(),
]);
$added++;
}
return $this->renderSuccess("同步完成:新增 {$added} 家,更新 {$updated} 家");
}
/** 记录同步结果 */
private function saveSync($model, string $text): void
{
$model->save(['sync_time' => time(), 'sync_text' => $text, 'update_time' => time()]);
}
}
+26 -1
View File
@@ -63,12 +63,37 @@ class Setting extends SettingModel
$callback = [
'store' => function ($values) {
return $this->validStore($values);
}
},
SettingEnum::QYT => function ($values) {
return $this->validQyt($values);
},
];
// 验证商城设置
return isset($callback[$key]) ? $callback[$key]($values) : true;
}
/**
* 验证企油通对接设置
* 只做「填错了会让发放整体失败」的强校验,留空一律回落 .env,不强制必填
* @param array $values
* @return bool
*/
private function validQyt(array $values)
{
foreach (['base_url' => '接口域名', 'h5_base_url' => 'H5落地页地址'] as $key => $name) {
$url = trim((string) ($values[$key] ?? ''));
if ($url !== '' && !preg_match('#^https?://#i', $url)) {
$this->error = "{$name}必须以 http:// 或 https:// 开头";
return false;
}
}
if (rtrim(trim((string) ($values['base_url'] ?? '')), '/') !== trim((string) ($values['base_url'] ?? ''))) {
$this->error = '接口域名结尾不要带斜杠';
return false;
}
return true;
}
/**
* 验证商城设置
* @param array $values
+14 -1
View File
@@ -13,6 +13,7 @@ declare (strict_types=1);
namespace app\store\model\partners;
use app\common\model\partners\Order as OrderModel;
use app\common\service\QytOil;
/**
* 合作商订单
@@ -21,6 +22,18 @@ use app\common\model\partners\Order as OrderModel;
*/
class Order extends OrderModel
{
/**
* 身份证号脱敏(仅后台展示层)
* 注意:本访问器只挂在 store 模型上;发放流程用的是 app\common\model\partners\Order,
* 取到的仍是明文,不会把打码后的身份证号发给企油通。
* @param string|null $value
* @return string
*/
public function getIdCardAttr($value): string
{
return QytOil::maskIdCard((string) $value);
}
/**
* 获取余额变动明细列表
* @param array $param
@@ -64,7 +77,7 @@ class Order extends OrderModel
'driver_name' => '', // 司机姓名
'orderNo' => '', // 单号:油卡编号/下单平台单号/批次号/运单号
'oil_status' => -1, // 分油(发放)状态
'oil_status_zsy' => -1, // 企油通发放态 0未提交 2成功 3失败
'oil_status_zsy' => -1, // 企油通发放态 0未提交 1发放中 2成功 3失败
'use_status' => -1, // 加油核销状态
'betweenTime' => [] // 起止时间
]);
@@ -0,0 +1,166 @@
<?php
// +----------------------------------------------------------------------
// | 后台 · 企油通企业模型
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\store\model\qyt;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use think\facade\Db;
class Enterprise extends EnterpriseModel
{
/**
* 列表
*/
public function getList(array $param = [])
{
$filter = [];
if (!empty($param['name'])) {
$filter[] = ['company_name|business_license', 'like', '%' . trim($param['name']) . '%'];
}
if (isset($param['status']) && $param['status'] !== '' && $param['status'] !== 'all') {
$filter[] = ['status', '=', $param['status']];
}
if (isset($param['passageway']) && $param['passageway'] !== '' && $param['passageway'] !== 'all') {
$filter[] = ['passageway', '=', $param['passageway']];
}
return $this->where($filter)
->order(['is_default' => 'desc', 'sort' => 'asc', 'enterprise_id' => 'desc'])
->paginate(15);
}
/**
* 全部启用中的企业(下拉选择用)
*/
public static function options(): array
{
return (new static)->where('is_enable', 1)
->order(['is_default' => 'desc', 'sort' => 'asc'])
->field(['enterprise_id', 'company_name', 'business_license', 'passageway', 'status', 'is_default'])
->select()->toArray();
}
/**
* 新增
*/
public function add(array $data): bool
{
if (!$this->validForm($data)) {
return false;
}
$data['store_id'] = self::$storeId;
$data['create_time'] = time();
$data['update_time'] = time();
return $this->transaction(function () use ($data) {
$ok = $this->save($data) !== false;
// 设为默认时,把其他企业的默认标记清掉
$ok && !empty($data['is_default']) && $this->clearOtherDefault((int) $this->enterprise_id);
return $ok;
});
}
/**
* 编辑
*/
public function edit(array $data): bool
{
if (!$this->validForm($data, (int) $this['enterprise_id'])) {
return false;
}
$data['update_time'] = time();
return $this->transaction(function () use ($data) {
$ok = $this->save($data) !== false;
$ok && !empty($data['is_default']) && $this->clearOtherDefault((int) $this['enterprise_id']);
return $ok;
});
}
/**
* 删除:已产生订单的企业不允许删除,改用停用
*/
public function remove(): bool
{
$used = Db::name('partners_order')->where('qyt_enterprise_id', $this['enterprise_id'])->count();
if ($used > 0) {
$this->error = "该企业已产生 {$used} 笔订单,不能删除;如需停用请把「启用」关掉";
return false;
}
$bound = Db::name('partners')->where('qyt_enterprise_id', $this['enterprise_id'])->count();
if ($bound > 0) {
$this->error = "还有 {$bound} 个合作商绑定了该企业,请先改绑";
return false;
}
if ($this['is_default']) {
$this->error = '默认企业不能删除,请先把默认设到其他企业';
return false;
}
return $this->delete();
}
/**
* 设为默认
*/
public function setDefault(): bool
{
if (!$this['is_enable']) {
$this->error = '已停用的企业不能设为默认';
return false;
}
return $this->transaction(function () {
$this->save(['is_default' => 1, 'update_time' => time()]);
$this->clearOtherDefault((int) $this['enterprise_id']);
return true;
});
}
/** 清掉其他企业的默认标记 */
protected function clearOtherDefault(int $keepId): void
{
Db::name('qyt_enterprise')
->where('enterprise_id', '<>', $keepId)
->where('is_default', 1)
->update(['is_default' => 0, 'update_time' => time()]);
}
/**
* 表单校验
*/
protected function validForm(array $data, int $selfId = 0): bool
{
if (empty($data['company_name'])) {
$this->error = '请填写企业名称';
return false;
}
$license = trim((string) ($data['business_license'] ?? ''));
if ($license === '') {
$this->error = '请填写统一社会信用代码';
return false;
}
if (!preg_match('/^[0-9A-Z]{18}$/', $license)) {
$this->error = '统一社会信用代码应为 18 位大写字母或数字,请检查是否混入了空格或括号';
return false;
}
$exist = Db::name('qyt_enterprise')->where('business_license', $license);
$selfId > 0 && $exist->where('enterprise_id', '<>', $selfId);
if ($exist->count() > 0) {
$this->error = '该统一社会信用代码已存在';
return false;
}
if (!isset(self::PASSAGEWAY_CODE[$data['passageway'] ?? ''])) {
$this->error = '支付通道取值不合法';
return false;
}
// 支付宝企业码通道必须有机构号,否则每一单都会在下发时失败并退款
if (($data['passageway'] ?? '') === 'alipay' && trim((string) ($data['institution_id'] ?? '')) === '') {
$this->error = '支付宝企业码通道必须填写机构号(institution_id)';
return false;
}
if (!empty($data['contact_mobile']) && !preg_match('/^1[3-9]\d{9}$/', (string) $data['contact_mobile'])) {
$this->error = '企业联系人电话格式不正确';
return false;
}
return true;
}
}
+1
View File
@@ -11,6 +11,7 @@ return [
'callback:queue' => \app\console\command\CallbackQueue::class,
'card:queue' => \app\console\command\CardQueue::class,
'partners:queue' => \app\console\command\PartnersQueue::class,
'qyt:status' => \app\console\command\QytStatusQueue::class,
'notify:queue' => \app\console\command\NotifyQueue::class,
],
];
+12 -7
View File
@@ -26,13 +26,18 @@ return [
'timeout' => 15,
// ============ 企业(单企业,线下已开户,固定支付宝企业码通道) ============
// ★ 营业执照编码 / 统一社会信用代码
'business_license' => env('qyt.business_license', ''),
// ★ 机构号:支付宝企业码通道 7.7 必填,待企油通系统分配
'institution_id' => env('qyt.institution_id', ''),
// ============ 企业(已迁移到「后台 → 企业管理」,见 yoshop_qyt_enterprise) ============
//
// 依据《企油通接口对接文档.md》:appId 与 RSA 密钥属于「接入方(慧明)」级别,
// 一个 appId 下可挂多个企业,业务接口靠 business_license 区分。因此以下参数
// 不再放配置文件,改为每个企业各存一份:
// business_license 统一社会信用代码
// passageway 通道开关(alipay=1 支付宝企业码 / pingan=2 平安银行)
// institution_id 机构号(支付宝企业码通道必填)
//
// 通道编码是文档固定枚举(§2.5),不做配置项:1 支付宝企业码 / 2 平安银行
// (3 智慧油客已停用,勿传;4 企油通仅出现在 7.22 返回值中)。
// 通道与实名的关系也由文档确定:平安银行通道 7.7 必传 id_card。
// ============ 编号 / 业务参数 ============
+20 -1
View File
@@ -1,8 +1,11 @@
; ============================================================
; 企油通对接 · Supervisor 守护进程配置
; ------------------------------------------------------------
; 三个常驻队列消费者,缺一不可:
; 四个常驻进程,缺一不可:
; partners:queue 下单 -> 企油通 7.7 运单油费下发(含丢单兜底巡检 reconcile)
; qyt:status 轮询 7.8 运单发放结果,确定订单最终发放态并回调下游
; (7.7 是异步接口,受理成功不等于发放成功,缺了这个进程
; 订单会永远停在「发放中」,下游拿不到结果)
; callback:queue 企油通 7.10 加油消费回调入队后的落库与状态更新
; notify:queue 我方 -> 下单平台 结果回调(含 60s/5min/30min 退避重试)
;
@@ -34,6 +37,22 @@ stdout_logfile=/www/wwwlogs/supervisor/qyt-partners-queue.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=5
[program:qyt-status-queue]
command=/www/server/php/74/bin/php think qyt:status
directory=/www/wwwroot/www.hmzkw.com
user=www
numprocs=1
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
startsecs=3
startretries=99
stopwaitsecs=10
redirect_stderr=true
stdout_logfile=/www/wwwlogs/supervisor/qyt-status-queue.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=5
[program:qyt-callback-queue]
command=/www/server/php/74/bin/php think callback:queue
directory=/www/wwwroot/www.hmzkw.com
+167 -13
View File
@@ -6,11 +6,13 @@
* 放在 public/ 下,浏览器访问:http://你的域名/qyt_test.php
* 也可命令行:php qyt_test.php share
*
* 它做四件事:
* 它做五件事:
* 1) 从数据库读取合作商(appKey / RSA 私钥 / orgNo)
* 2) 按「慧明宝」规范组装 RSA+AES 信封并调用我方入站接口
* 3) 展示明文请求 / 待签串 / 密文报文 / 服务端响应
* 4) act=notify_receiver 作为回调接收端,解密+验签我方推送的
* 2) 展示本次下单会用哪个【企油通企业】及其支付通道(口径同后端:
* 合作商绑定的企业 -> 默认企业),含是否要求身份证号、机构号是否已配置
* 3) 按「慧明宝」规范组装 RSA+AES 信封并调用我方入站接口
* 4) 展示明文请求 / 待签串 / 密文报文 / 服务端响应
* 5) act=notify_receiver 作为回调接收端,解密+验签我方推送的
* syb.notify.oilCard.change,并返回 {"code":"00"}
*
* ⚠️ 仅用于联调,正式环境请删除本文件。
@@ -70,6 +72,87 @@ function getPartner($root, $appKey)
return $row;
}
/**
* 解析某个合作商下单时实际使用的企油通企业与通道。
* 口径与 app\common\model\qyt\Enterprise::resolve 一致:
* 合作商绑定的企业 -> 默认企业(is_default=1 且启用)
* 接入方级参数(域名 / appId / 密钥)仍取 .env 的 [QYT] 段与后台设置。
* 本脚本是独立入口、不加载 ThinkPHP,所以重新实现了一遍,改动时两边要同步。
*/
function qytConf($root, $partner)
{
static $conf = null;
if ($conf !== null) {
return $conf;
}
$ini = parse_ini_file($root . '/.env', true, INI_SCANNER_RAW) ?: [];
$q = isset($ini['QYT']) ? $ini['QYT'] : [];
$get = function ($k, $d = '') use ($q) {
return isset($q[$k]) && trim((string)$q[$k]) !== '' ? trim((string)$q[$k]) : $d;
};
$conf = [
'base_url' => $get('BASE_URL'),
'no_prefix' => $get('NO_PREFIX', 'HM'),
'source' => '.env',
'enterprise' => null,
];
// 后台「企油通对接设置」覆盖接入方级默认值
try {
$st = db($root)->prepare("SELECT `values` FROM yoshop_store_setting WHERE `key`='qyt' ORDER BY store_id ASC LIMIT 1");
$st->execute();
$json = $st->fetchColumn();
$row = $json ? json_decode($json, true) : null;
if (is_array($row)) {
$conf['source'] = '.env + 后台设置';
foreach (['base_url', 'no_prefix'] as $k) {
if (isset($row[$k]) && trim((string)$row[$k]) !== '') {
$conf[$k] = trim((string)$row[$k]);
}
}
}
} catch (Throwable $e) {
// 忽略
}
// 企业:合作商绑定的 -> 默认企业
try {
$pdo = db($root);
$ent = null;
$bind = (int)(isset($partner['qyt_enterprise_id']) ? $partner['qyt_enterprise_id'] : 0);
if ($bind > 0) {
$st = $pdo->prepare('SELECT * FROM yoshop_qyt_enterprise WHERE enterprise_id = ? AND is_enable = 1 LIMIT 1');
$st->execute([$bind]);
$ent = $st->fetch() ?: null;
}
if (!$ent) {
$st = $pdo->query('SELECT * FROM yoshop_qyt_enterprise WHERE is_enable = 1 AND is_default = 1 ORDER BY enterprise_id ASC LIMIT 1');
$ent = $st->fetch() ?: null;
$conf['from_default'] = (bool)$ent;
} else {
$conf['from_default'] = false;
}
if ($ent) {
$pw = isset($ent['passageway']) && $ent['passageway'] === 'pingan' ? 'pingan' : 'alipay';
$ent['passageway'] = $pw;
$ent['passageway_code'] = $pw === 'pingan' ? 2 : 1;
$ent['passageway_name'] = $pw === 'pingan' ? '平安银行' : '支付宝企业码';
$ent['require_id_card'] = $pw === 'pingan';
// 企业可覆盖编号前缀
if (trim((string)$ent['no_prefix']) !== '') {
$conf['no_prefix'] = trim((string)$ent['no_prefix']);
}
$conf['enterprise'] = $ent;
}
} catch (Throwable $e) {
$conf['enterprise_error'] = $e->getMessage();
}
return $conf;
}
/** 平台公钥:加密 AES 密钥 / 验签我方回调 */
function platformPublicKey($root)
{
@@ -234,17 +317,23 @@ function defaultBody($act, $orgNo)
$mobile = isset($_GET['mobile']) ? $_GET['mobile'] : '13800000001';
if ($act === 'share') {
$body = [
'orgNo' => $orgNo,
'amount' => isset($_GET['amount']) ? $_GET['amount'] : '100.00',
'mobile' => $mobile,
'name' => isset($_GET['name']) ? $_GET['name'] : '张三',
'saleOrderNo' => $saleOrderNo,
'oilCardType' => 'zsy_ticket',
];
// 司机身份证号:平安银行通道必传,支付宝企业码通道可不传。
// 加 &noIdCard=1 可故意不传,用来验证实名通道下的 0001 拒单是否生效。
if (empty($_GET['noIdCard'])) {
$body['idCard'] = isset($_GET['idCard']) ? $_GET['idCard'] : '450103199001011238';
}
return [
'method' => 'syb.oil.oilCard.share',
'path' => '/oilCard/share',
'body' => [
'orgNo' => $orgNo,
'amount' => isset($_GET['amount']) ? $_GET['amount'] : '100.00',
'mobile' => $mobile,
'name' => isset($_GET['name']) ? $_GET['name'] : '张三',
'saleOrderNo' => $saleOrderNo,
'oilCardType' => 'zsy_ticket',
],
'body' => $body,
];
}
if ($act === 'qrcode') {
@@ -285,6 +374,7 @@ $act = isset($_GET['act']) ? $_GET['act'] : (isset($argv[1]) ? $argv[1] : '')
$fatal = '';
$result = null;
$partner = null;
$qconf = null;
try {
if ($act === 'notify_receiver') {
@@ -292,6 +382,7 @@ try {
}
$partner = getPartner($ROOT, TEST_APPKEY);
$platformPub = platformPublicKey($ROOT);
$qconf = qytConf($ROOT, $partner);
if (in_array($act, ['share', 'qrcode', 'status', 'sharelist'], true)) {
$def = defaultBody($act, $partner['orgNo']);
@@ -310,6 +401,22 @@ if (PHP_SAPI === 'cli') {
echo '错误:' . $fatal . "\n";
exit(1);
}
$ent = $qconf['enterprise'];
if (!$ent) {
echo "当前企业:【未匹配到可用企业】请先在后台「设置 -> 企业管理」新建企业并设为默认,否则下单会被拒绝
";
} else {
echo '当前企业:' . $ent['company_name'] . '(' . $ent['business_license'] . ')'
. ($qconf['from_default'] ? ' [默认企业]' : ' [合作商绑定]') . "
";
echo '当前通道:' . $ent['passageway_name'] . '(passageway=' . $ent['passageway_code'] . ')'
. ' 身份证号' . ($ent['require_id_card'] ? '必传' : '可不传')
. ' 机构号' . ($ent['institution_id'] !== '' ? '已配置' : '【未配置,下发必失败】')
. ' 企业状态' . $ent['status'] . "
";
echo '编号前缀:' . $qconf['no_prefix'] . ' 接入方配置来源:' . $qconf['source'] . "
";
}
if (!$result) {
echo "用法:php qyt_test.php [share|qrcode|status|sharelist]\n";
exit;
@@ -345,6 +452,7 @@ $curOil = isset($_GET['oilCardNo']) ? $_GET['oilCardNo'] : '';
.btns a{display:inline-block;margin:0 8px 8px 0;padding:7px 16px;background:#1890ff;color:#fff;
border-radius:4px;text-decoration:none;font-size:13px}
.btns a.g{background:#52c41a}.btns a.o{background:#faad14}.btns a.p{background:#722ed1}
.btns a.r{background:#ff4d4f}
table{border-collapse:collapse;width:100%;font-size:13px}
th,td{border:1px solid #eee;padding:6px 10px;text-align:left;vertical-align:top;word-break:break-all}
th{background:#fafafa;width:150px;font-weight:600;color:#555}
@@ -375,6 +483,48 @@ $curOil = isset($_GET['oilCardNo']) ? $_GET['oilCardNo'] : '';
</table>
</div>
<?php $ent = $qconf['enterprise']; ?>
<div class="card">
<h2>本次下单使用的企业与通道</h2>
<?php if (!$ent): ?>
<p class="err">未匹配到可用企业。请先到后台「设置 &rarr; 企业管理」新建企业并设为默认,否则下单接口会直接拒绝。</p>
<?php if (!empty($qconf['enterprise_error'])): ?>
<p class="err">读取企业表出错:<?php echo htmlspecialchars($qconf['enterprise_error']); ?></p>
<?php endif; ?>
<?php else: ?>
<table>
<tr><th>企业</th><td>
<b><?php echo htmlspecialchars($ent['company_name']); ?></b>
<?php echo $qconf['from_default'] ? '(来自<b>默认企业</b>兜底)' : '(该合作商<b>已绑定</b>此企业)'; ?>
</td></tr>
<tr><th>统一社会信用代码</th><td><?php echo htmlspecialchars($ent['business_license']); ?></td></tr>
<tr><th>企业状态</th><td>
<?php if ($ent['status'] === 'approved'): ?><span class="ok">审核通过</span>
<?php else: ?><span class="err"><?php echo htmlspecialchars($ent['status']); ?></span> 未通过审核时 7.15 免登地址会报「企业尚未通过审核」
<?php endif; ?>
</td></tr>
<tr><th>支付通道</th><td>
<b><?php echo htmlspecialchars($ent['passageway_name']); ?></b>(passageway=<?php echo (int)$ent['passageway_code']; ?>)
</td></tr>
<tr><th>司机身份证号</th><td>
<?php if ($ent['require_id_card']): ?>
<span class="err">必传</span> 该通道要求实名,下单不传 idCard 会返回 0001 拒单
<?php else: ?>
<span class="ok">可不传</span> 该通道不要求实名,传了也不会上送给企油通
<?php endif; ?>
</td></tr>
<tr><th>机构号</th><td>
<?php if ($ent['institution_id'] !== ''): ?><span class="ok">已配置</span>
<?php elseif ((int)$ent['passageway_code'] === 1): ?><span class="err">未配置</span> 支付宝企业码通道必填,下发时会失败并原路退回余额
<?php else: ?><span class="ok">平安通道无需机构号</span>
<?php endif; ?>
</td></tr>
<tr><th>编号前缀</th><td><?php echo htmlspecialchars($qconf['no_prefix']); ?></td></tr>
<tr><th>企油通域名</th><td><?php echo $qconf['base_url'] !== '' ? htmlspecialchars($qconf['base_url']) : '<span class="err">未配置</span>'; ?> <span class="sub">来源:<?php echo htmlspecialchars($qconf['source']); ?></span></td></tr>
</table>
<?php endif; ?>
</div>
<div class="tip">
接口地址默认取当前域名;如需指定,链接后加 <code>&amp;api=http://你的域名</code>。<br>
<b>下单前提</b>:三个队列进程已启动(partners:queue / callback:queue / notify:queue)、Redis 可用、合作商余额充足。
@@ -387,10 +537,14 @@ $curOil = isset($_GET['oilCardNo']) ? $_GET['oilCardNo'] : '';
<a class="o" href="?act=qrcode&amp;oilCardNo=<?php echo urlencode($curOil); ?>">② 获取加油链接 (qrCode)</a>
<a class="p" href="?act=status&amp;oilCardNo=<?php echo urlencode($curOil); ?>">③ 订单状态 (orderStatus)</a>
<a href="?act=sharelist">④ 分油记录 (shareList)</a>
<a class="r" href="?act=share&amp;noIdCard=1">⑤ 下单但不传身份证号</a>
</div>
<div class="sub" style="margin:6px 0 0">
②③ 需要 oilCardNo:先点①拿到后会自动带上。<br>
① 可自定义参数:<code>?act=share&amp;amount=50.00&amp;mobile=13800000002&amp;name=李四</code>
① 可自定义参数:<code>?act=share&amp;amount=50.00&amp;mobile=13800000002&amp;name=李四&amp;idCard=110101199003071233</code><br>
⑤ 用来验证实名校验:<?php echo !$ent ? '当前无可用企业' : ($ent['require_id_card']
? '当前是<b>' . htmlspecialchars($ent['passageway_name']) . '</b>通道,应当返回 <b>0001</b> 拒单'
: '当前是<b>' . htmlspecialchars($ent['passageway_name']) . '</b>通道,不要求实名,应当<b>正常下单成功</b>'); ?>
</div>
</div>
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
.ant-modal-body[data-v-547be636]{padding-top:0!important}.ant-form-item[data-v-547be636]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-547be636]{padding:10px 16px}.ant-card-body[data-v-c8e4e012]{padding:22px 29px 25px}.tab-list[data-v-c8e4e012]{margin-right:20px}.ant-card-body[data-v-78d16cc2]{padding:22px 29px 25px}.tab-list[data-v-78d16cc2]{margin-right:20px}
@@ -1 +0,0 @@
.ant-modal-body[data-v-547be636]{padding-top:0!important}.ant-form-item[data-v-547be636]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-547be636]{padding:10px 16px}.ant-card-body[data-v-c8e4e012]{padding:22px 29px 25px}.tab-list[data-v-c8e4e012]{margin-right:20px}.ant-card-body[data-v-78d16cc2]{padding:22px 29px 25px}.tab-list[data-v-78d16cc2]{margin-right:20px}
@@ -1 +0,0 @@
.platform-icon{font-size:16px}.platform-icon .icon{margin-right:5px}.platform-icon .icon:last-child{margin-right:0}.platform-icon .H5-WEIXIN,.platform-icon .MP-WEIXIN{color:#04be02}.platform-icon .H5{color:#e44c27}.user-info[data-v-7feb1f70]{width:160px;height:40px;line-height:1.3}.user-info .in-left[data-v-7feb1f70]{float:left;margin-right:10px}.user-info .in-left img[data-v-7feb1f70]{width:40px;height:40px;border-radius:50%}.user-info .in-right[data-v-7feb1f70]{float:left;width:100px;height:100%}.user-info .in-right .user-name[data-v-7feb1f70]{margin-bottom:2px}.goods-info[data-v-6d71ff35]{width:270px;line-height:1.3;white-space:normal}.goods-info .in-left[data-v-6d71ff35]{float:left;margin-right:8px}.goods-info .in-left img[data-v-6d71ff35]{width:60px;height:60px}.goods-info .in-right[data-v-6d71ff35]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;float:left;height:60px}.goods-info .in-right .title[data-v-6d71ff35]{font-size:13px;margin-bottom:5px}.goods-info .in-right .subtitle[data-v-6d71ff35]{font-size:12px;color:#7b7b7b}.goods-info .goods-props[data-v-6d71ff35]{color:#8a8a8a;font-size:12px;overflow:hidden}.goods-info .goods-props-item[data-v-6d71ff35]{display:inline-block;margin-right:4px}
@@ -1 +0,0 @@
.platform-icon{font-size:16px}.platform-icon .icon{margin-right:5px}.platform-icon .icon:last-child{margin-right:0}.platform-icon .H5-WEIXIN,.platform-icon .MP-WEIXIN{color:#04be02}.platform-icon .H5{color:#e44c27}.user-info[data-v-7feb1f70]{width:160px;height:40px;line-height:1.3}.user-info .in-left[data-v-7feb1f70]{float:left;margin-right:10px}.user-info .in-left img[data-v-7feb1f70]{width:40px;height:40px;border-radius:50%}.user-info .in-right[data-v-7feb1f70]{float:left;width:100px;height:100%}.user-info .in-right .user-name[data-v-7feb1f70]{margin-bottom:2px}.goods-info[data-v-6d71ff35]{width:270px;line-height:1.3;white-space:normal}.goods-info .in-left[data-v-6d71ff35]{float:left;margin-right:8px}.goods-info .in-left img[data-v-6d71ff35]{width:60px;height:60px}.goods-info .in-right[data-v-6d71ff35]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;float:left;height:60px}.goods-info .in-right .title[data-v-6d71ff35]{font-size:13px;margin-bottom:5px}.goods-info .in-right .subtitle[data-v-6d71ff35]{font-size:12px;color:#7b7b7b}.goods-info .goods-props[data-v-6d71ff35]{color:#8a8a8a;font-size:12px;overflow:hidden}.goods-info .goods-props-item[data-v-6d71ff35]{display:inline-block;margin-right:4px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
.platform-icon{font-size:16px}.platform-icon .icon{margin-right:5px}.platform-icon .icon:last-child{margin-right:0}.platform-icon .H5-WEIXIN,.platform-icon .MP-WEIXIN{color:#04be02}.platform-icon .H5{color:#e44c27}.user-info[data-v-3d0bc671]{width:160px;height:55px;line-height:1.3}.user-info .in-left[data-v-3d0bc671]{float:left;margin-right:10px}.user-info .in-left img[data-v-3d0bc671]{width:40px;height:40px;border-radius:50%}.user-info .in-right[data-v-3d0bc671]{float:left;width:100px;height:100%}.user-info .in-right .user-name[data-v-3d0bc671]{margin-bottom:2px}.goods-info[data-v-6d71ff35]{width:270px;line-height:1.3;white-space:normal}.goods-info .in-left[data-v-6d71ff35]{float:left;margin-right:8px}.goods-info .in-left img[data-v-6d71ff35]{width:60px;height:60px}.goods-info .in-right[data-v-6d71ff35]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;float:left;height:60px}.goods-info .in-right .title[data-v-6d71ff35]{font-size:13px;margin-bottom:5px}.goods-info .in-right .subtitle[data-v-6d71ff35]{font-size:12px;color:#7b7b7b}.goods-info .goods-props[data-v-6d71ff35]{color:#8a8a8a;font-size:12px;overflow:hidden}.goods-info .goods-props-item[data-v-6d71ff35]{display:inline-block;margin-right:4px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
.ant-form-item[data-v-1e3b16a8]{margin-bottom:15px}[data-v-1e3b16a8] .ant-form-item-control{padding-left:10px}[data-v-1e3b16a8] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-1e3b16a8]{margin-top:50px!important}.ant-form-item[data-v-42865d94]{margin-bottom:15px}[data-v-42865d94] .ant-form-item-control{padding-left:10px}[data-v-42865d94] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-42865d94]{margin-top:60px!important}.ant-form-item[data-v-0d8ee9ca]{margin-bottom:24px}[data-v-0d8ee9ca] .ant-form-item-control{padding-left:10px}[data-v-0d8ee9ca] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-0d8ee9ca]{margin-top:60px!important}
@@ -1 +0,0 @@
.ant-form-item[data-v-1e3b16a8]{margin-bottom:15px}[data-v-1e3b16a8] .ant-form-item-control{padding-left:10px}[data-v-1e3b16a8] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-1e3b16a8]{margin-top:50px!important}.ant-form-item[data-v-42865d94]{margin-bottom:15px}[data-v-42865d94] .ant-form-item-control{padding-left:10px}[data-v-42865d94] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-42865d94]{margin-top:60px!important}.ant-form-item[data-v-0d8ee9ca]{margin-bottom:24px}[data-v-0d8ee9ca] .ant-form-item-control{padding-left:10px}[data-v-0d8ee9ca] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-0d8ee9ca]{margin-top:60px!important}
@@ -1 +0,0 @@
.ant-modal-body[data-v-208811be]{padding-top:0!important}.ant-form-item[data-v-208811be]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-208811be]{padding:10px 16px}.ant-modal-body[data-v-3b02bc19]{padding-top:0!important}.ant-form-item[data-v-3b02bc19]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-3b02bc19]{padding:10px 16px}.ant-modal-body[data-v-7b36fd12]{padding-top:0!important}.ant-form-item[data-v-7b36fd12]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-7b36fd12]{padding:10px 16px}.ant-card-body[data-v-472f7823]{padding:22px 29px 25px}.tab-list[data-v-472f7823]{margin-right:20px}.tabs-content .tab-pane[data-v-47919056]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-47919056] .ant-form-item-control{padding-left:10px}[data-v-47919056] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-47919056]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-47919056]{margin-bottom:0}.input-number-group[data-v-47919056]{margin:3px 0}.company-textarea[data-v-47919056]{height:80px}.tabs-content .tab-pane[data-v-7ae51d82]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-7ae51d82] .ant-form-item-control{padding-left:10px}[data-v-7ae51d82] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-7ae51d82]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-7ae51d82]{margin-bottom:0}.input-number-group[data-v-7ae51d82]{margin:3px 0}.company-textarea[data-v-7ae51d82]{height:80px}
@@ -1 +0,0 @@
.ant-modal-body[data-v-208811be]{padding-top:0!important}.ant-form-item[data-v-208811be]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-208811be]{padding:10px 16px}.ant-modal-body[data-v-3b02bc19]{padding-top:0!important}.ant-form-item[data-v-3b02bc19]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-3b02bc19]{padding:10px 16px}.ant-modal-body[data-v-7b36fd12]{padding-top:0!important}.ant-form-item[data-v-7b36fd12]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-7b36fd12]{padding:10px 16px}.ant-card-body[data-v-488d5846]{padding:22px 29px 25px}.tab-list[data-v-488d5846]{margin-right:20px}.tabs-content .tab-pane[data-v-47919056]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-47919056] .ant-form-item-control{padding-left:10px}[data-v-47919056] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-47919056]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-47919056]{margin-bottom:0}.input-number-group[data-v-47919056]{margin:3px 0}.company-textarea[data-v-47919056]{height:80px}.tabs-content .tab-pane[data-v-7ae51d82]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-7ae51d82] .ant-form-item-control{padding-left:10px}[data-v-7ae51d82] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-7ae51d82]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-7ae51d82]{margin-bottom:0}.input-number-group[data-v-7ae51d82]{margin:3px 0}.company-textarea[data-v-7ae51d82]{height:80px}
@@ -1 +0,0 @@
.ant-modal-body[data-v-208811be]{padding-top:0!important}.ant-form-item[data-v-208811be]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-208811be]{padding:10px 16px}.ant-modal-body[data-v-3b02bc19]{padding-top:0!important}.ant-form-item[data-v-3b02bc19]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-3b02bc19]{padding:10px 16px}.ant-modal-body[data-v-7b36fd12]{padding-top:0!important}.ant-form-item[data-v-7b36fd12]{margin-bottom:15px}.ant-tabs-nav .ant-tabs-tab[data-v-7b36fd12]{padding:10px 16px}.ant-card-body[data-v-472f7823]{padding:22px 29px 25px}.tab-list[data-v-472f7823]{margin-right:20px}.tabs-content .tab-pane[data-v-47919056]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-47919056] .ant-form-item-control{padding-left:10px}[data-v-47919056] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-47919056]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-47919056]{margin-bottom:0}.input-number-group[data-v-47919056]{margin:3px 0}.company-textarea[data-v-47919056]{height:80px}.tabs-content .tab-pane[data-v-7ae51d82]{-webkit-animation:fadenum .6s ease;animation:fadenum .6s ease}[data-v-7ae51d82] .ant-form-item-control{padding-left:10px}[data-v-7ae51d82] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-7ae51d82]{margin-bottom:18px}.ant-form-item .ant-form-item[data-v-7ae51d82]{margin-bottom:0}.input-number-group[data-v-7ae51d82]{margin:3px 0}.company-textarea[data-v-7ae51d82]{height:80px}
@@ -1 +0,0 @@
.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-54e3e492],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-53667bd9],.ant-card-body[data-v-650748d8]{padding:22px 29px 25px}.tab-list[data-v-53667bd9]{margin-right:20px}.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-650748d8],.ant-card-body[data-v-46700646]{padding:22px 29px 25px}.flex-box[data-v-46700646]{display:-webkit-box;display:-ms-flexbox;display:flex}.button-group[data-v-46700646]{margin-right:10px}.form-total[data-v-46700646]{color:#1890ff;margin-right:10px;font-size:20px;font-weight:500}.ant-card-body[data-v-1b1d1568]{padding:22px 29px 25px}.tab-list[data-v-1b1d1568]{margin-right:20px}.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-669bb9b6],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-card-body[data-v-2fdb5d18],.ant-card-body[data-v-15c0eb1e],.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-24840d9d],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-card-body[data-v-1b1d1568],.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-54e3e492],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-650748d8]{padding:22px 29px 25px}.tab-list[data-v-1b1d1568]{margin-right:20px}.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-669bb9b6],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-54e3e492],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-650748d8],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-card-body[data-v-1b1d1568],.ant-card-body[data-v-18d6ec63],.ant-card-body[data-v-54e3e492],.ant-card-body[data-v-85cbc1ce],.ant-card-body[data-v-650748d8]{padding:22px 29px 25px}.tab-list[data-v-1b1d1568]{margin-right:20px}.ant-card-body[data-v-614fa1f7],.ant-card-body[data-v-2466693d],.ant-card-body[data-v-e7f9db82]{padding:22px 29px 25px}.tab-list[data-v-614fa1f7]{margin-right:20px}.ant-card-body[data-v-58bb637f]{padding:22px 29px 25px}.tab-list[data-v-58bb637f]{margin-right:20px}.preview-box[data-v-58bb637f]{width:120px;height:80px;line-height:80px}.preview-box img[data-v-58bb637f]{display:block;max-width:100%;max-height:100%}[data-v-74b71be6] .ant-form-item-control{padding-left:10px}[data-v-74b71be6] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-74b71be6]{margin-top:50px!important}.table-rules .operation{text-align:right}.table-rules .operation a{font-size:13px;margin-left:6px}.table-rules .content{color:#505050;white-space:normal}.table-rules .content .city-name{font-size:12.5px;color:#7b7b7b}.ant-checkbox-wrapper+.ant-checkbox-wrapper[data-v-4c243eb9]{margin-left:0}.ant-checkbox-wrapper[data-v-4c243eb9]{margin-right:14px;margin-bottom:18px}[data-v-4c243eb9] .ant-form-item-control{padding-left:10px}[data-v-4c243eb9] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
[data-v-6bcd977c]{margin:0;padding:0;-webkit-tap-highlight-color:transparent}[data-v-6bcd977c],[data-v-6bcd977c]:after,[data-v-6bcd977c]:before{-webkit-box-sizing:border-box;box-sizing:border-box}address[data-v-6bcd977c],applet[data-v-6bcd977c],area[data-v-6bcd977c],article[data-v-6bcd977c],aside[data-v-6bcd977c],blockquote[data-v-6bcd977c],fieldset[data-v-6bcd977c],figcaption[data-v-6bcd977c],figure[data-v-6bcd977c],footer[data-v-6bcd977c],header[data-v-6bcd977c],map[data-v-6bcd977c],section[data-v-6bcd977c],summary[data-v-6bcd977c]{display:block}body[data-v-6bcd977c]{font:12px/1.5 Microsoft YaHei,tahoma,arial,Hiragino Sans GB,\\5b8b\4f53,sans-serif;background:#fff}.container[data-v-6bcd977c]{text-align:center;max-width:640px}.img[data-v-6bcd977c]{width:60px;vertical-align:middle}.box-title[data-v-6bcd977c]{line-height:30px}.box-p[data-v-6bcd977c]{line-height:35px;text-align:left;height:40px}.text-val[data-v-6bcd977c]{display:inline-block;width:240px;border:1px solid grey;height:29px;line-height:25px;padding:0 5px;background-color:#ededed}.copy[data-v-6bcd977c]{margin-left:10px;color:#000;font-size:13px;border:1px solid;padding:5px;background-color:#e9e9e9}.sure-but[data-v-6bcd977c]{display:inline-block;width:100%;height:45px;line-height:45px;background-color:#4caf50;color:#fff;text-align:center;margin-bottom:60px;margin-top:20px;border-radius:5px;font-size:21px}.text-input[data-v-6bcd977c]{display:inline-block;width:176px;border:1px solid grey;height:29px;line-height:25px;padding:0 5px}.tick-text[data-v-6bcd977c]{line-height:30px}.tick-text-no[data-v-6bcd977c]{height:60px;line-height:60px;font-weight:600}.explain[data-v-6bcd977c]{color:red;font-weight:600;width:90%;margin:0 auto;text-align:left;font-size:16px}.explain-text[data-v-6bcd977c]{text-align:left;width:90%;margin:0 auto;height:36px}.explain-blue[data-v-6bcd977c]{color:#fff;font-weight:unset;width:80%;font-size:19px;background-color:#40a9ff;border-radius:5px}.orange[data-v-6bcd977c]{color:#ff5200;font-weight:600}.text-box-top[data-v-6bcd977c]{width:90%;margin:0 auto}.red[data-v-6bcd977c]{color:red}.title[data-v-6bcd977c]{height:75px;line-height:75px}.title-text[data-v-6bcd977c]{font-size:40px;vertical-align:middle;margin-left:15px}.money[data-v-6bcd977c]{font-size:40px;color:#ff5200;margin-right:8px}.qrcode[data-v-6bcd977c]{width:70%}.qrcode-div[data-v-6bcd977c]{border-top:1px solid #ebebeb;border-bottom:1px solid #ebebeb;margin:20px 0 20px 0}.black[data-v-6bcd977c]{color:#000!important;font-size:13px}.center[data-v-6bcd977c]{text-align:center;margin:15px auto!important}.min[data-v-6bcd977c],.sec[data-v-6bcd977c]{color:red}.ali-img[data-v-6bcd977c]{width:170px;vertical-align:middle}.ali-explain[data-v-6bcd977c]{font-weight:600;width:90%;margin:0 auto;font-size:15px}.ali-title[data-v-6bcd977c]{height:125px;line-height:125px}.ali-a[data-v-6bcd977c]{color:#fff;display:inline-block;width:100%}.ali-time[data-v-6bcd977c]{color:#000;font-size:14px}.ali-explain-trip[data-v-6bcd977c]{margin-top:27%!important}.down img[data-v-6bcd977c]{width:270px!important}.box[data-v-6bcd977c]{margin-top:10%}.c-red[data-v-6bcd977c]{color:#c7171e}
@@ -1 +0,0 @@
[data-v-6bcd977c]{margin:0;padding:0;-webkit-tap-highlight-color:transparent}[data-v-6bcd977c],[data-v-6bcd977c]:after,[data-v-6bcd977c]:before{-webkit-box-sizing:border-box;box-sizing:border-box}address[data-v-6bcd977c],applet[data-v-6bcd977c],area[data-v-6bcd977c],article[data-v-6bcd977c],aside[data-v-6bcd977c],blockquote[data-v-6bcd977c],fieldset[data-v-6bcd977c],figcaption[data-v-6bcd977c],figure[data-v-6bcd977c],footer[data-v-6bcd977c],header[data-v-6bcd977c],map[data-v-6bcd977c],section[data-v-6bcd977c],summary[data-v-6bcd977c]{display:block}body[data-v-6bcd977c]{font:12px/1.5 Microsoft YaHei,tahoma,arial,Hiragino Sans GB,\\5b8b\4f53,sans-serif;background:#fff}.container[data-v-6bcd977c]{text-align:center;max-width:640px}.img[data-v-6bcd977c]{width:60px;vertical-align:middle}.box-title[data-v-6bcd977c]{line-height:30px}.box-p[data-v-6bcd977c]{line-height:35px;text-align:left;height:40px}.text-val[data-v-6bcd977c]{display:inline-block;width:240px;border:1px solid grey;height:29px;line-height:25px;padding:0 5px;background-color:#ededed}.copy[data-v-6bcd977c]{margin-left:10px;color:#000;font-size:13px;border:1px solid;padding:5px;background-color:#e9e9e9}.sure-but[data-v-6bcd977c]{display:inline-block;width:100%;height:45px;line-height:45px;background-color:#4caf50;color:#fff;text-align:center;margin-bottom:60px;margin-top:20px;border-radius:5px;font-size:21px}.text-input[data-v-6bcd977c]{display:inline-block;width:176px;border:1px solid grey;height:29px;line-height:25px;padding:0 5px}.tick-text[data-v-6bcd977c]{line-height:30px}.tick-text-no[data-v-6bcd977c]{height:60px;line-height:60px;font-weight:600}.explain[data-v-6bcd977c]{color:red;font-weight:600;width:90%;margin:0 auto;text-align:left;font-size:16px}.explain-text[data-v-6bcd977c]{text-align:left;width:90%;margin:0 auto;height:36px}.explain-blue[data-v-6bcd977c]{color:#fff;font-weight:unset;width:80%;font-size:19px;background-color:#40a9ff;border-radius:5px}.orange[data-v-6bcd977c]{color:#ff5200;font-weight:600}.text-box-top[data-v-6bcd977c]{width:90%;margin:0 auto}.red[data-v-6bcd977c]{color:red}.title[data-v-6bcd977c]{height:75px;line-height:75px}.title-text[data-v-6bcd977c]{font-size:40px;vertical-align:middle;margin-left:15px}.money[data-v-6bcd977c]{font-size:40px;color:#ff5200;margin-right:8px}.qrcode[data-v-6bcd977c]{width:70%}.qrcode-div[data-v-6bcd977c]{border-top:1px solid #ebebeb;border-bottom:1px solid #ebebeb;margin:20px 0 20px 0}.black[data-v-6bcd977c]{color:#000!important;font-size:13px}.center[data-v-6bcd977c]{text-align:center;margin:15px auto!important}.min[data-v-6bcd977c],.sec[data-v-6bcd977c]{color:red}.ali-img[data-v-6bcd977c]{width:170px;vertical-align:middle}.ali-explain[data-v-6bcd977c]{font-weight:600;width:90%;margin:0 auto;font-size:15px}.ali-title[data-v-6bcd977c]{height:125px;line-height:125px}.ali-a[data-v-6bcd977c]{color:#fff;display:inline-block;width:100%}.ali-time[data-v-6bcd977c]{color:#000;font-size:14px}.ali-explain-trip[data-v-6bcd977c]{margin-top:27%!important}.down img[data-v-6bcd977c]{width:270px!important}.box[data-v-6bcd977c]{margin-top:10%}.c-red[data-v-6bcd977c]{color:#c7171e}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
.card-title[data-v-0eeb4ae3]{font-size:14px;font-weight:700;margin-bottom:15px}.statistics[data-v-0eeb4ae3]{line-height:1}.statistics .ant-card .ant-card-body[data-v-0eeb4ae3]{height:100%;padding:30px 40px}.statistics .col-left[data-v-0eeb4ae3]{margin-right:25px;font-size:24px;color:#4799ff}.statistics .col-left .icon-body[data-v-0eeb4ae3]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.statistics .col-right[data-v-0eeb4ae3]{margin-top:-5px}.statistics .col-right .keynote[data-v-0eeb4ae3]{font-size:26px}.pending[data-v-0eeb4ae3]{text-align:center;padding:15px 0}.pending .item[data-v-0eeb4ae3]{display:inline-block;padding:0 20px}.pending .item a[data-v-0eeb4ae3]{color:unset}.pending .item a[data-v-0eeb4ae3]:hover{color:#1890ff}.pending .name[data-v-0eeb4ae3]{font-weight:700;margin-bottom:10px;font-size:13.6px}.pending .value[data-v-0eeb4ae3]{font-weight:700;color:red;font-size:16px}.functions .item[data-v-0eeb4ae3]{display:block;padding:13px 20px;cursor:pointer}.functions .item[data-v-0eeb4ae3]:hover{color:#1890ff}.functions .name[data-v-0eeb4ae3]{font-size:13px;margin-left:10px}.functions .icon[data-v-0eeb4ae3]{width:28px;height:28px;color:#fff;font-size:13px;border-radius:5px}.overview a[data-v-0eeb4ae3]{color:unset}.overview a[data-v-0eeb4ae3]:hover{color:#1890ff}.overview .item[data-v-0eeb4ae3]{padding:13px 20px}.overview .col-left[data-v-0eeb4ae3]{color:#4799ff;font-size:28px;margin-right:35px}.overview .col-left .icon-body[data-v-0eeb4ae3]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.overview .col-right .name[data-v-0eeb4ae3]{font-size:13px;color:#666}.overview .col-right .value[data-v-0eeb4ae3]{color:#000;font-size:24px}.overview .col-right .yesterday[data-v-0eeb4ae3]{display:none;font-size:12px;color:#999}
@@ -1 +0,0 @@
.card-title[data-v-0eeb4ae3]{font-size:14px;font-weight:700;margin-bottom:15px}.statistics[data-v-0eeb4ae3]{line-height:1}.statistics .ant-card .ant-card-body[data-v-0eeb4ae3]{height:100%;padding:30px 40px}.statistics .col-left[data-v-0eeb4ae3]{margin-right:25px;font-size:24px;color:#4799ff}.statistics .col-left .icon-body[data-v-0eeb4ae3]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.statistics .col-right[data-v-0eeb4ae3]{margin-top:-5px}.statistics .col-right .keynote[data-v-0eeb4ae3]{font-size:26px}.pending[data-v-0eeb4ae3]{text-align:center;padding:15px 0}.pending .item[data-v-0eeb4ae3]{display:inline-block;padding:0 20px}.pending .item a[data-v-0eeb4ae3]{color:unset}.pending .item a[data-v-0eeb4ae3]:hover{color:#1890ff}.pending .name[data-v-0eeb4ae3]{font-weight:700;margin-bottom:10px;font-size:13.6px}.pending .value[data-v-0eeb4ae3]{font-weight:700;color:red;font-size:16px}.functions .item[data-v-0eeb4ae3]{display:block;padding:13px 20px;cursor:pointer}.functions .item[data-v-0eeb4ae3]:hover{color:#1890ff}.functions .name[data-v-0eeb4ae3]{font-size:13px;margin-left:10px}.functions .icon[data-v-0eeb4ae3]{width:28px;height:28px;color:#fff;font-size:13px;border-radius:5px}.overview a[data-v-0eeb4ae3]{color:unset}.overview a[data-v-0eeb4ae3]:hover{color:#1890ff}.overview .item[data-v-0eeb4ae3]{padding:13px 20px}.overview .col-left[data-v-0eeb4ae3]{color:#4799ff;font-size:28px;margin-right:35px}.overview .col-left .icon-body[data-v-0eeb4ae3]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.overview .col-right .name[data-v-0eeb4ae3]{font-size:13px;color:#666}.overview .col-right .value[data-v-0eeb4ae3]{color:#000;font-size:24px}.overview .col-right .yesterday[data-v-0eeb4ae3]{display:none;font-size:12px;color:#999}
@@ -1 +0,0 @@
.card-title[data-v-0fc1832e]{font-size:14px;font-weight:700;margin-bottom:15px}.statistics[data-v-0fc1832e]{line-height:1}.statistics .ant-card .ant-card-body[data-v-0fc1832e]{height:100%;padding:30px 40px}.statistics .col-left[data-v-0fc1832e]{margin-right:25px;font-size:24px;color:#4799ff}.statistics .col-left .icon-body[data-v-0fc1832e]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.statistics .col-right[data-v-0fc1832e]{margin-top:-5px}.statistics .col-right .keynote[data-v-0fc1832e]{font-size:26px}.pending[data-v-0fc1832e]{text-align:center;padding:15px 0}.pending .item[data-v-0fc1832e]{display:inline-block;padding:0 20px}.pending .item a[data-v-0fc1832e]{color:unset}.pending .item a[data-v-0fc1832e]:hover{color:#1890ff}.pending .name[data-v-0fc1832e]{font-weight:700;margin-bottom:10px;font-size:13.6px}.pending .value[data-v-0fc1832e]{font-weight:700;color:red;font-size:16px}.functions .item[data-v-0fc1832e]{display:block;padding:13px 20px;cursor:pointer}.functions .item[data-v-0fc1832e]:hover{color:#1890ff}.functions .name[data-v-0fc1832e]{font-size:13px;margin-left:10px}.functions .icon[data-v-0fc1832e]{width:28px;height:28px;color:#fff;font-size:13px;border-radius:5px}.overview a[data-v-0fc1832e]{color:unset}.overview a[data-v-0fc1832e]:hover{color:#1890ff}.overview .item[data-v-0fc1832e]{padding:13px 20px}.overview .col-left[data-v-0fc1832e]{color:#4799ff;font-size:28px;margin-right:35px}.overview .col-left .icon-body[data-v-0fc1832e]{width:58px;height:58px;border-radius:50%;background-color:#eff6ff}.overview .col-right .name[data-v-0fc1832e]{font-size:13px;color:#666}.overview .col-right .value[data-v-0fc1832e]{color:#000;font-size:24px}.overview .col-right .yesterday[data-v-0fc1832e]{display:none;font-size:12px;color:#999}
@@ -1 +0,0 @@
.ant-form-item .ant-form-item[data-v-0a4749e4],.ant-form-item .ant-form-item[data-v-65eaa220],.ant-form-item .ant-form-item[data-v-270e929b]{margin-bottom:0}[data-v-65eaa220] .ant-form-item-control{padding-left:10px}[data-v-65eaa220] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item .ant-form-item[data-v-3a1c101e]{margin-bottom:0}[data-v-3a1c101e] .ant-form-item-control{padding-left:10px}[data-v-3a1c101e] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item .ant-form-item[data-v-e0c5fd3e]{margin-bottom:0}[data-v-e0c5fd3e] .ant-form-item-control{padding-left:10px}[data-v-e0c5fd3e] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.ant-form-item .ant-form-item[data-v-0a4749e4],.ant-form-item .ant-form-item[data-v-65eaa220],.ant-form-item .ant-form-item[data-v-270e929b]{margin-bottom:0}[data-v-65eaa220] .ant-form-item-control{padding-left:10px}[data-v-65eaa220] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item .ant-form-item[data-v-3a1c101e]{margin-bottom:0}[data-v-3a1c101e] .ant-form-item-control{padding-left:10px}[data-v-3a1c101e] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item .ant-form-item[data-v-e0c5fd3e]{margin-bottom:0}[data-v-e0c5fd3e] .ant-form-item-control{padding-left:10px}[data-v-e0c5fd3e] .ant-form-item-control .ant-form-item-control{padding-left:0}
@@ -1 +0,0 @@
.pagination[data-v-369ab6f2]{margin-top:16px}.pagination .ant-pagination[data-v-369ab6f2]{float:right}.ant-table table[data-v-369ab6f2]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-369ab6f2]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-369ab6f2]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-369ab6f2]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-369ab6f2]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-369ab6f2]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
@@ -1 +0,0 @@
.pagination[data-v-dd8c8f02]{margin-top:16px}.pagination .ant-pagination[data-v-dd8c8f02]{float:right}.ant-table table[data-v-dd8c8f02]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-dd8c8f02]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-dd8c8f02]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-dd8c8f02]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-dd8c8f02]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-dd8c8f02]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
@@ -1 +0,0 @@
.pagination[data-v-0ea0c1a9]{margin-top:16px}.pagination .ant-pagination[data-v-0ea0c1a9]{float:right}.ant-table table[data-v-0ea0c1a9]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-0ea0c1a9]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-0ea0c1a9]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-0ea0c1a9]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-0ea0c1a9]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-0ea0c1a9]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
@@ -1 +0,0 @@
.pagination[data-v-d8d24ca6]{margin-top:16px}.pagination .ant-pagination[data-v-d8d24ca6]{float:right}.ant-table table[data-v-d8d24ca6]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-d8d24ca6]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-d8d24ca6]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-d8d24ca6]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-d8d24ca6]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-d8d24ca6]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}.form-total[data-v-d8d24ca6]{color:#1890ff;margin-right:10px;font-size:20px;font-weight:500}
@@ -1 +0,0 @@
.pagination[data-v-70952afa]{margin-top:16px}.pagination .ant-pagination[data-v-70952afa]{float:right}.ant-table table[data-v-70952afa]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-70952afa]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-70952afa]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-70952afa]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-70952afa]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-70952afa]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
@@ -1 +0,0 @@
.pagination[data-v-0675489a]{margin-top:16px}.pagination .ant-pagination[data-v-0675489a]{float:right}.ant-table table[data-v-0675489a]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-0675489a]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-0675489a]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-0675489a]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-0675489a]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-0675489a]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
@@ -1 +0,0 @@
.pagination[data-v-140fcb66]{margin-top:16px}.pagination .ant-pagination[data-v-140fcb66]{float:right}.ant-table table[data-v-140fcb66]{border:none;border-collapse:collapse}.ant-table .ant-table-thead>tr[data-v-140fcb66]{border:1px solid #e8e8e8}.ant-table tr.order-empty[data-v-140fcb66]{height:15px;border:1px solid #fff}.ant-table tr.order-empty td[data-v-140fcb66]{padding:0;border-right:none;border-left:none;background:none!important}.ant-table-thead>tr>th[data-v-140fcb66]{border-right:none;border-bottom:none;padding:12px 12px;font-weight:700}.ant-table-tbody>tr>td[data-v-140fcb66]{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8;padding:12px 12px}
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
.header[data-v-644f46f3]{margin-bottom:40px;text-align:center}.header .logo[data-v-644f46f3]{height:50px;vertical-align:top;border-style:none;margin-bottom:20px}.header .title[data-v-644f46f3]{font-size:20px;color:#6c7293;font-family:Avenir,Helvetica Neue,Arial,Helvetica,sans-serif;position:relative;margin:0}.user-layout-login label[data-v-644f46f3]{font-size:14px}.user-layout-login[data-v-644f46f3] .login-input .ant-input{border:none;background:rgba(235,237,242,.4);border-radius:5px;font-size:14px}.user-layout-login[data-v-644f46f3] .ant-form-explain{margin-top:5px;color:rgba(0,0,0,.45)}.user-layout-login .login-button[data-v-644f46f3]{padding:0 15px;font-size:16px;height:40px;width:100%;margin-top:20px;-webkit-box-shadow:0 4px 16px 0 rgba(34,185,255,.15);box-shadow:0 4px 16px 0 rgba(34,185,255,.15)}.user-layout-login .login-button[data-v-644f46f3]:hover{-webkit-box-shadow:0 9px 16px 0 rgba(34,185,255,.25)!important;box-shadow:0 9px 16px 0 rgba(34,185,255,.25)!important}.user-layout-login .ant-alert-error[data-v-644f46f3]{margin-bottom:24px;background-color:#fffbfb}
@@ -1 +0,0 @@
.header[data-v-644f46f3]{margin-bottom:40px;text-align:center}.header .logo[data-v-644f46f3]{height:50px;vertical-align:top;border-style:none;margin-bottom:20px}.header .title[data-v-644f46f3]{font-size:20px;color:#6c7293;font-family:Avenir,Helvetica Neue,Arial,Helvetica,sans-serif;position:relative;margin:0}.user-layout-login label[data-v-644f46f3]{font-size:14px}.user-layout-login[data-v-644f46f3] .login-input .ant-input{border:none;background:rgba(235,237,242,.4);border-radius:5px;font-size:14px}.user-layout-login[data-v-644f46f3] .ant-form-explain{margin-top:5px;color:rgba(0,0,0,.45)}.user-layout-login .login-button[data-v-644f46f3]{padding:0 15px;font-size:16px;height:40px;width:100%;margin-top:20px;-webkit-box-shadow:0 4px 16px 0 rgba(34,185,255,.15);box-shadow:0 4px 16px 0 rgba(34,185,255,.15)}.user-layout-login .login-button[data-v-644f46f3]:hover{-webkit-box-shadow:0 9px 16px 0 rgba(34,185,255,.25)!important;box-shadow:0 9px 16px 0 rgba(34,185,255,.25)!important}.user-layout-login .ant-alert-error[data-v-644f46f3]{margin-bottom:24px;background-color:#fffbfb}
@@ -0,0 +1 @@
[data-v-f551b524] .ant-form-item-control{padding-left:10px}[data-v-f551b524] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-f551b524]{margin-top:60px!important}.ant-input-number[data-v-f551b524]{width:160px}.input-text_right[data-v-f551b524]{margin-left:10px}.radio-item[data-v-f551b524]{margin-bottom:16px}.tips-alert[data-v-5e958e96]{margin-bottom:20px}.search-form[data-v-5e958e96]{display:inline-block;margin-left:16px}.sub[data-v-5e958e96]{color:#999;font-size:12px;line-height:1.7}.ml-6[data-v-5e958e96]{margin-left:6px}.text-red[data-v-5e958e96]{color:#ff4d4f}.text-grey[data-v-5e958e96]{color:#bbb}[data-v-7e59f1c6] .ant-form-item-control{padding-left:10px}[data-v-7e59f1c6] .ant-form-item-control .ant-form-item-control{padding-left:0}.tips-alert[data-v-7e59f1c6]{margin-bottom:28px}.input-short[data-v-7e59f1c6]{width:160px}.input-code[data-v-7e59f1c6]{width:30%}.input-inst[data-v-7e59f1c6]{width:70%}[data-v-01d8132a] .ant-form-item-control{padding-left:10px}[data-v-01d8132a] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-01d8132a]{margin-top:60px!important}.ant-input-number[data-v-01d8132a]{width:160px}.input-text_right[data-v-01d8132a]{margin-left:10px}.radio-item[data-v-01d8132a]{margin-bottom:16px}[data-v-dd6519b2] .ant-form-item-control{padding-left:10px}[data-v-dd6519b2] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-12cd9802]{margin-bottom:10px}[data-v-12cd9802] .ant-form-item-control{padding-left:10px}[data-v-12cd9802] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-12cd9802]{margin-top:50px!important}
@@ -1 +0,0 @@
[data-v-08321479] .ant-form-item-control{padding-left:10px}[data-v-08321479] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-08321479]{margin-top:60px!important}.ant-input-number[data-v-08321479]{width:160px}.input-text_right[data-v-08321479]{margin-left:10px}.radio-item[data-v-08321479]{margin-bottom:16px}[data-v-01d8132a] .ant-form-item-control{padding-left:10px}[data-v-01d8132a] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-01d8132a]{margin-top:60px!important}.ant-input-number[data-v-01d8132a]{width:160px}.input-text_right[data-v-01d8132a]{margin-left:10px}.radio-item[data-v-01d8132a]{margin-bottom:16px}[data-v-dd6519b2] .ant-form-item-control{padding-left:10px}[data-v-dd6519b2] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-12cd9802]{margin-bottom:10px}[data-v-12cd9802] .ant-form-item-control{padding-left:10px}[data-v-12cd9802] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-12cd9802]{margin-top:50px!important}
@@ -1 +0,0 @@
[data-v-f551b524] .ant-form-item-control{padding-left:10px}[data-v-f551b524] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-f551b524]{margin-top:60px!important}.ant-input-number[data-v-f551b524]{width:160px}.input-text_right[data-v-f551b524]{margin-left:10px}.radio-item[data-v-f551b524]{margin-bottom:16px}[data-v-01d8132a] .ant-form-item-control{padding-left:10px}[data-v-01d8132a] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-01d8132a]{margin-top:60px!important}.ant-input-number[data-v-01d8132a]{width:160px}.input-text_right[data-v-01d8132a]{margin-left:10px}.radio-item[data-v-01d8132a]{margin-bottom:16px}[data-v-dd6519b2] .ant-form-item-control{padding-left:10px}[data-v-dd6519b2] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-12cd9802]{margin-bottom:10px}[data-v-12cd9802] .ant-form-item-control{padding-left:10px}[data-v-12cd9802] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-12cd9802]{margin-top:50px!important}
@@ -1 +0,0 @@
[data-v-f551b524] .ant-form-item-control{padding-left:10px}[data-v-f551b524] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-f551b524]{margin-top:60px!important}.ant-input-number[data-v-f551b524]{width:160px}.input-text_right[data-v-f551b524]{margin-left:10px}.radio-item[data-v-f551b524]{margin-bottom:16px}[data-v-01d8132a] .ant-form-item-control{padding-left:10px}[data-v-01d8132a] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-01d8132a]{margin-top:60px!important}.ant-input-number[data-v-01d8132a]{width:160px}.input-text_right[data-v-01d8132a]{margin-left:10px}.radio-item[data-v-01d8132a]{margin-bottom:16px}[data-v-dd6519b2] .ant-form-item-control{padding-left:10px}[data-v-dd6519b2] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-form-item[data-v-12cd9802]{margin-bottom:10px}[data-v-12cd9802] .ant-form-item-control{padding-left:10px}[data-v-12cd9802] .ant-form-item-control .ant-form-item-control{padding-left:0}.ant-divider[data-v-12cd9802]{margin-top:50px!important}

Some files were not shown because too many files have changed in this diff Show More