1. 特色方功能迭代(固定价格),固定价格目前统一使用药品id(-102),后期会考虑新增特色方的时候自动添加一个特色方药品
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled

2. 退款的时候可以退回分成
This commit is contained in:
李琦
2026-07-09 15:45:39 +08:00
parent 5620c39e1e
commit d93e465d3c
48 changed files with 556 additions and 160 deletions

View File

@@ -16,6 +16,12 @@ import { message } from 'ant-design-vue';
// eslint-disable-next-line n/no-extraneous-import
import { Base64 } from 'js-base64';
import {
ENCRYPT_FIELDS,
isSensitiveField,
maskSensitiveField,
TM_TEXT_SUFFIX,
} from '#/constants/sensitive-fields';
import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
@@ -23,33 +29,6 @@ import {downloadByData} from "#/util/tool";
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
const arr = new Set([
'account',
'accept_tel',
'doctor',
'express_mobile',
'id_card',
'idcard',
'mobile',
'password',
'patient_mobile',
'phone',
]);
// 敏感数据
const sensitiveData = new Set([
'account',
'accept_tel',
'doctor',
'express_mobile',
'id_card',
'idcard',
'mobile',
'password',
'patient_mobile',
'phone',
]);
function createRequestClient(baseURL: string) {
const client = new RequestClient({
baseURL,
@@ -189,61 +168,34 @@ export const baseRequestClient = new RequestClient({ baseURL: apiURL });
*/
function getRes(obj: any, isDecode = true) {
try {
if (obj == null || typeof obj !== 'object') {
return obj;
}
for (const key in obj) {
// 跳过脱敏展示字段,避免重复处理
if (key.endsWith(TM_TEXT_SUFFIX)) {
continue;
}
if (Array.isArray(obj[key])) {
obj[key] = getRes(obj[key]);
} else if (typeof obj[key] === 'object') {
obj[key] = getRes(obj[key]);
} else {
// 判断obj[key]是否在arr中
if (arr.has(key)) {
let aseFile = '';
aseFile = isDecode
? customBase64Decode(obj[key], key)
: customBase64Encode(obj[key]);
const isGarbled =
// eslint-disable-next-line no-control-regex
/[\u0000-\u0008\v\f\u000E-\u001F\u007F-\u00FF\u{E000}-\u{F8FF}]/u.test(
aseFile,
);
if (isGarbled) {
aseFile = obj[key];
}
if (isDecode && sensitiveData.has(key)) {
// 数据脱敏,自动匹配姓名、手机号、身份证
// switch (key) {
// case 'accept_name':
// case 'express_name':
// case 'express_region':
// case 'patient': {
// // 保留前两个字符,其余用星号代替
// aseFile = aseFile.slice(0, 1).padEnd(aseFile.length, '*');
// break;
// }
// case 'express_mobile':
// case 'mobile':
// case 'phone': {
// // 保留前三位和后四位,中间用星号代替
// aseFile =
// aseFile.slice(0, 3).padEnd(aseFile.length - 4, '*') +
// aseFile.slice(-4);
// break;
// }
// case 'id_card':
// case 'idcard': {
// // 保留前六位和后四位,中间用星号代替
// aseFile =
// aseFile.slice(0, 6).padEnd(aseFile.length - 4, '*') +
// aseFile.slice(-4);
// break;
// }
// default: {
// // 默认情况下,全部用星号代替
// break;
// }
// }
}
obj[key] = aseFile;
obj[key] = getRes(obj[key], isDecode);
} else if (obj[key] !== null && typeof obj[key] === 'object') {
obj[key] = getRes(obj[key], isDecode);
} else if (ENCRYPT_FIELDS.has(key)) {
let plainValue = isDecode
? customBase64Decode(obj[key], key)
: customBase64Encode(obj[key]);
const isGarbled =
// eslint-disable-next-line no-control-regex
/[\u0000-\u0008\v\f\u000E-\u001F\u007F-\u00FF\u{E000}-\u{F8FF}]/u.test(
plainValue,
);
if (isGarbled) {
plainValue = obj[key];
}
obj[key] = plainValue;
// 解密后写入脱敏展示字段,明文保留在原字段
if (isDecode && isSensitiveField(key)) {
obj[key + TM_TEXT_SUFFIX] = maskSensitiveField(key, plainValue);
}
}
}

View File

@@ -33,6 +33,7 @@ import {
} from 'ant-design-vue';
import FloatFab from '#/components/float-fab/FloatFab.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import {
DEFAULT_SUB_WINDOW_Z_INDEX,
getSubWindowPopupZIndex,
@@ -462,7 +463,10 @@ defineExpose({ loadList });
</div>
<div class="card-row">
<span class="label">收货人</span>
<span class="value">{{ item.patient_name }} {{ item.patient_mobile }}</span>
<span class="value">
{{ item.patient_name }}
<SensitiveText :record="item" field="patient_mobile" :show-eye="false" />
</span>
</div>
<div v-if="item.patient_address" class="card-row">
<span class="label">地址</span>

View File

@@ -5,6 +5,7 @@ import { useVModel } from '@vueuse/core';
import { Avatar, Empty, Input, Popover, Spin } from 'ant-design-vue';
import { getDoctorOptionApi } from '#/views/system/store/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
defineOptions({
@@ -156,7 +157,9 @@ watch(
<div class="doctor-card-name" :title="item.name">
{{ item.name || '-' }}
</div>
<div v-if="item.mobile" class="doctor-card-sub">{{ item.mobile }}</div>
<div v-if="item.mobile" class="doctor-card-sub">
<SensitiveText :record="item" field="mobile" :show-eye="false" />
</div>
<div v-if="item.depart_name" class="doctor-card-sub">
{{ item.depart_name }}
</div>
@@ -174,7 +177,13 @@ watch(
<div class="doctor-selected-info">
<div class="doctor-selected-name">{{ selectedOption.name }}</div>
<div class="doctor-selected-sub">
{{ selectedOption.mobile || `su_id: ${selectedOption.id}` }}
<SensitiveText
v-if="selectedOption.mobile"
:record="selectedOption"
field="mobile"
:show-eye="false"
/>
<span v-else>su_id: {{ selectedOption.id }}</span>
</div>
</div>
<span

View File

@@ -145,7 +145,9 @@ onMounted(() => {
{{ item.nick_name || '-' }}
</div>
<div class="promoter-card-sub">{{ item.code }}</div>
<div v-if="item.phone" class="promoter-card-phone">{{ item.phone }}</div>
<div v-if="item.phone" class="promoter-card-phone">
<SensitiveText :record="item" field="phone" :show-eye="false" />
</div>
<Tag class="promoter-card-role" :bordered="false">
{{ item.role_name || '业务员' }}
</Tag>

View File

@@ -5,6 +5,7 @@ import { useVModel } from '@vueuse/core';
import { Avatar, Empty, Input, Popover, Spin } from 'ant-design-vue';
import { getStoreOption } from '#/views/system/store/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
defineOptions({
@@ -133,7 +134,9 @@ onMounted(() => {
<div class="store-card-name" :title="item.name">
{{ item.name || '-' }}
</div>
<div v-if="item.mobile" class="store-card-sub">{{ item.mobile }}</div>
<div v-if="item.mobile" class="store-card-sub">
<SensitiveText :record="item" field="mobile" :show-eye="false" />
</div>
<div class="store-card-sub">ID: {{ item.id }}</div>
</button>
</div>
@@ -149,7 +152,13 @@ onMounted(() => {
<div class="store-selected-info">
<div class="store-selected-name">{{ selectedOption.name }}</div>
<div class="store-selected-sub">
{{ selectedOption.mobile || `ID: ${selectedOption.id}` }}
<SensitiveText
v-if="selectedOption.mobile"
:record="selectedOption"
field="mobile"
:show-eye="false"
/>
<span v-else>ID: {{ selectedOption.id }}</span>
</div>
</div>
<span

View File

@@ -6,6 +6,7 @@ import { useVbenModal } from '@vben/common-ui';
import { Avatar, Descriptions, Spin } from 'ant-design-vue';
import { getPromoterDetailApi } from '#/views/system/store-input/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
type PromoterDetail = {
@@ -62,7 +63,9 @@ async function loadDetail(id?: number, code?: string) {
</div>
<Descriptions bordered :column="1" size="small" class="promoter-detail__desc">
<Descriptions.Item label="推广码">{{ detail.code || '-' }}</Descriptions.Item>
<Descriptions.Item label="手机号">{{ detail.phone || '-' }}</Descriptions.Item>
<Descriptions.Item label="手机号">
<SensitiveText :record="detail" field="phone" />
</Descriptions.Item>
<Descriptions.Item label="录入诊所">
{{ detail.clinic_input_count ?? 0 }}
</Descriptions.Item>

View File

@@ -6,6 +6,7 @@ import { useVbenModal } from '@vben/common-ui';
import { Avatar } from 'ant-design-vue';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import PromoterDetailModal from './PromoterDetailModal.vue';
@@ -96,7 +97,7 @@ function onClear(e: Event) {
业务码{{ admin.code }}
</div>
<div v-else-if="admin.phone" class="promoter-info-card__sub">
{{ admin.phone }}
<SensitiveText :record="admin" field="phone" :show-eye="false" />
</div>
</div>
<span

View File

@@ -0,0 +1,158 @@
<script lang="ts" setup>
import type { SensitiveFieldSource } from '#/constants/sensitive-fields';
import { computed, ref } from 'vue';
import { useClipboard } from '@vueuse/core';
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import {
getSensitiveMaskText,
getSensitivePlainText,
} from '#/constants/sensitive-fields';
const props = withDefaults(
defineProps<{
/** 数据对象 */
record?: Record<string, unknown> | null;
/** 字段名 */
field: string;
/** 主字段为空时的备选来源 */
fallback?: SensitiveFieldSource;
/** 空值占位 */
emptyText?: string;
/** 是否显示眼睛切换 */
showEye?: boolean;
/** 点击文本是否复制明文 */
copyable?: boolean;
}>(),
{
record: null,
emptyText: '—',
showEye: true,
copyable: true,
},
);
const revealed = ref(false);
const { copy } = useClipboard({ legacy: true });
/** 明文值,复制与展示切换时使用 */
const plainText = computed(() =>
getSensitivePlainText(props.record, props.field, props.fallback),
);
/** 脱敏展示值 */
const maskText = computed(() =>
getSensitiveMaskText(props.record, props.field, props.fallback),
);
/** 当前界面展示文本 */
const displayText = computed(() => {
if (!plainText.value) {
return props.emptyText;
}
return revealed.value ? plainText.value : maskText.value;
});
/** 是否有有效值(非空占位) */
const hasValue = computed(() => plainText.value !== '');
/**
* 点击文本复制明文(脱敏前的原始值)
*/
async function handleCopyText() {
if (!props.copyable || !hasValue.value) {
return;
}
try {
await copy(plainText.value);
message.success('复制成功');
} catch {
message.error('复制失败');
}
}
/**
* 切换明文 / 脱敏展示
*/
function toggleReveal() {
if (!hasValue.value) {
return;
}
revealed.value = !revealed.value;
}
</script>
<template>
<span class="sensitive-text">
<span
class="sensitive-text__value"
:class="{ 'sensitive-text__value--copyable': copyable && hasValue }"
@click="handleCopyText"
>
{{ displayText }}
</span>
<EyeOutlined
v-if="showEye && hasValue && !revealed"
class="sensitive-text__eye"
@click.stop="toggleReveal"
/>
<EyeInvisibleOutlined
v-else-if="showEye && hasValue && revealed"
class="sensitive-text__eye"
@click.stop="toggleReveal"
/>
</span>
</template>
<style scoped>
.sensitive-text {
display: inline-flex;
align-items: center;
gap: 4px;
max-width: 100%;
}
.sensitive-text__value {
word-break: break-all;
}
.sensitive-text__value--copyable {
cursor: pointer;
}
.sensitive-text__value--copyable:hover {
color: var(--ant-color-primary, #1677ff);
}
.sensitive-text__eye {
flex-shrink: 0;
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 0.2s;
}
.sensitive-text__eye :deep(svg) {
fill: currentColor;
}
.sensitive-text__eye:hover {
color: var(--ant-color-primary, #1677ff);
}
/* 暗色模式:避免 fallback 黑色导致图标不可见 */
.dark .sensitive-text__eye {
color: rgba(255, 255, 255, 0.45);
}
.dark .sensitive-text__eye:hover {
color: var(--ant-color-primary, #4096ff);
}
.dark .sensitive-text__value--copyable:hover {
color: var(--ant-color-primary, #4096ff);
}
</style>

View File

@@ -0,0 +1,133 @@
/**
* 敏感字段集中定义:拦截器脱敏与 SensitiveText 组件共用
*/
/** 脱敏展示后缀,明文保留在原字段 */
export const TM_TEXT_SUFFIX = '_tm_text';
/** 需要加解密的字段(与 request 拦截器 arr 一致) */
export const ENCRYPT_FIELDS = new Set([
'account',
'accept_tel',
'doctor',
'express_mobile',
'id_card',
'idcard',
'mobile',
'password',
'patient_mobile',
'phone',
]);
/** 需要生成 _tm_text 并默认脱敏展示的字段encrypt_only */
export const MASK_DISPLAY_FIELDS = ENCRYPT_FIELDS;
const PHONE_FIELDS = new Set([
'mobile',
'phone',
'express_mobile',
'patient_mobile',
'accept_tel',
]);
const ID_CARD_FIELDS = new Set(['id_card', 'idcard']);
export interface SensitiveFieldSource {
record?: Record<string, unknown> | null;
field: string;
}
/**
* 判断是否为敏感字段
*/
export function isSensitiveField(field: string): boolean {
return MASK_DISPLAY_FIELDS.has(field);
}
/**
* 从 record 读取明文,支持 fallback 链
*/
export function getSensitivePlainText(
record: Record<string, unknown> | null | undefined,
field: string,
fallback?: SensitiveFieldSource,
): string {
const primary = readFieldValue(record, field);
if (primary !== '') {
return primary;
}
if (fallback?.record) {
return readFieldValue(fallback.record, fallback.field);
}
return '';
}
/**
* 读取脱敏文本:优先 _tm_text否则对明文现场脱敏
*/
export function getSensitiveMaskText(
record: Record<string, unknown> | null | undefined,
field: string,
fallback?: SensitiveFieldSource,
): string {
const tmKey = `${field}${TM_TEXT_SUFFIX}`;
const tmFromPrimary = readFieldValue(record, tmKey);
if (tmFromPrimary !== '') {
return tmFromPrimary;
}
if (fallback?.record) {
const tmFromFallback = readFieldValue(fallback.record, tmKey);
if (tmFromFallback !== '') {
return tmFromFallback;
}
}
const plain = getSensitivePlainText(record, field, fallback);
if (plain === '') {
return '';
}
return maskSensitiveField(field, plain);
}
/**
* 按字段类型生成脱敏文本
*/
export function maskSensitiveField(field: string, plainText: unknown): string {
if (plainText == null || plainText === '') {
return '';
}
const str = String(plainText);
if (field === 'password') {
return '*'.repeat(Math.max(str.length, 6));
}
if (PHONE_FIELDS.has(field)) {
if (str.length <= 7) {
return str;
}
return str.slice(0, 3) + '*'.repeat(str.length - 7) + str.slice(-4);
}
if (ID_CARD_FIELDS.has(field)) {
if (str.length <= 10) {
return str;
}
return str.slice(0, 6) + '*'.repeat(str.length - 10) + str.slice(-4);
}
// account / doctor 等通用兜底:保留前 2 + 后 2
if (str.length <= 4) {
return str;
}
return str.slice(0, 2) + '*'.repeat(str.length - 4) + str.slice(-2);
}
function readFieldValue(
record: Record<string, unknown> | null | undefined,
field: string,
): string {
if (!record) {
return '';
}
const value = record[field];
if (value == null || value === '') {
return '';
}
return String(value);
}

View File

@@ -8,6 +8,7 @@ import { Card, Col, Image, Row, Spin } from 'ant-design-vue';
import { getSelfMyInfoApi } from '#/views/business/clinic-salesperson/api';
import QrCodePreview from '#/views/business/store/settings/components/SalespersonQrCodePreview.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const [QrCodePreviewModal, QrCodePreviewApi] = useVbenModal({
connectedComponent: QrCodePreview,
});
@@ -37,7 +38,7 @@ function openQrCode(item: any) {
<template v-if="info">
<Card class="mb-4" title="基本信息">
<p>姓名{{ info.salesperson?.nick_name }}</p>
<p>手机{{ info.salesperson?.phone }}</p>
<p>手机<SensitiveText :record="info.salesperson" field="phone" /></p>
<p>所属诊所{{ info.store?.name }}</p>
<div v-if="info.qr_code" class="mt-3">
<p class="mb-2">推广二维码</p>

View File

@@ -18,6 +18,7 @@ import {
getPlatformSalespersonListApi,
openAdminAccountApi,
} from '#/views/business/store/settings/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import QrCodePreview from '#/views/business/store/settings/components/SalespersonQrCodePreview.vue';
import SalespersonCommissionDrawer from '#/views/system/store/components/SalespersonCommissionDrawer.vue';
import SalespersonCreateModal from '#/views/business/store/settings/components/SalespersonCreateModal.vue';
@@ -192,7 +193,7 @@ function tcmBaseLabel(type: number) {
</div>
<div class="mb-3 space-y-1 text-sm text-gray-600">
<div>{{ item.phone }}</div>
<div><SensitiveText :record="item" field="phone" /></div>
<div v-if="item.parent_nick_name" class="text-xs text-gray-500">
邀请人{{ item.parent_nick_name }}
</div>

View File

@@ -13,6 +13,7 @@ import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
import { formatPriceDiscountLabel, normalizeQuickOptions } from '#/utils/pricePercentAdjust';
import { expressDetailByOrderId, getOrderInfo } from '../api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
defineOptions({
name: 'DetailModal',
@@ -310,7 +311,11 @@ function prescriptionStatusColor() {
</span>
</Descriptions.Item>
<Descriptions.Item label="联系电话">
{{ data.address?.mobile || data.patient_mobile }}
<SensitiveText
:record="data.address"
field="mobile"
:fallback="{ record: data, field: 'patient_mobile' }"
/>
</Descriptions.Item>
<Descriptions.Item v-if="deliveryMethod === 0" label="配送地址">
{{

View File

@@ -18,6 +18,7 @@ import { Button, Image, message, Modal as AntdModal, Popconfirm, Popover, Space,
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import {
cancelOrderApi,
getOrderInfo,
@@ -576,7 +577,11 @@ const openOrderAmountVerify = () => {
}}
</div>
<div class="text-gray-500">
{{ row.address?.mobile || row.patient_mobile || '—' }}
<SensitiveText
:record="row.address"
field="mobile"
:fallback="{ record: row, field: 'patient_mobile' }"
/>
</div>
<div class="text-xs text-gray-500">{{ formatExpressAddress(row) }}</div>
</div>

View File

@@ -4,6 +4,7 @@ import { computed, ref } from 'vue';
import { Image, Modal, Table, Tag } from 'ant-design-vue';
import { getSalespersonTransferDetailApi } from '../api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const visible = ref(false);
const loading = ref(false);
@@ -92,7 +93,10 @@ defineExpose({ open });
<Tag :color="statusColor">{{ detail.status_text || '—' }}</Tag>
<Tag>{{ detail.transfer_mode_text || '—' }}</Tag>
</div>
<div>患者{{ detail.patient_name }} {{ detail.patient_mobile }}</div>
<div class="flex items-center gap-1 flex-wrap">
<span>患者{{ detail.patient_name }}</span>
<SensitiveText :record="detail" field="patient_mobile" :show-eye="false" />
</div>
<div>地址{{ detail.patient_address || '—' }}</div>
<div v-if="detail.remark">备注{{ detail.remark }}</div>
<div v-if="detail.status === 3 && detail.exception_message" class="text-red-500">

View File

@@ -6,6 +6,7 @@ import { Page } from '@vben/common-ui';
import { Button, Empty, Spin, Table, Tag, Tooltip } from 'ant-design-vue';
import DetailModal from './components/DetailModal.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { getSalespersonTransferListApi } from './api';
const loading = ref(true);
@@ -32,7 +33,11 @@ const columns = [
title: '患者',
key: 'patient',
width: 140,
customRender: ({ record }: any) => `${record.patient_name || ''} ${record.patient_mobile || ''}`,
customRender: ({ record }: any) =>
h('span', { class: 'inline-flex items-center gap-1 flex-wrap' }, [
record.patient_name || '',
h(SensitiveText, { record, field: 'patient_mobile', showEye: false }),
]),
},
{
title: '方式',

View File

@@ -14,6 +14,7 @@ import SalespersonCommissionDrawer from '#/views/business/store/settings/compone
import SalespersonCreateModal from '#/views/business/store/settings/components/SalespersonCreateModal.vue';
import SalespersonUserBindDrawer from '#/views/business/store/settings/components/SalespersonUserBindDrawer.vue';
import SalespersonInvitedDrawer from '#/views/business/store/settings/components/SalespersonInvitedDrawer.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const listData = ref<{ total: number; items: any[] }>({
total: 0,
@@ -148,7 +149,7 @@ function tcmBaseLabel(type: number) {
</div>
<div class="mb-4 space-y-1.5 text-sm text-gray-600">
<div>{{ item.phone }}</div>
<div><SensitiveText :record="item" field="phone" /></div>
<div v-if="item.parent_nick_name" class="text-xs text-gray-500">
邀请人{{ item.parent_nick_name }}
</div>

View File

@@ -1,8 +1,9 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { h, ref, watch } from 'vue';
import { Drawer, Table } from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { invitedSalespersonListApi } from '#/views/business/store/settings/api';
const open = ref(false);
@@ -13,7 +14,13 @@ const list = ref<any[]>([]);
const columns = [
{ title: '名称', dataIndex: 'nick_name', key: 'nick_name', width: 120 },
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 120 },
{
title: '手机号',
key: 'phone',
width: 120,
customRender: ({ record }: any) =>
h(SensitiveText, { record, field: 'phone', showEye: false }),
},
{ title: '邀请分成', dataIndex: 'inviter_split', key: 'inviter_split', width: 90 },
{ title: '待结算', dataIndex: 'invite_pending_amount', key: 'pending', width: 100 },
{ title: '已结算', dataIndex: 'invite_settled_amount', key: 'settled', width: 100 },

View File

@@ -4,6 +4,7 @@ import { computed, ref, watch } from 'vue';
import { Avatar, Drawer, Empty, Input, Spin } from 'ant-design-vue';
import { salespersonOptionsApi } from '#/views/business/store/settings/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const props = withDefaults(
defineProps<{
@@ -105,7 +106,7 @@ watch(
<div class="min-w-0 flex-1">
<div class="truncate font-medium">{{ displayName }}</div>
<div v-if="selectedItem?.phone" class="text-xs text-gray-500">
{{ selectedItem.phone }}
<SensitiveText :record="selectedItem" field="phone" :show-eye="false" />
</div>
</div>
<a class="text-xs text-gray-400" @click.stop="clear">清空</a>
@@ -131,7 +132,9 @@ watch(
<Avatar v-else :size="40">{{ (item.nick_name || '?').charAt(0) }}</Avatar>
<div>
<div class="font-medium">{{ item.nick_name }}</div>
<div class="text-xs text-gray-500">{{ item.phone }}</div>
<div class="text-xs text-gray-500">
<SensitiveText :record="item" field="phone" :show-eye="false" />
</div>
</div>
</div>
</Spin>

View File

@@ -31,6 +31,7 @@ import {
updateNavSortApi,
updateClinicTypeApi,
} from '#/views/business/store/settings/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import UploadModal from './components/uploadModal.vue';
import SalespersonCard from "#/views/business/store/settings/components/SalespersonCard.vue";
@@ -391,7 +392,7 @@ const handleSwitchClinicType = () => {
{{ data.contact }}
</p>
<p class="flex items-center gap-2">
{{ data.mobile }}
<SensitiveText :record="data" field="mobile" />
</p>
<p class="flex items-center gap-2">
入驻时间{{ formatDate(data.created_at) }}
@@ -571,7 +572,7 @@ const handleSwitchClinicType = () => {
</p>
<p class="text-sm text-gray-500">
手机号:
<span>{{ doctor.doctor.mobile }}</span>
<SensitiveText :record="doctor.doctor" field="mobile" />
</p>
<p class="text-sm text-gray-500">
专业:

View File

@@ -12,6 +12,24 @@ export const formOptions: VbenFormProps = {
fieldName: 'name',
label: '药名称',
},
{
component: 'VbenSelect',
componentProps: {
placeholder: '选择状态',
options: [
{
label: '下架',
value: 1,
},
{
label: '上架',
value: 2,
},
],
},
fieldName: 'status',
label: '上架状态',
},
],
showCollapseButton: true,
submitButtonOptions: {

View File

@@ -4,6 +4,7 @@ import { ref, watch } from 'vue';
import { Button, Drawer, Empty, List, message, Tag } from 'ant-design-vue';
import { getSalespersonTransferByRegisterApi } from '#/views/business/salesperson-transfer-prescription/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { parseSalespersonTransferDrugList } from '#/views/doctor/doctor-reception/utils/parseSalespersonTransferDrugList';
const props = defineProps<{
@@ -77,9 +78,13 @@ function close() {
<template #renderItem="{ item }">
<List.Item>
<List.Item.Meta
:title="`${item.patient_name || ''} ${item.patient_mobile || ''}`"
:description="`推广员:${item.salesperson_name || '-'} · ${item.transfer_time_text || ''}`"
/>
>
<template #title>
<span>{{ item.patient_name || '' }} </span>
<SensitiveText :record="item" field="patient_mobile" :show-eye="false" />
</template>
</List.Item.Meta>
<template #actions>
<Tag v-if="matchTypeLabel(item.match_type)" :color="item.match_type === 'mobile' ? 'success' : 'processing'">
{{ matchTypeLabel(item.match_type) }}

View File

@@ -66,6 +66,7 @@ import CommonPrescriptionModal from './components/CommonPrescriptionModal.vue';
import ConfirmModal from './components/ConfirmModal.vue';
// 信息提示模态框组件
import InfoModal from '#/components/modal/InfoModal.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
// 药品搜索选择组件
import { DrugSearchSelect } from '#/components/drug-search-select';
// 保存常用方API
@@ -2459,7 +2460,9 @@ watch(
}}
</Tag>
</h3>
<p class="phone">{{ patient.user_patient.mobile }}</p>
<p class="phone">
<SensitiveText :record="patient.user_patient" field="mobile" :show-eye="false" />
</p>
<p class="text-sm text-gray-500">
推广员{{ patient.salesperson?.nick_name || '无' }}
</p>

View File

@@ -25,6 +25,7 @@ import {
import AvatarUpload from '#/components/form/components/avatar.vue';
import DoctorQrCodePreview from '#/components/modal/DoctorQrCodePreview.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import OrderDetail from '#/views/business/order/product-order/components/detail.vue';
import { openPcWindowsApi, updateDoctor } from '../api';
@@ -526,7 +527,9 @@ const [Modal, modalApi] = useVbenModal({
</div>
</Form>
<Descriptions v-else bordered :column="2" size="middle">
<Descriptions.Item label="登录手机号">{{ serviceUserForm.mobile || '-' }}</Descriptions.Item>
<Descriptions.Item label="登录手机号">
<SensitiveText :record="serviceUserForm" field="mobile" />
</Descriptions.Item>
<Descriptions.Item label="昵称">{{ serviceUserForm.nickname || '-' }}</Descriptions.Item>
<Descriptions.Item label="账号状态">
<Tag :color="cardData?.service_user?.status === 2 ? 'success' : 'default'">
@@ -631,7 +634,7 @@ const [Modal, modalApi] = useVbenModal({
</p>
<p class="flex items-center">
<svg class="w-4 h-4 mr-2 text-gray-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" /></svg>
<span>{{ item.store?.contact }} ({{ item.store?.mobile }})</span>
<span>{{ item.store?.contact }} (<SensitiveText :record="item.store" field="mobile" />)</span>
</p>
</div>
@@ -656,7 +659,7 @@ const [Modal, modalApi] = useVbenModal({
<h3 class="text-xl font-bold text-gray-800 dark:text-slate-100 mb-1">{{ cardData.admin.nick_name }}</h3>
<p class="text-gray-500 dark:text-slate-400 mb-4 flex items-center gap-2">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
{{ cardData.admin.phone }}
<SensitiveText :record="cardData.admin" field="phone" />
</p>
<Tag color="processing">PC 端权限已开通</Tag>
</div>

View File

@@ -31,7 +31,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '名称', slots: { default: 'name' } },
{ field: 'mobile', align: 'left', title: '手机号' },
{ field: 'mobile', align: 'left', title: '手机号', slots: { default: 'mobile' } },
{
field: 'avatar',
align: 'left',

View File

@@ -9,6 +9,7 @@ import { Button, Image, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { auditApi, openPcWindowsApi, refuseApi } from './api';
import BindStore from './components/BindStoreModal.vue';
@@ -160,6 +161,9 @@ const refuse = (id: number) => {
{{ row.name }}
</Button>
</template>
<template #mobile="{ row }">
<SensitiveText :record="row" field="mobile" />
</template>
<template #stores="{ row }">
<p v-for="item in row.stores" :key="item.store_id">
{{ item?.store?.name }}

View File

@@ -7,6 +7,7 @@ import { sendMessage } from '#/views/business/chat/utils/request';
import { useUserStore } from '#/views/business/chat/stores/user';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionCard from '#/views/doctor/online-consultation/components/TransferPrescriptionCard.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { usePrescriptionStore } from '#/store/prescription';
const userStore = useUserStore();
@@ -206,7 +207,12 @@ watch(
{{ getSexText(patientDetail.user_patient?.sex) }}
</Descriptions.Item>
<Descriptions.Item label="手机号">
{{ patientDetail.user_patient?.mobile || patientDetail.user?.mobile || '未填写' }}
<SensitiveText
:record="patientDetail.user_patient"
field="mobile"
:fallback="{ record: patientDetail.user, field: 'mobile' }"
empty-text="未填写"
/>
</Descriptions.Item>
<Descriptions.Item v-if="patientDetail.register_order?.order_no" label="订单号">
{{ patientDetail.register_order.order_no }}

View File

@@ -3,6 +3,7 @@ import { ref, watch, onMounted } from 'vue';
import { RadioButton, RadioGroup, Button, message } from 'ant-design-vue';
import { ReloadOutlined } from '@ant-design/icons-vue';
import { getPatientList } from '../api/index.ts';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { useChatStore } from '#/views/business/chat/stores/chat';
const chatStore = useChatStore();
@@ -161,7 +162,14 @@ defineExpose({
<h3 class="patient-name">
{{ patient.user_patient?.name || patient.user?.nick_name || '未知患者' }}
</h3>
<p class="patient-phone">{{ patient.user_patient?.mobile || patient.user?.mobile || '' }}</p>
<p class="patient-phone">
<SensitiveText
:record="patient.user_patient"
field="mobile"
:fallback="{ record: patient.user, field: 'mobile' }"
:show-eye="false"
/>
</p>
<p v-if="patient.order_no" class="order-no">订单号{{ patient.order_no }}</p>
<div class="patient-meta">
<span :class="['status', getStatusClass(patient.status)]">

View File

@@ -7,6 +7,7 @@ import { sendMessage } from '#/views/business/chat/utils/request';
import { useUserStore } from '#/views/business/chat/stores/user';
import { getTransferPrescriptionByRegisterApi } from '#/views/business/chat/api/transferPrescription';
import TransferPrescriptionCard from './TransferPrescriptionCard.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { usePrescriptionStore } from '#/store/prescription';
const userStore = useUserStore();
@@ -206,7 +207,12 @@ watch(
{{ getSexText(patientDetail.user_patient?.sex) }}
</Descriptions.Item>
<Descriptions.Item label="手机号">
{{ patientDetail.user_patient?.mobile || patientDetail.user?.mobile || '未填写' }}
<SensitiveText
:record="patientDetail.user_patient"
field="mobile"
:fallback="{ record: patientDetail.user, field: 'mobile' }"
empty-text="未填写"
/>
</Descriptions.Item>
<Descriptions.Item v-if="patientDetail.register_order?.order_no" label="订单号">
{{ patientDetail.register_order.order_no }}

View File

@@ -3,6 +3,7 @@ import { ref, watch, onMounted } from 'vue';
import { RadioButton, RadioGroup, Button, message } from 'ant-design-vue';
import { ReloadOutlined } from '@ant-design/icons-vue';
import { getPatientList } from '../api/index.ts';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { useChatStore } from '#/views/business/chat/stores/chat';
const chatStore = useChatStore();
@@ -170,7 +171,14 @@ defineExpose({
<h3 class="patient-name">
{{ patient.user_patient?.name || patient.user?.nick_name || '未知患者' }}
</h3>
<p class="patient-phone">{{ patient.user_patient?.mobile || patient.user?.mobile || '' }}</p>
<p class="patient-phone">
<SensitiveText
:record="patient.user_patient"
field="mobile"
:fallback="{ record: patient.user, field: 'mobile' }"
:show-eye="false"
/>
</p>
<p v-if="patient.order_no" class="order-no">订单号{{ patient.order_no }}</p>
<div class="patient-meta">
<div class="meta-left">

View File

@@ -32,6 +32,7 @@ import {
import { UserOutlined } from '@ant-design/icons-vue';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import {
getPatientDetailApi,
@@ -442,10 +443,18 @@ function getSexText(sex: number): string {
{{ patientDetail.age }}
</Descriptions.Item>
<Descriptions.Item label="手机号">
{{ patientDetail.patient?.mobile || '未填写' }}
<SensitiveText
:record="patientDetail.patient"
field="mobile"
empty-text="未填写"
/>
</Descriptions.Item>
<Descriptions.Item label="身份证" :span="2">
{{ patientDetail.patient?.id_card || '未填写' }}
<SensitiveText
:record="patientDetail.patient"
field="id_card"
empty-text="未填写"
/>
</Descriptions.Item>
</Descriptions>
</Card>

View File

@@ -44,6 +44,7 @@ import {
type PageResponse,
} from './api';
import PatientDetailModal from './components/PatientDetailModal.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import SendMessageModal from './components/SendMessageModal.vue';
// ==================== 响应式数据 ====================
@@ -341,7 +342,7 @@ onMounted(() => {
>
</div>
<p class="patient-mobile" v-if="patient.mobile">
{{ patient.mobile }}
<SensitiveText :record="patient" field="mobile" :show-eye="false" />
</p>
</div>

View File

@@ -6,6 +6,8 @@ import { useVbenModal } from '@vben/common-ui';
import { getPharmacistInfo } from '../api';
import { Image } from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const data = ref();
const loading = ref(false);
const error = ref('');
@@ -20,18 +22,6 @@ const formatDate = (dateStr: string) => {
});
};
// 身份证号脱敏处理
const formatIdCard = (idCard: string) => {
if (!idCard) return '';
try {
// 假设需要Base64解码根据实际情况调整
const decoded = atob(idCard);
return `${decoded.slice(0, 6)}********${decoded.slice(14)}`;
} catch {
return idCard;
}
};
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
@@ -140,12 +130,12 @@ const qualificationImages = computed(() => {
</div>
<div class="flex items-center">
<span class="w-24 text-sm font-medium text-gray-600">手机号:</span>
<span class="text-gray-700">{{ data.mobile }}</span>
<SensitiveText :record="data" field="mobile" />
</div>
<div class="flex items-center">
<span class="w-24 text-sm font-medium text-gray-600">身份证号:</span>
<SensitiveText :record="data" field="idcard" empty-text="未填写" />
</div>
<!-- <div class="flex items-center">-->
<!-- <span class="w-24 text-sm font-medium text-gray-600">身份证号:</span>-->
<!-- <span class="text-gray-700">{{ data.idcard }}</span>-->
<!-- </div>-->
<div class="flex items-center">
<span class="w-24 text-sm font-medium text-gray-600">门店ID:</span>
<span class="text-gray-700">{{ data.store_id }}</span>

View File

@@ -31,7 +31,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '名称' },
{ field: 'mobile', align: 'left', title: '手机号' },
{ field: 'mobile', align: 'left', title: '手机号', slots: { default: 'mobile' } },
{
field: 'avatar',
align: 'left',

View File

@@ -9,6 +9,7 @@ import { Button, Image, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { auditApi, openPcWindowsApi, refuseApi } from './api';
import BindStore from './components/BindStoreModal.vue';
@@ -140,6 +141,9 @@ const refuse = (id: number) => {
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #mobile="{ row }">
<SensitiveText :record="row" field="mobile" />
</template>
<template #stores="{ row }">
<p v-for="item in row.bind_stores" :key="item.id">
{{ item?.name }}

View File

@@ -45,6 +45,7 @@ import {
saveServicePriceApi,
} from '#/views/doctor/settings/api';
import type { CommonPrescriptionResponse } from '#/views/doctor/settings/api';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
const userInfoStore = useUserStore();
@@ -375,7 +376,7 @@ const handleViewSignature = () => {
</h1>
<p class="flex items-center gap-2">
<PhoneIcon class="h-5 w-5" />
{{ data.mobile }}
<SensitiveText :record="data" field="mobile" />
</p>
<p class="flex items-center gap-2">
<CalendarIcon class="h-5 w-5" />

View File

@@ -10,6 +10,7 @@ import { Button, Image, message, Avatar } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import {
createAdmin,
deleteAdmin,
@@ -293,13 +294,16 @@ const copyToClipboard = async (text: string) => {
{{ row.doctor_profile?.name || `su_id: ${row.doctor_id}` }}
</div>
<div v-if="row.doctor_profile?.mobile" class="text-xs text-gray-400">
{{ row.doctor_profile.mobile }}
<SensitiveText :record="row.doctor_profile" field="mobile" />
</div>
</div>
</div>
</Button>
<span v-else>-</span>
</template>
<template #phone="{ row }">
<SensitiveText :record="row" field="phone" />
</template>
<template #login_account="{ row }">
<span v-if="row.login_account">
<Button

View File

@@ -88,7 +88,7 @@ function buildColumns(formType: AdminFormType) {
}
cols.push(
{ field: 'phone', title: '手机号码' },
{ field: 'phone', title: '手机号码', slots: { default: 'phone' } },
{
field: 'login_account',
align: 'left',

View File

@@ -44,7 +44,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ field: 'code', title: '业务推广码' },
{ field: 'platform.name', title: '所属平台' },
{ field: 'supplier.name', title: '所属供应商' },
{ field: 'phone', title: '手机号码' },
{ field: 'phone', title: '手机号码', slots: { default: 'phone' } },
{
field: 'login_account',
align: 'left',

View File

@@ -5,6 +5,8 @@ import { useVbenModal } from '@vben/common-ui';
import { Avatar, Descriptions, DescriptionsItem, Image, Spin, message } from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { getDoctorInputDetail } from '../api';
import {
inputUserDisplayName,
@@ -72,8 +74,12 @@ const [Modal, modalApi] = useVbenModal({
<Descriptions :column="2" bordered>
<DescriptionsItem label="ID">{{ detailData.id }}</DescriptionsItem>
<DescriptionsItem label="医生姓名">{{ detailData.name }}</DescriptionsItem>
<DescriptionsItem label="手机号">{{ detailData.mobile }}</DescriptionsItem>
<DescriptionsItem label="身份证">{{ detailData.idcard }}</DescriptionsItem>
<DescriptionsItem label="手机号">
<SensitiveText :record="detailData" field="mobile" />
</DescriptionsItem>
<DescriptionsItem label="身份证">
<SensitiveText :record="detailData" field="idcard" />
</DescriptionsItem>
<DescriptionsItem label="身份">{{ getTypeText(detailData.type) }}</DescriptionsItem>
<DescriptionsItem label="签名类型">{{ getSignTypeText(detailData.sign_type) }}</DescriptionsItem>
<DescriptionsItem label="科室">{{ detailData.depart?.name || '-' }}</DescriptionsItem>

View File

@@ -41,7 +41,7 @@ export const inputGridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '医生姓名' },
{ field: 'mobile', title: '手机号', width: 140 },
{ field: 'mobile', title: '手机号', width: 140, slots: { default: 'mobile' } },
{
field: 'type',
align: 'left',
@@ -111,7 +111,7 @@ export const auditGridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '医生姓名' },
{ field: 'mobile', title: '手机号', width: 140 },
{ field: 'mobile', title: '手机号', width: 140, slots: { default: 'mobile' } },
{
field: 'type',
align: 'left',

View File

@@ -9,6 +9,7 @@ import { Avatar, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import DetailModal from './components/DetailModal.vue';
import FormModalDemo from './components/modal.vue';
@@ -119,6 +120,9 @@ const getStatusText = (status: number) => {
</div>
<span v-else>-</span>
</template>
<template #mobile="{ row }">
<SensitiveText :record="row" field="mobile" />
</template>
<template #action="{ row }">
<TableAction
:actions="[

View File

@@ -14,6 +14,7 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { Icon } from '#/components/icon';
import QrCodePreview from '#/components/modal/QrCodePreview.vue';
import PromoterInfoCard from '#/components/promoter/PromoterInfoCard.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { TableAction } from '#/components/table-action';
// 导入药店管理相关的API直接使用store的API
@@ -478,8 +479,7 @@ const batchSyncDrugPrice = () => {
<Button type="link" @click="copyText(row.contact)">复制</Button>
</div>
<div>
号码{{ row.mobile }}
<Button type="link" @click="copyText(row.mobile)">复制</Button>
号码<SensitiveText :record="row" field="mobile" />
</div>
</template>
<template #qr_code="{ row }">

View File

@@ -12,6 +12,8 @@ import {
message,
} from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { getStoreInputDetail } from '../api';
import DoctorInputCardGrid from './DoctorInputCardGrid.vue';
import { getLinkedDoctors } from '../utils/doctorInputDisplay';
@@ -86,7 +88,9 @@ const [Modal, modalApi] = useVbenModal({
}}
</DescriptionsItem>
<DescriptionsItem label="联系人">{{ detailData.contact }}</DescriptionsItem>
<DescriptionsItem label="联系电话">{{ detailData.mobile }}</DescriptionsItem>
<DescriptionsItem label="联系电话">
<SensitiveText :record="detailData" field="mobile" />
</DescriptionsItem>
<DescriptionsItem label="省市区">
{{ detailData.province?.name }} {{ detailData.city?.name }}
</DescriptionsItem>

View File

@@ -9,6 +9,8 @@ import {
Popover,
} from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { resolveAvatarUrl } from '../utils/inputUser';
import {
doctorCertImages,
@@ -98,8 +100,12 @@ function cardClass(doc: Record<string, any>) {
<Descriptions :column="1" size="small" class="max-w-[480px]">
<DescriptionsItem label="ID">{{ doc.id }}</DescriptionsItem>
<DescriptionsItem label="姓名">{{ doc.name }}</DescriptionsItem>
<DescriptionsItem label="手机号">{{ doc.mobile || '-' }}</DescriptionsItem>
<DescriptionsItem label="身份证">{{ doc.idcard || '-' }}</DescriptionsItem>
<DescriptionsItem label="手机号">
<SensitiveText :record="doc" field="mobile" />
</DescriptionsItem>
<DescriptionsItem label="身份证">
<SensitiveText :record="doc" field="idcard" />
</DescriptionsItem>
<DescriptionsItem label="身份">{{ getDoctorTypeText(doc.type) }}</DescriptionsItem>
<DescriptionsItem label="签名类型">
{{ getSignTypeText(doc.sign_type) }}

View File

@@ -57,7 +57,7 @@ export const inputGridOptions: VxeGridProps<RowType> = {
slots: { default: 'status' },
},
{ field: 'contact', title: '联系人' },
{ field: 'mobile', title: '联系电话' },
{ field: 'mobile', title: '联系电话', slots: { default: 'mobile' } },
{ field: 'position', title: '详细地址' },
{ field: 'created_at', title: '录入时间' },
{
@@ -159,7 +159,7 @@ export const auditGridOptions: VxeGridProps<RowType> = {
},
{ field: 'audit_time', title: '审核时间', slots: { default: 'audit_time' } },
{ field: 'contact', title: '联系人' },
{ field: 'mobile', title: '联系电话' },
{ field: 'mobile', title: '联系电话', slots: { default: 'mobile' } },
{ field: 'position', title: '详细地址' },
{ field: 'created_at', title: '录入时间' },
{

View File

@@ -10,6 +10,7 @@ import { Avatar, Button, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import PromoterInfoCard from '#/components/promoter/PromoterInfoCard.vue';
import {
@@ -158,6 +159,9 @@ const getStatusText = (status: number) => {
<template #promoter_admin="{ row }">
<PromoterInfoCard :admin="row.promoter_admin" size="compact" />
</template>
<template #mobile="{ row }">
<SensitiveText :record="row" field="mobile" />
</template>
<template #action="{ row }">
<TableAction
:actions="[

View File

@@ -1,6 +1,8 @@
<script lang="ts" setup>
import { Button } from 'ant-design-vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
defineProps<{
row: Record<string, any>;
variant: 'clinic' | 'pharmacy';
@@ -52,16 +54,12 @@ function getClinicTypeText(clinicType: number) {
</div>
<div class="info-item">
<span class="info-item__label">管理员</span>
<template v-if="row.store_admin?.phone">
<Button
type="link"
size="small"
class="info-item__action"
@click="onCopy(row.store_admin.phone)"
>
{{ row.store_admin.phone }}
</Button>
</template>
<SensitiveText
v-if="row.store_admin?.phone"
:record="row.store_admin"
field="phone"
empty-text="-"
/>
<span v-else class="info-item__value">-</span>
</div>
</template>

View File

@@ -14,6 +14,7 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { Icon } from '#/components/icon';
import QrCodePreview from '#/components/modal/QrCodePreview.vue';
import PromoterInfoCard from '#/components/promoter/PromoterInfoCard.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { TableAction } from '#/components/table-action';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
@@ -510,8 +511,7 @@ const handleSwitchClinicType = (row: any) => {
<Button type="link" @click="copyText(row.contact)">复制</Button>
</div>
<div>
号码{{ row.mobile }}
<Button type="link" @click="copyText(row.mobile)">复制</Button>
号码<SensitiveText :record="row" field="mobile" />
</div>
</template>
<template #qr_code="{ row }">