add telephone link

This commit is contained in:
Timi
2026-08-03 01:27:46 +08:00
parent 2f9442e4a2
commit cd1b45eb24
13 changed files with 128 additions and 36 deletions
+2 -1
View File
@@ -12,7 +12,7 @@
</div> </div>
<span class="gray" v-text="customerCode" /> <span class="gray" v-text="customerCode" />
</div> </div>
<span class="telephone gray" v-text="customerTelephone" /> <telephone-link class="telephone gray" :value="customerTelephone" />
</div> </div>
<t-image-viewer <t-image-viewer
v-model:visible="photoViewerVisible" v-model:visible="photoViewerVisible"
@@ -105,6 +105,7 @@ function openPhotoViewer(): void {
} }
.telephone { .telephone {
display: inline-block;
font-size: .875rem; font-size: .875rem;
} }
} }
+3 -1
View File
@@ -16,7 +16,6 @@
<t-cell <t-cell
v-for="(item, index) in customerOptions" v-for="(item, index) in customerOptions"
:key="item.id || item.code || index" :key="item.id || item.code || index"
:description="item.telephone || '未填写电话'"
:note="item.code" :note="item.code"
hover hover
@click="selectCustomer(item)" @click="selectCustomer(item)"
@@ -27,6 +26,9 @@
<gender-icon :value="item.gender" /> <gender-icon :value="item.gender" />
</div> </div>
</template> </template>
<template #description>
<telephone-link :value="item.telephone" placeholder="未填写电话" />
</template>
<template #leftIcon> <template #leftIcon>
<t-image v-if="getCustomerPhotoURL(item)" :src="getCustomerPhotoURL(item)" :alt="item.name" fit="cover" class="avatar" /> <t-image v-if="getCustomerPhotoURL(item)" :src="getCustomerPhotoURL(item)" :alt="item.name" fit="cover" class="avatar" />
<div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" /> <div v-else class="avatar fallback" v-text="(item.name || item.code || '客').slice(0, 1)" />
+44
View File
@@ -0,0 +1,44 @@
<template>
<component
:is="href ? 'a' : 'span'"
v-if="displayText"
:href="href || undefined"
class="telephone-link"
:class="{ clickable: !!href }"
@click.stop
>
<span v-text="displayText" />
</component>
</template>
<script setup lang="ts">
import { buildTelephoneURL, canDialTelephone } from "@/utils/telephone";
defineOptions({
name: "TelephoneLink"
});
const props = withDefaults(defineProps<{
placeholder?: string;
value?: string;
}>(), {
placeholder: ""
});
const textValue = computed(() => props.value?.trim() || "");
const displayText = computed(() => textValue.value || props.placeholder);
const href = computed(() => canDialTelephone(textValue.value) ? buildTelephoneURL(textValue.value) : "");
</script>
<style scoped lang="less">
.telephone-link {
color: inherit;
word-break: break-all;
text-decoration: none;
&.clickable {
text-decoration: underline;
text-underline-offset: .16rem;
}
}
</style>
-4
View File
@@ -13,10 +13,6 @@
@decode="handleDecodedText" @decode="handleDecodedText"
@status-change="handleScanStatusChange" @status-change="handleScanStatusChange"
/> />
<div class="code-box">
<span class="required-label">扫出编码</span>
<strong v-text="scanCode || '未识别'" />
</div>
<customer-profile-card <customer-profile-card
v-if="matchedCustomer" v-if="matchedCustomer"
:customer="matchedCustomer" :customer="matchedCustomer"
+6 -2
View File
@@ -27,10 +27,12 @@
<t-cell-group v-if="inviter" :title="`被 ${inviter.name} 邀请`" class="invite" theme="card"> <t-cell-group v-if="inviter" :title="`被 ${inviter.name} 邀请`" class="invite" theme="card">
<t-cell <t-cell
:title="inviter.name" :title="inviter.name"
:description="inviter.telephone || '未填写电话'"
arrow arrow
@click="goCustomerDetail(inviter)" @click="goCustomerDetail(inviter)"
> >
<template #description>
<telephone-link :value="inviter.telephone" placeholder="未填写电话" />
</template>
<template #leftIcon> <template #leftIcon>
<t-image <t-image
v-if="getCustomerPhotoURL(inviter)" v-if="getCustomerPhotoURL(inviter)"
@@ -67,7 +69,6 @@
:key="item.id || item.code" :key="item.id || item.code"
> >
<t-cell <t-cell
:description="item.telephone || '未填写电话'"
arrow arrow
hover hover
@click="goCustomerDetail(item)" @click="goCustomerDetail(item)"
@@ -77,6 +78,9 @@
<span v-text="item.name || item.code" /> <span v-text="item.name || item.code" />
</div> </div>
</template> </template>
<template #description>
<telephone-link :value="item.telephone" placeholder="未填写电话" />
</template>
<template #leftIcon> <template #leftIcon>
<t-image <t-image
v-if="getCustomerPhotoURL(item)" v-if="getCustomerPhotoURL(item)"
+1 -1
View File
@@ -60,7 +60,7 @@
</t-radio-group> </t-radio-group>
</t-form-item> </t-form-item>
<t-form-item label="手机号"> <t-form-item label="手机号">
<t-input v-model.trim="formModel.telephone" placeholder="手机号" borderless /> <t-input v-model.trim="formModel.telephone" type="tel" placeholder="手机号" borderless />
</t-form-item> </t-form-item>
<t-form-item label="介绍人"> <t-form-item label="介绍人">
<div class="referrer"> <div class="referrer">
+1 -1
View File
@@ -34,7 +34,7 @@
<div class="gray"> <div class="gray">
<template v-if="item.telephone"> <template v-if="item.telephone">
<t-icon name="call" /> <t-icon name="call" />
<span class="word-space" v-text="item.telephone" /> <telephone-link class="word-space" :value="item.telephone" />
</template> </template>
</div> </div>
<t-tag <t-tag
+16 -11
View File
@@ -339,6 +339,9 @@ function buildEventStructureOption(): EChartsOption {
function buildGenderStatOption(): EChartsOption { function buildGenderStatOption(): EChartsOption {
return { return {
...buildBaseOption(), ...buildBaseOption(),
tooltip: {
trigger: "item"
},
xAxis: { xAxis: {
type: "category", type: "category",
data: ["男", "女", "未知"], data: ["男", "女", "未知"],
@@ -349,30 +352,32 @@ function buildGenderStatOption(): EChartsOption {
color: "#52607a" color: "#52607a"
} }
}, },
series: [{ series: [
name: "客户数量",
type: "bar",
data: [
{ {
value: getGenderCount("MALE"), name: "男",
type: "bar",
itemStyle: { itemStyle: {
color: genderColorMap.MALE color: genderColorMap.MALE
} },
data: [getGenderCount("MALE"), 0, 0]
}, },
{ {
value: getGenderCount("FEMALE"), name: "女",
type: "bar",
itemStyle: { itemStyle: {
color: genderColorMap.FEMALE color: genderColorMap.FEMALE
} },
data: [0, getGenderCount("FEMALE"), 0]
}, },
{ {
value: getGenderCount("UNKNOWN"), name: "未知",
type: "bar",
itemStyle: { itemStyle: {
color: genderColorMap.UNKNOWN color: genderColorMap.UNKNOWN
} },
data: [0, 0, getGenderCount("UNKNOWN")]
} }
] ]
}]
}; };
} }
+9 -1
View File
@@ -27,7 +27,10 @@
</t-radio-group> </t-radio-group>
</t-form-item> </t-form-item>
<t-form-item label="手机号"> <t-form-item label="手机号">
<t-input :model-value="formModel.mobile" readonly borderless /> <div class="mobile-value">
<telephone-link v-if="formModel.mobile" :value="formModel.mobile" />
<span v-else v-text="'未填写'" />
</div>
</t-form-item> </t-form-item>
</t-form> </t-form>
<input <input
@@ -259,6 +262,11 @@ onMounted(() => {
display: none; display: none;
} }
.mobile-value {
color: var(--td-text-color-primary);
line-height: 1.5rem;
}
.save, .save,
.logout { .logout {
left: 1rem; left: 1rem;
+5 -1
View File
@@ -17,7 +17,11 @@
</t-cell-group> </t-cell-group>
<t-cell-group v-if="currentUser" theme="card"> <t-cell-group v-if="currentUser" theme="card">
<t-cell title="手机号" :description="currentUser.mobile || '未填写'" /> <t-cell title="手机号">
<template #description>
<telephone-link :value="currentUser.mobile" placeholder="未填写" />
</template>
</t-cell>
<t-cell title="上次登录时间" :description="currentUser.lastLoginAt || '暂无记录'" /> <t-cell title="上次登录时间" :description="currentUser.lastLoginAt || '暂无记录'" />
</t-cell-group> </t-cell-group>
+1 -1
View File
@@ -5,7 +5,7 @@
<t-input v-model.trim="formModel.name" placeholder="姓名" borderless /> <t-input v-model.trim="formModel.name" placeholder="姓名" borderless />
</t-form-item> </t-form-item>
<t-form-item v-if="isCreateMode" label="手机号"> <t-form-item v-if="isCreateMode" label="手机号">
<t-input v-model.trim="formModel.mobile" placeholder="手机号" borderless /> <t-input v-model.trim="formModel.mobile" type="tel" placeholder="手机号" borderless />
</t-form-item> </t-form-item>
<t-form-item v-if="isCreateMode" label="密码"> <t-form-item v-if="isCreateMode" label="密码">
<t-input v-model.trim="formModel.password" type="password" placeholder="密码" borderless /> <t-input v-model.trim="formModel.password" type="password" placeholder="密码" borderless />
+4 -1
View File
@@ -3,11 +3,14 @@
<t-cell-group class="user-card" theme="card"> <t-cell-group class="user-card" theme="card">
<t-cell <t-cell
:title="currentUserName" :title="currentUserName"
:description="currentUserDesc"
arrow arrow
hover hover
@click="go('/management/profile')" @click="go('/management/profile')"
> >
<template #description>
<telephone-link v-if="currentUser?.phoneNo" :value="currentUser.phoneNo" />
<span v-else v-text="currentUserDesc" />
</template>
<template #leftIcon> <template #leftIcon>
<t-image <t-image
class="avatar" class="avatar"
+25
View File
@@ -0,0 +1,25 @@
export function normalizeTelephone(value?: string): string {
if (!value) {
return "";
}
const trimmedValue = value.trim();
if (!trimmedValue) {
return "";
}
const normalizedValue = trimmedValue.startsWith("+")
? `+${trimmedValue.slice(1).replace(/\D/g, "")}`
: trimmedValue.replace(/\D/g, "");
return normalizedValue === "+" ? "" : normalizedValue;
}
export function canDialTelephone(value?: string): boolean {
const normalizedValue = normalizeTelephone(value);
return 2 < normalizedValue.replace("+", "").length;
}
export function buildTelephoneURL(value?: string): string {
const normalizedValue = normalizeTelephone(value);
return normalizedValue ? `tel:${normalizedValue}` : "";
}