1. 诊所管理
This commit is contained in:
38
api/bankCardReport.js
Normal file
38
api/bankCardReport.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
function getPrefix() {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
if (mode === 'platform_admin') {
|
||||
return { prefix: '/newApi/platform-admin-store-bank-card-report', headerKey: '_platformAdmin' };
|
||||
}
|
||||
return { prefix: '/newApi/salesperson-store-bank-card-report', headerKey: '_salesperson' };
|
||||
}
|
||||
|
||||
function bizRequest(options) {
|
||||
const { prefix, headerKey } = getPrefix();
|
||||
return req.request({
|
||||
...options,
|
||||
url: `${prefix}${options.path}`,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
[headerKey]: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
export function getStoreBankCardReportDetail(storeId) {
|
||||
return bizRequest({ path: '/detail', method: 'GET', data: { store_id: storeId } });
|
||||
}
|
||||
|
||||
export function reportStoreBankCard(data) {
|
||||
return bizRequest({ path: '/report', method: 'POST', data });
|
||||
}
|
||||
|
||||
export function syncStoreBankCardStatusByStore(storeId) {
|
||||
return bizRequest({ path: '/sync-status-by-store', method: 'POST', data: { store_id: storeId } });
|
||||
}
|
||||
@@ -155,6 +155,9 @@ export function createBusinessApi(prefix, headerFlag) {
|
||||
getPromoterOptions() {
|
||||
return bizRequest({ url: `${prefix}-store-input/promoter-options`, method: 'GET' });
|
||||
},
|
||||
getPromoterDetail(params) {
|
||||
return bizRequest({ url: `${prefix}-store-input/promoter-detail`, method: 'GET', data: params });
|
||||
},
|
||||
getStoreInputOption() {
|
||||
return bizRequest({ url: `${prefix}-store-input/option`, method: 'GET' });
|
||||
},
|
||||
|
||||
@@ -28,6 +28,30 @@ function getOssSignatureByPrefix(prefix, headerKey) {
|
||||
});
|
||||
}
|
||||
|
||||
function registerOssFileByPrefix(prefix, headerKey, url) {
|
||||
return req.request({
|
||||
url: `${prefix}register-oss-file`,
|
||||
method: 'POST',
|
||||
data: { url, source: 2 },
|
||||
header: {
|
||||
[headerKey]: '1',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function registerBusinessOssFile(url) {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
if (mode === 'platform_admin') {
|
||||
return registerOssFileByPrefix(PLATFORM_PREFIX, '_platformAdmin', url);
|
||||
}
|
||||
if (mode === 'clinic_admin') {
|
||||
return registerOssFileByPrefix(CLINIC_ADMIN_PREFIX, '_clinicAdmin', url);
|
||||
}
|
||||
return registerOssFileByPrefix(SALESPERSON_PREFIX, '_salesperson', url);
|
||||
}
|
||||
|
||||
export { registerBusinessOssFile };
|
||||
|
||||
/**
|
||||
* 业务员录入/聊天同款:服务端转存 OSS,返回 url
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 运维:微信小程序 uploadFile 合法域名需包含 OSS host
|
||||
* 例如 https://xiaokang88.oss-cn-hangzhou.aliyuncs.com
|
||||
*/
|
||||
import { getBusinessOssSignatureApi, unwrapOssSignature } from '@/api/upload.js';
|
||||
import { getBusinessOssSignatureApi, unwrapOssSignature, registerBusinessOssFile } from '@/api/upload.js';
|
||||
|
||||
/**
|
||||
* @param {string} filePath 本地临时文件路径
|
||||
@@ -75,8 +75,12 @@ export async function uploadToOss(options) {
|
||||
const code = uploadRes.statusCode;
|
||||
if (code >= 200 && code < 300 || code === 204) {
|
||||
const host = String(signature.host).replace(/\/$/, '');
|
||||
const url = `${host}/${objectName}`;
|
||||
registerBusinessOssFile(url).catch((err) => {
|
||||
console.warn('OSS 文件登记失败:', err);
|
||||
});
|
||||
resolve({
|
||||
url: `${host}/${objectName}`,
|
||||
url,
|
||||
objectName,
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -506,6 +506,7 @@
|
||||
{ "path": "store-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom" } },
|
||||
{ "path": "store-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
||||
{ "path": "store-input/detail", "style": { "navigationBarTitleText": "录入详情", "navigationStyle": "custom" } },
|
||||
{ "path": "store-bank-card/report", "style": { "navigationBarTitleText": "银行卡报备", "navigationStyle": "custom" } },
|
||||
{ "path": "doctor-input/index", "style": { "navigationBarTitleText": "列表", "navigationStyle": "custom" } },
|
||||
{ "path": "doctor-input/form", "style": { "navigationBarTitleText": "录入", "navigationStyle": "custom" } },
|
||||
{ "path": "doctor-input/detail", "style": { "navigationBarTitleText": "预填详情", "navigationStyle": "custom" } }
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
<text class="status" :class="'s' + item.status">{{ statusText(item.status) }}</text>
|
||||
</view>
|
||||
<view class="sub">{{ item.mobile }} · {{ formatTime(item.created_at) }}</view>
|
||||
<view v-if="inputType === 'store' && item.promoter_admin" class="promoter-row" @click.stop>
|
||||
<promoter-info-card :admin="item.promoter_admin" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!list.length" class="empty">暂无记录</view>
|
||||
</view>
|
||||
@@ -18,11 +21,12 @@
|
||||
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||||
import PromoterInfoCard from '@/subPackages/sub_salesperson/components/PromoterInfoCard.vue'
|
||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
components: { BusinessPageLayout },
|
||||
components: { BusinessPageLayout, PromoterInfoCard },
|
||||
props: {
|
||||
auditMode: { type: Boolean, default: false },
|
||||
inputType: { type: String, default: 'store' },
|
||||
@@ -82,5 +86,6 @@ export default {
|
||||
.s1 { color: #00B42A; }
|
||||
.s2 { color: #F53F3F; }
|
||||
.sub { margin-top: 12rpx; color: #86909C; font-size: 24rpx; }
|
||||
.promoter-row { margin-top: 16rpx; padding-top: 12rpx; border-top: 1rpx solid #f5f5f5; }
|
||||
.empty { text-align: center; color: #86909C; padding: 80rpx 0; }
|
||||
</style>
|
||||
|
||||
@@ -57,11 +57,26 @@ function storeNamesText(info) {
|
||||
}
|
||||
|
||||
function promoterText(info) {
|
||||
const u = info.promoter || info.inputUser
|
||||
const u = info.promoter_admin || info.promoter || info.inputUser
|
||||
if (!u) return info.code || '-'
|
||||
return [u.nick_name, u.code].filter(Boolean).join(' · ') || '-'
|
||||
}
|
||||
|
||||
export function getPromoterAdmin(info) {
|
||||
if (info?.promoter_admin) return info.promoter_admin
|
||||
if (info?.code) {
|
||||
return {
|
||||
id: info.promoter?.id,
|
||||
code: info.code,
|
||||
nick_name: info.promoter?.nick_name,
|
||||
avatar: info.promoter?.avatar,
|
||||
phone: info.promoter?.phone,
|
||||
role_name: info.promoter?.role_name,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function bankTypeText(type) {
|
||||
return Number(type) === 1 ? '对公' : '对私'
|
||||
}
|
||||
@@ -79,7 +94,7 @@ export function getStoreDetailSections() {
|
||||
{ key: 'mobile', label: '联系电话' },
|
||||
{ key: 'region', label: '省市区', format: (_, info) => regionText(info) },
|
||||
{ key: 'position', label: '详细地址' },
|
||||
{ key: 'code', label: '业务员', format: (_, info) => promoterText(info) },
|
||||
{ key: 'code', label: '业务员', type: 'promoter' },
|
||||
{ key: 'is_shipping_free', label: '是否包邮', format: (_, info) => yesNoText(info.is_shipping_free) },
|
||||
{ key: 'subscribe_price_change', label: '订阅价格波动', format: (_, info) => yesNoText(info.subscribe_price_change) },
|
||||
{
|
||||
|
||||
101
subPackages/sub_salesperson/components/PromoterDetailPopup.vue
Normal file
101
subPackages/sub_salesperson/components/PromoterDetailPopup.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<drawer-page-container v-model="visible" title="业务员详情">
|
||||
<view v-if="loading" class="loading">加载中...</view>
|
||||
<view v-else-if="detail" class="detail-wrap">
|
||||
<view class="header">
|
||||
<image v-if="detail.avatar" :src="detail.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ (detail.nick_name || '?').charAt(0) }}</view>
|
||||
<text class="name">{{ detail.nick_name || '-' }}</text>
|
||||
<text class="role">{{ detail.role_name || '业务员' }}</text>
|
||||
</view>
|
||||
<view class="row"><text class="k">推广码</text><text class="v">{{ detail.code || '-' }}</text></view>
|
||||
<view class="row"><text class="k">手机号</text><text class="v">{{ detail.phone || '-' }}</text></view>
|
||||
<view class="row"><text class="k">录入诊所</text><text class="v">{{ detail.clinic_input_count || 0 }} 家</text></view>
|
||||
<view class="row"><text class="k">录入药店</text><text class="v">{{ detail.pharmacy_input_count || 0 }} 家</text></view>
|
||||
</view>
|
||||
<view v-else class="loading">暂无数据</view>
|
||||
</drawer-page-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DrawerPageContainer from '@/subPackages/sub_salesperson/components/DrawerPageContainer.vue'
|
||||
import { getInputBusinessContext } from '@/subPackages/sub_salesperson/common/context.js'
|
||||
|
||||
export default {
|
||||
name: 'PromoterDetailPopup',
|
||||
components: { DrawerPageContainer },
|
||||
props: {
|
||||
value: { type: Boolean, default: false },
|
||||
admin: { type: Object, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
detail: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() { return this.value },
|
||||
set(v) { this.$emit('input', v) },
|
||||
},
|
||||
bizApi() {
|
||||
return getInputBusinessContext().api
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(open) {
|
||||
if (open) this.loadDetail()
|
||||
else this.detail = null
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadDetail() {
|
||||
const id = this.admin?.id
|
||||
const code = this.admin?.code
|
||||
if (!id && !code) return
|
||||
this.loading = true
|
||||
this.bizApi.getPromoterDetail({ id, code })
|
||||
.then((w) => {
|
||||
if (w.ok) this.detail = w.data || null
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading { text-align: center; color: #86909c; padding: 80rpx 0; font-size: 28rpx; }
|
||||
.detail-wrap { padding: 0 32rpx 32rpx; }
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding: 32rpx 0 40rpx;
|
||||
}
|
||||
.avatar { width: 128rpx; height: 128rpx; border-radius: 50%; }
|
||||
.avatar.placeholder {
|
||||
background: #e8f7f4;
|
||||
color: #6acdbb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 48rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.name { font-size: 34rpx; font-weight: 600; color: #1d2129; }
|
||||
.role { font-size: 26rpx; color: #86909c; }
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.k { color: #86909c; }
|
||||
.v { color: #1d2129; max-width: 420rpx; text-align: right; word-break: break-all; }
|
||||
</style>
|
||||
80
subPackages/sub_salesperson/components/PromoterInfoCard.vue
Normal file
80
subPackages/sub_salesperson/components/PromoterInfoCard.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<view class="promoter-info-card">
|
||||
<view
|
||||
v-if="displayAdmin"
|
||||
class="card-body"
|
||||
:class="{ clickable: clickable && canOpenDetail }"
|
||||
@click.stop="openDetail"
|
||||
>
|
||||
<image v-if="displayAdmin.avatar" :src="displayAdmin.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ displayName.charAt(0) }}</view>
|
||||
<view class="meta">
|
||||
<text class="name">{{ displayName }}</text>
|
||||
<text v-if="displayAdmin.phone" class="sub">{{ displayAdmin.phone }}</text>
|
||||
<text v-else-if="displayAdmin.code" class="sub">业务码:{{ displayAdmin.code }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-else class="empty">-</text>
|
||||
<promoter-detail-popup v-model="showDetail" :admin="displayAdmin" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PromoterDetailPopup from '@/subPackages/sub_salesperson/components/PromoterDetailPopup.vue'
|
||||
|
||||
export default {
|
||||
name: 'PromoterInfoCard',
|
||||
components: { PromoterDetailPopup },
|
||||
props: {
|
||||
admin: { type: Object, default: null },
|
||||
clickable: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return { showDetail: false }
|
||||
},
|
||||
computed: {
|
||||
displayAdmin() {
|
||||
if (!this.admin) return null
|
||||
if (this.admin.id || this.admin.code || this.admin.nick_name) return this.admin
|
||||
return null
|
||||
},
|
||||
displayName() {
|
||||
return this.displayAdmin?.nick_name?.trim() || this.displayAdmin?.phone?.trim() || '-'
|
||||
},
|
||||
canOpenDetail() {
|
||||
return !!(this.displayAdmin?.id || this.displayAdmin?.code)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openDetail() {
|
||||
if (!this.clickable || !this.canOpenDetail) return
|
||||
this.showDetail = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.promoter-info-card { min-width: 0; }
|
||||
.card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
.card-body.clickable:active { opacity: 0.85; }
|
||||
.avatar { width: 56rpx; height: 56rpx; border-radius: 50%; flex-shrink: 0; }
|
||||
.avatar.placeholder {
|
||||
background: #e8f7f4;
|
||||
color: #6acdbb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.meta { flex: 1; min-width: 0; }
|
||||
.name { display: block; font-size: 26rpx; color: #1d2129; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.sub { display: block; font-size: 22rpx; color: #86909c; margin-top: 4rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.empty { font-size: 26rpx; color: #86909c; }
|
||||
</style>
|
||||
@@ -1,44 +1,65 @@
|
||||
<template>
|
||||
<view class="picker-wrap">
|
||||
<view class="promoter-picker" @click="onOpen">
|
||||
<view v-if="selected" class="promoter-selected">
|
||||
<image v-if="selected.avatar" :src="selected.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ (selected.nick_name || '?').charAt(0) }}</view>
|
||||
<view class="meta">
|
||||
<text class="name">{{ selected.nick_name || '-' }}</text>
|
||||
<text class="sub">{{ selected.code }} · {{ selected.role_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text v-else class="placeholder-text">{{ placeholder }}</text>
|
||||
<u-icon v-if="!disabled" name="arrow-right" color="#c0c4cc" size="28"></u-icon>
|
||||
</view>
|
||||
|
||||
<drawer-page-container v-model="showPopup" title="选择业务员">
|
||||
<scroll-view scroll-y class="sheet-scroll">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!options.length" class="empty">暂无可选业务员</view>
|
||||
<view
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
class="option-item"
|
||||
@click="onPick(item)"
|
||||
>
|
||||
<image v-if="item.avatar" :src="item.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ (item.nick_name || '?').charAt(0) }}</view>
|
||||
<view v-if="selected" class="promoter-selected">
|
||||
<image v-if="selected.avatar" :src="selected.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ (selected.nick_name || '?').charAt(0) }}</view>
|
||||
<view class="meta">
|
||||
<text class="name">{{ item.nick_name || '-' }}</text>
|
||||
<text class="sub">{{ item.code }} · {{ item.role_name }}</text>
|
||||
<text class="name">{{ selected.nick_name || '-' }}</text>
|
||||
<text class="sub">{{ selectedSubText }}</text>
|
||||
</view>
|
||||
<u-icon v-if="value === item.code" name="checkmark" color="#6acdbb" size="32"></u-icon>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</drawer-page-container>
|
||||
<text v-else class="placeholder-text">{{ placeholder }}</text>
|
||||
<u-icon v-if="!disabled" name="arrow-right" color="#c0c4cc" size="28"></u-icon>
|
||||
</view>
|
||||
|
||||
<drawer-page-container v-model="showPopup" title="选择业务员">
|
||||
<view class="search-wrap">
|
||||
<u-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索姓名、业务码、手机号"
|
||||
:clearable="true"
|
||||
border="surround"
|
||||
/>
|
||||
</view>
|
||||
<scroll-view scroll-y class="sheet-scroll">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!filteredOptions.length" class="empty">无匹配业务员</view>
|
||||
<view
|
||||
v-for="item in filteredOptions"
|
||||
:key="item.id"
|
||||
class="option-item"
|
||||
@click="onPick(item)"
|
||||
>
|
||||
<image v-if="item.avatar" :src="item.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar placeholder">{{ (item.nick_name || '?').charAt(0) }}</view>
|
||||
<view class="meta">
|
||||
<text class="name">{{ item.nick_name || '-' }}</text>
|
||||
<text class="sub">{{ item.code }} · {{ item.role_name || '业务员' }}</text>
|
||||
<text v-if="item.phone" class="phone">{{ item.phone }}</text>
|
||||
</view>
|
||||
<u-icon v-if="value === item.code" name="checkmark" color="#6acdbb" size="32"></u-icon>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</drawer-page-container>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DrawerPageContainer from '@/subPackages/sub_salesperson/components/DrawerPageContainer.vue'
|
||||
|
||||
function filterPromoterOptions(keyword, list) {
|
||||
const q = String(keyword || '').trim().toLowerCase()
|
||||
if (!q) return list
|
||||
return list.filter((item) => {
|
||||
const name = (item.nick_name || '').toLowerCase()
|
||||
const code = (item.code || '').toLowerCase()
|
||||
const phone = (item.phone || '').toLowerCase()
|
||||
const role = (item.role_name || '').toLowerCase()
|
||||
return name.includes(q) || code.includes(q) || phone.includes(q) || role.includes(q)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'PromoterPicker',
|
||||
components: { DrawerPageContainer },
|
||||
@@ -50,16 +71,31 @@ export default {
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return { showPopup: false }
|
||||
return {
|
||||
showPopup: false,
|
||||
searchKeyword: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.options.find((o) => o.code === this.value) || null
|
||||
},
|
||||
filteredOptions() {
|
||||
return filterPromoterOptions(this.searchKeyword, this.options)
|
||||
},
|
||||
selectedSubText() {
|
||||
if (!this.selected) return ''
|
||||
const parts = []
|
||||
if (this.selected.code) parts.push(this.selected.code)
|
||||
if (this.selected.phone) parts.push(this.selected.phone)
|
||||
else if (this.selected.role_name) parts.push(this.selected.role_name)
|
||||
return parts.join(' · ')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onOpen() {
|
||||
if (this.disabled) return
|
||||
this.searchKeyword = ''
|
||||
this.showPopup = true
|
||||
},
|
||||
onPick(item) {
|
||||
@@ -72,9 +108,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.picker-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.picker-wrap { width: 100%; }
|
||||
.promoter-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -98,8 +132,10 @@ export default {
|
||||
.meta { flex: 1; min-width: 0; }
|
||||
.name { display: block; font-size: 28rpx; color: #1d2129; }
|
||||
.sub { display: block; font-size: 24rpx; color: #86909c; margin-top: 6rpx; }
|
||||
.phone { display: block; font-size: 22rpx; color: #86909c; margin-top: 4rpx; }
|
||||
.placeholder-text { font-size: 28rpx; color: #c0c4cc; flex: 1; }
|
||||
.sheet-scroll { max-height: 60vh; padding-bottom: 24rpx; }
|
||||
.search-wrap { padding: 0 32rpx 16rpx; }
|
||||
.sheet-scroll { max-height: 55vh; padding-bottom: 24rpx; }
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
@click="openFile(file.url)"
|
||||
>{{ file.name }}</text>
|
||||
</view>
|
||||
<view v-else-if="field.type === 'promoter'" class="v-promoter">
|
||||
<promoter-info-card :admin="promoterAdmin" />
|
||||
</view>
|
||||
<text v-else class="v-text">{{ fieldValue(field) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -80,6 +83,9 @@
|
||||
<view class="footer-actions">
|
||||
<u-button v-if="canEdit" class="action-btn" type="primary" :custom-style="primaryStyle" @click="goEdit">继续编辑</u-button>
|
||||
<u-button v-if="showDoctorEntry" class="action-btn" type="primary" :custom-style="primaryStyle" @click="goDoctorInput">录入医生</u-button>
|
||||
<u-button v-if="showBankCardReport" class="action-btn" type="primary" plain @click="goBankCardReport">
|
||||
{{ bankCardReportButtonText }}
|
||||
</u-button>
|
||||
<template v-if="canAudit">
|
||||
<u-button class="action-btn reject" @click="audit(2)">审核拒绝</u-button>
|
||||
<u-button class="action-btn" type="primary" :custom-style="primaryStyle" @click="audit(1)">审核通过</u-button>
|
||||
@@ -91,6 +97,7 @@
|
||||
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_salesperson/components/business-page-layout.vue'
|
||||
import PromoterInfoCard from '@/subPackages/sub_salesperson/components/PromoterInfoCard.vue'
|
||||
import businessMixin from '@/subPackages/sub_salesperson/common/businessMixin.js'
|
||||
import {
|
||||
getStoreDetailSections,
|
||||
@@ -99,13 +106,14 @@ import {
|
||||
getDetailImageUrls,
|
||||
getDetailFileLinks,
|
||||
getLinkedDoctorInputs,
|
||||
getPromoterAdmin,
|
||||
inputStatusText,
|
||||
} from '@/subPackages/sub_salesperson/common/inputDetailFields.js'
|
||||
import { goDoctorInputFromStore } from '@/subPackages/sub_salesperson/common/inputNavigation.js'
|
||||
|
||||
export default {
|
||||
mixins: [businessMixin],
|
||||
components: { BusinessPageLayout },
|
||||
components: { BusinessPageLayout, PromoterInfoCard },
|
||||
props: {
|
||||
recordId: { type: Number, default: 0 },
|
||||
inputType: { type: String, default: 'store' },
|
||||
@@ -150,6 +158,19 @@ export default {
|
||||
linkedDoctors() {
|
||||
return getLinkedDoctorInputs(this.info)
|
||||
},
|
||||
promoterAdmin() {
|
||||
return getPromoterAdmin(this.info)
|
||||
},
|
||||
showBankCardReport() {
|
||||
return this.inputType === 'store'
|
||||
&& Number(this.info.status) === 1
|
||||
&& Number(this.info.synced_store_id) > 0
|
||||
},
|
||||
bankCardReportButtonText() {
|
||||
const text = this.info.bank_card_report_status_text
|
||||
if (text && text !== '未报备') return `银行卡报备(${text})`
|
||||
return '银行卡报备'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
recordId: {
|
||||
@@ -192,6 +213,9 @@ export default {
|
||||
},
|
||||
shouldShowField(field) {
|
||||
if (field.show && !field.show(this.info)) return false
|
||||
if (field.type === 'promoter') {
|
||||
return !!getPromoterAdmin(this.info)
|
||||
}
|
||||
if (field.type === 'image' || field.type === 'images') {
|
||||
return getDetailImageUrls(field, this.info).length > 0
|
||||
}
|
||||
@@ -226,6 +250,13 @@ export default {
|
||||
goDoctorInput() {
|
||||
goDoctorInputFromStore(this.info.id, this.bizCtx)
|
||||
},
|
||||
goBankCardReport() {
|
||||
const storeId = Number(this.info.synced_store_id || 0)
|
||||
if (!storeId) return
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_salesperson/store-bank-card/report?store_id=${storeId}&store_input_id=${this.info.id}`,
|
||||
})
|
||||
},
|
||||
goDoctorDetail(doctorId) {
|
||||
if (!doctorId || !this.doctorDetailPath) return
|
||||
uni.navigateTo({ url: `${this.doctorDetailPath}?id=${doctorId}` })
|
||||
|
||||
278
subPackages/sub_salesperson/store-bank-card/report.vue
Normal file
278
subPackages/sub_salesperson/store-bank-card/report.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<business-page-layout title="银行卡报备" :show-back="true">
|
||||
<view class="page-wrap">
|
||||
<view v-if="loading" class="loading">加载中...</view>
|
||||
<view v-else>
|
||||
<view v-if="reportInfo && reportInfo.id" class="status-card">
|
||||
<text class="status-label">报备状态</text>
|
||||
<text class="status-value">{{ reportStatusText }}</text>
|
||||
<u-button size="mini" class="sync-btn" @click="syncStatus">刷新状态</u-button>
|
||||
</view>
|
||||
|
||||
<view v-if="!hasReport || canSubmitNew" class="form-wrap">
|
||||
<view class="tip">新报备需上传合同附件,字段与 PC 端一致</view>
|
||||
<u-form :model="form" label-width="180">
|
||||
<u-form-item label="开户人姓名" :required="true">
|
||||
<u-input v-model="form.bank_user_name" placeholder="请输入开户人姓名" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银行卡号" :required="true">
|
||||
<u-input v-model="form.bank_card" type="number" placeholder="请输入银行卡号" />
|
||||
</u-form-item>
|
||||
<u-form-item label="开户银行" :required="true">
|
||||
<u-input v-model="form.bank_name" placeholder="请输入开户银行名称" />
|
||||
</u-form-item>
|
||||
<u-form-item label="账户类型" :required="true">
|
||||
<u-input :value="accountTypeLabel" type="select" placeholder="请选择" @click="showAccountType = true" />
|
||||
<u-select v-model="showAccountType" :list="accountTypeOptions" @confirm="onAccountTypeConfirm" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银联行号">
|
||||
<u-input v-model="form.bank_no" placeholder="对公/非62开头卡号必填" />
|
||||
</u-form-item>
|
||||
<u-form-item label="证件号码">
|
||||
<u-input v-model="form.cert_no" placeholder="选填" />
|
||||
</u-form-item>
|
||||
<u-form-item label="合同附件" :required="true">
|
||||
<view class="attachment-row">
|
||||
<text v-if="form.attachment" class="attachment-name" @click="previewAttachment">{{ attachmentName }}</text>
|
||||
<text v-else class="attachment-placeholder">请上传合同附件</text>
|
||||
<u-button size="mini" @click="uploadAttachment">上传</u-button>
|
||||
</view>
|
||||
</u-form-item>
|
||||
<u-form-item label="备注">
|
||||
<u-input v-model="form.remark" placeholder="选填" />
|
||||
</u-form-item>
|
||||
<u-form-item label="摘要">
|
||||
<u-input v-model="form.summary" placeholder="选填" />
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<u-button type="primary" :custom-style="primaryStyle" :loading="submitting" @click="submit">提交报备</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</business-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BusinessPageLayout from '@/subPackages/sub_salesperson/components/business-page-layout.vue'
|
||||
import { getStoreBankCardReportDetail, reportStoreBankCard, syncStoreBankCardStatusByStore } from '@/api/bankCardReport.js'
|
||||
import { pickAndUploadImage } from '@/subPackages/sub_salesperson/common/inputFormHelpers.js'
|
||||
import { uploadToOss } from '@/common/js/oss-upload.js'
|
||||
import { prepareImagePath } from '@/common/js/image-compress.js'
|
||||
|
||||
const ACCOUNT_TYPES = [
|
||||
{ label: '对公账户', value: '1' },
|
||||
{ label: '法人账户', value: '2' },
|
||||
{ label: '授权对公账户', value: '3' },
|
||||
{ label: '授权个人账户', value: '4' },
|
||||
]
|
||||
|
||||
export default {
|
||||
components: { BusinessPageLayout },
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
storeInputId: 0,
|
||||
loading: true,
|
||||
submitting: false,
|
||||
reportInfo: null,
|
||||
showAccountType: false,
|
||||
form: {
|
||||
bank_user_name: '',
|
||||
bank_card: '',
|
||||
bank_name: '',
|
||||
account_type: '2',
|
||||
bank_no: '',
|
||||
cert_no: '',
|
||||
attachment: '',
|
||||
remark: '',
|
||||
summary: '',
|
||||
use_type: '2',
|
||||
},
|
||||
primaryStyle: {
|
||||
backgroundColor: '#6acdbb',
|
||||
color: '#ffffff',
|
||||
borderColor: '#6acdbb',
|
||||
marginTop: '32rpx',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
accountTypeOptions() {
|
||||
return ACCOUNT_TYPES
|
||||
},
|
||||
accountTypeLabel() {
|
||||
const item = ACCOUNT_TYPES.find((o) => o.value === this.form.account_type)
|
||||
return item ? item.label : '请选择'
|
||||
},
|
||||
hasReport() {
|
||||
return !!(this.reportInfo && this.reportInfo.id)
|
||||
},
|
||||
canSubmitNew() {
|
||||
if (!this.hasReport) return true
|
||||
return [3, 4].includes(Number(this.reportInfo.report_status))
|
||||
},
|
||||
reportStatusText() {
|
||||
const s = Number(this.reportInfo?.report_status)
|
||||
if (s === 2) return '有效'
|
||||
if (s === 3) return '审核不通过'
|
||||
if (s === 4) return '无效'
|
||||
if (s === 1) return '待审核'
|
||||
return this.reportInfo?.report_status_text || '未知'
|
||||
},
|
||||
attachmentName() {
|
||||
if (!this.form.attachment) return ''
|
||||
return this.form.attachment.split('/').pop() || '已上传'
|
||||
},
|
||||
},
|
||||
onLoad(q) {
|
||||
this.storeId = Number(q.store_id || 0)
|
||||
this.storeInputId = Number(q.store_input_id || 0)
|
||||
this.loadDetail()
|
||||
},
|
||||
methods: {
|
||||
loadDetail() {
|
||||
if (!this.storeId) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getStoreBankCardReportDetail(this.storeId)
|
||||
.then((w) => {
|
||||
if (w.ok && w.data) {
|
||||
this.reportInfo = w.data
|
||||
this.applyDraft(w.data)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
applyDraft(data) {
|
||||
if (!data || data.id) return
|
||||
this.form.bank_user_name = data.bank_user_name || ''
|
||||
this.form.bank_card = data.bank_card || ''
|
||||
this.form.bank_name = data.bank_name || ''
|
||||
this.form.account_type = String(data.account_type || data.bank_account_type || '2')
|
||||
this.form.bank_no = data.bank_no || ''
|
||||
this.form.cert_no = data.cert_no || ''
|
||||
},
|
||||
onAccountTypeConfirm(e) {
|
||||
const item = e[0]
|
||||
if (item) this.form.account_type = String(item.value)
|
||||
},
|
||||
uploadAttachment() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['相册/拍照', '选择文件'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
pickAndUploadImage().then((url) => {
|
||||
this.form.attachment = url
|
||||
}).catch(() => {})
|
||||
} else {
|
||||
this.chooseAndUploadFile()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
chooseAndUploadFile() {
|
||||
uni.chooseMessageFile({
|
||||
count: 1,
|
||||
type: 'file',
|
||||
success: async (res) => {
|
||||
const file = res.tempFiles && res.tempFiles[0]
|
||||
if (!file) return
|
||||
uni.showLoading({ title: '上传中...', mask: true })
|
||||
try {
|
||||
const prepared = await prepareImagePath({ path: file.path, size: file.size })
|
||||
const uploadPath = prepared ? prepared.path : file.path
|
||||
const result = await uploadToOss({ filePath: uploadPath })
|
||||
this.form.attachment = result.url
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.message || '上传失败', icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
previewAttachment() {
|
||||
if (!this.form.attachment) return
|
||||
const url = this.form.attachment
|
||||
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(url)) {
|
||||
uni.previewImage({ urls: [url] })
|
||||
} else {
|
||||
uni.setClipboardData({ data: url })
|
||||
uni.showToast({ title: '链接已复制', icon: 'none' })
|
||||
}
|
||||
},
|
||||
validateForm() {
|
||||
const f = this.form
|
||||
if (!f.bank_user_name?.trim()) return this.toast('请输入开户人姓名')
|
||||
if (!f.bank_card?.trim()) return this.toast('请输入银行卡号')
|
||||
if (!f.bank_name?.trim()) return this.toast('请输入开户银行')
|
||||
if (!f.account_type) return this.toast('请选择账户类型')
|
||||
if (!f.attachment?.trim()) return this.toast('请上传合同附件')
|
||||
return true
|
||||
},
|
||||
toast(msg) {
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
return false
|
||||
},
|
||||
submit() {
|
||||
if (!this.validateForm()) return
|
||||
this.submitting = true
|
||||
reportStoreBankCard({
|
||||
store_id: this.storeId,
|
||||
store_input_id: this.storeInputId,
|
||||
...this.form,
|
||||
bank_account_type: this.mapAccountTypeToStore(this.form.account_type),
|
||||
})
|
||||
.then((w) => {
|
||||
if (w.ok) {
|
||||
uni.showToast({ title: '报备已提交', icon: 'success' })
|
||||
setTimeout(() => this.loadDetail(), 500)
|
||||
} else {
|
||||
uni.showToast({ title: (w.res && w.res.msg) || '提交失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
},
|
||||
mapAccountTypeToStore(accountType) {
|
||||
const map = { '1': 1, '2': 2, '3': 1, '4': 2 }
|
||||
return map[String(accountType)] || 2
|
||||
},
|
||||
syncStatus() {
|
||||
syncStoreBankCardStatusByStore(this.storeId).then((w) => {
|
||||
if (w.ok) {
|
||||
this.reportInfo = w.data
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-wrap { padding: 24rpx; }
|
||||
.loading { text-align: center; color: #86909c; padding: 80rpx 0; }
|
||||
.status-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.status-label { color: #86909c; font-size: 26rpx; }
|
||||
.status-value { color: #1d2129; font-size: 30rpx; font-weight: 600; flex: 1; }
|
||||
.form-wrap { background: #fff; border-radius: 16rpx; padding: 24rpx; }
|
||||
.tip { font-size: 24rpx; color: #86909c; margin-bottom: 24rpx; line-height: 1.5; }
|
||||
.attachment-row { display: flex; align-items: center; gap: 16rpx; width: 100%; }
|
||||
.attachment-name { flex: 1; font-size: 24rpx; color: #165dff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.attachment-placeholder { flex: 1; font-size: 26rpx; color: #c0c4cc; }
|
||||
</style>
|
||||
@@ -132,20 +132,21 @@
|
||||
</u-form-item>
|
||||
</view>
|
||||
|
||||
<!-- ================= 步骤 3:结算信息 ================= -->
|
||||
<!-- ================= 步骤 3:结算信息(可选) ================= -->
|
||||
<view v-show="currentStep === 2" class="step-content">
|
||||
<view class="section-title">银行卡信息</view>
|
||||
<view class="step-tip">可选填写;也可在审核通过后于详情页单独报备</view>
|
||||
<u-form-item label="账户类型" right-icon="arrow-right">
|
||||
<u-input :value="bankTypes[bankTypeIndex].label" type="select" @click="showBankType = true" placeholder="请选择" />
|
||||
<u-select v-model="showBankType" :list="bankTypes" @confirm="onBankTypeConfirm"></u-select>
|
||||
</u-form-item>
|
||||
<u-form-item label="开户人姓名" :required="true">
|
||||
<u-form-item label="开户人姓名">
|
||||
<u-input v-model="form.bank_user_name" placeholder="请输入开户人姓名" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银行卡号" :required="true">
|
||||
<u-form-item label="银行卡号">
|
||||
<u-input v-model="form.bank_card" type="number" placeholder="请输入银行卡号" />
|
||||
</u-form-item>
|
||||
<u-form-item label="开户行" :required="true">
|
||||
<u-form-item label="开户行">
|
||||
<u-input v-model="form.bank_name" placeholder="请输入开户行名称 (如: 招商银行)" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银联行号">
|
||||
@@ -438,13 +439,19 @@ export default {
|
||||
return false;
|
||||
},
|
||||
|
||||
validateOptionalBankFields() {
|
||||
const f = this.form
|
||||
const hasAny = !!(f.bank_user_name?.trim() || f.bank_card?.trim() || f.bank_name?.trim() || f.bank_no?.trim())
|
||||
if (!hasAny) return true
|
||||
if (!f.bank_user_name?.trim()) return this.showError('填写银行卡时请填写开户人姓名')
|
||||
if (!f.bank_card?.trim()) return this.showError('填写银行卡时请填写卡号')
|
||||
if (!f.bank_name?.trim()) return this.showError('填写银行卡时请填写开户行')
|
||||
return true
|
||||
},
|
||||
|
||||
// --- 最终提交 ---
|
||||
submit() {
|
||||
// 最终提交前校验当前(最后一步)的信息
|
||||
const f = this.form;
|
||||
if (!f.bank_user_name?.trim()) return this.showError('请输入开户人姓名');
|
||||
if (!f.bank_card?.trim()) return this.showError('请输入银行卡号');
|
||||
if (!f.bank_name?.trim()) return this.showError('请输入开户行');
|
||||
if (!this.validateOptionalBankFields()) return
|
||||
|
||||
const payload = {
|
||||
...this.form,
|
||||
@@ -501,6 +508,7 @@ export default {
|
||||
|
||||
/* 标题 */
|
||||
.section-title { font-size: 32rpx; font-weight: 600; padding: 40rpx 0 20rpx; color: #1D2129; position: relative; }
|
||||
.step-tip { font-size: 24rpx; color: #86909c; margin: -8rpx 0 16rpx; line-height: 1.5; }
|
||||
.section-title::after { content: ''; position: absolute; left: -32rpx; top: 46rpx; width: 8rpx; height: 32rpx; background: #6acdbb; border-radius: 0 4rpx 4rpx 0; }
|
||||
|
||||
/* 营业时间特别处理 */
|
||||
|
||||
Reference in New Issue
Block a user