Files
xk-doctor-wx/subPackages/sub_platform_admin/store/detail.vue
李琦 cb290f3e95 fix(login): 密码登录补充密码非空前端校验
- doctor-login submitLogin 增加「请输入密码」前置校验,
  与 getCode 一致,避免空密码误触发登录请求
- 配合后端 service_user 空密码回退 PC 医生/药师密码校验
2026-07-19 16:08:23 +08:00

257 lines
9.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<store-page-layout title="门店详情" :show-back="true">
<view class="page" v-if="store.id">
<view class="hero-card">
<text class="name">{{ store.name }}</text>
<text class="sub">ID {{ store.id }} · {{ clinicTypeText(store.clinic_type) }}</text>
<text class="addr">{{ store.position || '暂无地址' }}</text>
</view>
<!-- 只读信息业务员无编辑权限时展示配置摘要 -->
<view v-if="!canEdit" class="section">
<text class="section-title">门店信息</text>
<view class="info-row"><text>推广员可见价格</text><text>{{ Number(store.salesperson_see_price) === 1 ? '是' : '否' }}</text></view>
<view class="info-row"><text>包邮</text><text>{{ Number(store.is_shipping_free) === 1 ? '是' : '否' }}</text></view>
<view class="info-row"><text>订阅价格波动</text><text>{{ Number(store.subscribe_price_change) === 0 ? '是' : '否' }}</text></view>
<view class="info-row"><text>查看毛利率</text><text>{{ Number(store.see_rate) === 1 ? '是' : '否' }}</text></view>
<view class="info-row"><text>开方可选医保</text><text>{{ Number(store.allow_insurance_category) === 1 ? '是' : '否' }}</text></view>
<view class="info-row"><text>诊所类型</text><text>{{ clinicTypeText(store.clinic_type) }}</text></view>
</view>
<view v-if="canEdit" class="section">
<text class="section-title">门店配置</text>
<view v-if="!isSalesperson" class="switch-row">
<text>推广员可见价格</text>
<switch :checked="Number(store.salesperson_see_price) === 1" color="#6ACDBB" @change="toggleSeePrice" />
</view>
<view class="switch-row">
<text>包邮</text>
<switch :checked="Number(store.is_shipping_free) === 1" color="#6ACDBB" @change="toggleShipping" />
</view>
<view class="switch-row">
<text>订阅价格波动</text>
<switch :checked="Number(store.subscribe_price_change) === 0" color="#6ACDBB" @change="toggleSubscribe" />
</view>
<view class="switch-row">
<text>查看毛利率</text>
<switch :checked="Number(store.see_rate) === 1" color="#6ACDBB" @change="toggleSeeRate" />
</view>
<view class="switch-row">
<text>开方可选医保</text>
<switch :checked="Number(store.allow_insurance_category) === 1" color="#6ACDBB" @change="toggleInsurance" />
</view>
<view class="picker-row">
<text>诊所类型</text>
<picker :range="clinicTypeLabels" :value="clinicTypeIndex" @change="onClinicType">
<view class="picker-val">{{ clinicTypeLabels[clinicTypeIndex] }}</view>
</picker>
</view>
</view>
<view class="section">
<text class="section-title">快捷操作</text>
<view class="action-grid">
<view v-if="canEdit" class="action-btn" @click="goEdit">编辑资料</view>
<view v-if="canEdit" class="action-btn" @click="goDrugPrice">修改药品价格</view>
<view v-if="canEdit" class="action-btn" @click="goConsultation">在线复诊配置</view>
<view v-if="canEdit && !isSalesperson" class="action-btn" @click="goPromoters">管理推广员</view>
<view class="action-btn" @click="openQr">诊所二维码</view>
<view class="action-btn" @click="goBankCardReport">{{ bankCardReportButtonText }}</view>
<view v-if="canEdit && !hasAdminAccount" class="action-btn warn" @click="openPc">开通后台</view>
</view>
</view>
<view v-if="store.online_consultation_doctor_name || store.delegate_store_name" class="section">
<text class="section-title">在线复诊</text>
<text v-if="store.delegate_store_name" class="info-line">委托诊所{{ store.delegate_store_name }}</text>
<text v-if="store.online_consultation_doctor_name" class="info-line">负责人{{ store.online_consultation_doctor_name }}</text>
</view>
</view>
</store-page-layout>
</template>
<script>
/**
* 门店详情:超管可编辑;业务员默认只读,系统配置开启后显示编辑入口
*/
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
import {
getStoreDetail,
getSalespersonStoreEditPermission,
toggleSalespersonSeePrice,
updateStoreShippingFree,
updateStoreSubscribeStatus,
updateStoreSeeRate,
updateStoreAllowInsurance,
updateStoreClinicType,
openStorePcWindows,
openStoreQrCode,
isSalespersonStoreMode,
} from '@/api/storeManage.js';
import { openQrCodePreview, buildClinicQrPayload } from '@/utils/qrCodePreview.js';
const ROOT = '/subPackages/sub_platform_admin/store';
const MANAGE_ROOT = '/subPackages/sub_salesperson_manage';
export default {
components: { StorePageLayout },
data() {
return {
storeId: 0,
store: {},
clinicTypeLabels: ['未设置', '西医诊所', '中医诊所'],
isSalesperson: false,
/** 业务员编辑开关;超管始终 true */
salespersonEditEnabled: false,
};
},
computed: {
canEdit() {
if (!this.isSalesperson) return true;
return !!this.salespersonEditEnabled;
},
clinicTypeIndex() {
return Number(this.store.clinic_type) || 0;
},
hasAdminAccount() {
const admin = this.store.new_admin || this.store.newAdmin;
return !!(admin && (admin.id || admin.code));
},
bankCardReportButtonText() {
const text = this.store.bank_card_report_status_text;
if (Number(this.store.bank_card_report_status) === 0 || !text || text === '未报备') {
return '银行卡报备';
}
return `银行卡报备(${text}`;
},
},
onLoad(options) {
this.storeId = Number(options.id || 0);
this.isSalesperson = isSalespersonStoreMode();
this.bootstrap();
},
methods: {
clinicTypeText(type) {
return this.clinicTypeLabels[Number(type) || 0] || '未设置';
},
async bootstrap() {
if (this.isSalesperson) {
try {
const wrap = await getSalespersonStoreEditPermission();
const data = wrap && wrap.ok ? wrap.data : null;
this.salespersonEditEnabled = !!(data && data.salesperson_store_edit_enabled);
} catch (e) {
this.salespersonEditEnabled = false;
}
}
await this.loadDetail();
},
async loadDetail() {
if (!this.storeId) return;
const wrap = await getStoreDetail(this.storeId);
if (wrap && wrap.ok) {
this.store = wrap.data || {};
}
},
async toggleSeePrice() {
const wrap = await toggleSalespersonSeePrice(this.storeId);
if (wrap && wrap.ok && wrap.data) {
this.store.salesperson_see_price = wrap.data.see_price;
}
},
async toggleShipping() {
const wrap = await updateStoreShippingFree(this.storeId);
if (wrap && wrap.ok) await this.loadDetail();
},
async toggleSubscribe() {
const wrap = await updateStoreSubscribeStatus(this.storeId);
if (wrap && wrap.ok) await this.loadDetail();
},
async toggleSeeRate() {
const wrap = await updateStoreSeeRate(this.storeId);
if (wrap && wrap.ok) await this.loadDetail();
},
async toggleInsurance() {
const wrap = await updateStoreAllowInsurance(this.storeId);
if (wrap && wrap.ok) await this.loadDetail();
},
async onClinicType(e) {
const idx = Number(e.detail.value);
if (idx <= 0) return;
const wrap = await updateStoreClinicType(this.storeId, idx);
if (wrap && wrap.ok) {
this.store.clinic_type = idx;
uni.showToast({ title: '已更新', icon: 'success' });
}
},
goEdit() {
uni.navigateTo({ url: `${ROOT}/edit?id=${this.storeId}` });
},
goDrugPrice() {
uni.navigateTo({ url: `${ROOT}/drug-price?id=${this.storeId}&name=${encodeURIComponent(this.store.name || '')}` });
},
goConsultation() {
uni.navigateTo({ url: `${ROOT}/consultation?id=${this.storeId}` });
},
goPromoters() {
uni.navigateTo({ url: `${MANAGE_ROOT}/index?mode=platform&store_id=${this.storeId}` });
},
goBankCardReport() {
if (!this.storeId) return;
uni.navigateTo({
url: `/subPackages/sub_salesperson/store-bank-card/report?store_id=${this.storeId}`,
});
},
async openQr() {
uni.showLoading({ title: '生成中' });
try {
const wrap = await openStoreQrCode(this.storeId);
if (wrap && wrap.ok) {
await this.loadDetail();
const payload = buildClinicQrPayload(this.store);
openQrCodePreview({ type: 'clinic', title: this.store.name, payload: payload.payload });
}
} finally {
uni.hideLoading();
}
},
async openPc() {
uni.showModal({
title: '开通后台',
content: '确定为该诊所开通 PC 后台账号?',
success: async (res) => {
if (!res.confirm) return;
const wrap = await openStorePcWindows(this.storeId);
if (wrap && wrap.ok) {
uni.showToast({ title: '开通成功', icon: 'success' });
await this.loadDetail();
}
},
});
},
},
};
</script>
<style lang="scss" scoped>
.page { padding: 24rpx; }
.hero-card { background: linear-gradient(135deg, #6acdbb, #4db6a8); border-radius: 20rpx; padding: 32rpx; color: #fff; margin-bottom: 24rpx; }
.name { display: block; font-size: 36rpx; font-weight: 700; }
.sub, .addr { display: block; font-size: 24rpx; margin-top: 8rpx; opacity: 0.9; }
.section { background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; }
.section-title { display: block; font-size: 28rpx; font-weight: 600; margin-bottom: 16rpx; }
.switch-row, .picker-row, .info-row {
display: flex; justify-content: space-between; align-items: center;
padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; font-size: 28rpx;
}
.info-row text:last-child { color: #6b7280; }
.picker-val { color: #6ACDBB; }
.action-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
.action-btn {
flex: 1; min-width: 40%; text-align: center; padding: 20rpx;
background: #f0fbf8; color: #6ACDBB; border-radius: 12rpx; font-size: 26rpx;
}
.action-btn.warn { background: #FEF3C7; color: #D97706; }
.info-line { display: block; font-size: 26rpx; color: #666; margin-bottom: 8rpx; }
</style>