商城c端前端

This commit is contained in:
richjohnrambo
2026-10-12 23:40:02 +08:00
parent dd729cb679
commit 96d1de4a95
105 changed files with 6762 additions and 6 deletions
+109
View File
@@ -0,0 +1,109 @@
<template>
<section v-if="!isChat" class="m-support">
<div class="m-support-hero" :style="{ backgroundImage: `url(${mobileAssets.supportHero})` }">
<PageHeader title="客服中心" :back="false" />
<div><h1>在线客服</h1><p>账户与业务问题请联系在线客服,全程跟进处理</p></div>
</div>
<div class="m-support-body">
<AsyncState
:loading="loading"
:error="error"
:empty="!services.length"
empty-text="在线客服暂未开放"
@retry="load"
>
<button
v-for="item in services"
:key="item.id"
class="m-service-card"
@click="$router.push(`/app/support/${item.id}`)"
>
<div class="m-service-avatar"
><img
v-if="safeImage(item.avatar_url)"
:src="safeImage(item.avatar_url)"
:alt="item.name" /><n-icon v-else :component="HeadsetOutline"
/></div>
<div
><h2>{{ item.name }}</h2
><p>工作时间:{{ item.work_time || '以客服在线状态为准' }}</p
><small v-if="item.backup_contact">{{ item.backup_contact }}</small></div
>
<span :class="`is-${item.online_status}`">{{ statusText(item.online_status) }}</span>
</button>
</AsyncState>
</div>
</section>
<section v-else class="m-chat">
<PageHeader :title="current?.name || '在线客服'" />
<div class="m-chat-time">会话内容由客服系统提供</div>
<div class="m-chat-empty">
<n-icon :component="ChatbubbleEllipsesOutline" />
<h2>正在连接客服</h2>
<p>{{ current?.privacy_notice || '请勿在聊天中发送登录密码或支付密码' }}</p>
<a
v-if="safeServiceUrl"
:href="safeServiceUrl"
target="_blank"
rel="noopener noreferrer"
class="m-primary-btn m-btn-link"
>进入客服会话</a
>
<p v-else class="m-form-error">客服会话地址尚未配置</p>
</div>
<div class="m-chat-input"
><button aria-label="语音"><img :src="mobileAssets.chatVoice" alt="" /></button
><input disabled placeholder="请先连接客服会话" /><button aria-label="更多"
><img :src="mobileAssets.chatMore" alt="" /></button
></div>
</section>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { ChatbubbleEllipsesOutline, HeadsetOutline } from '@vicons/ionicons5';
import PageHeader from '../components/PageHeader.vue';
import AsyncState from '../components/AsyncState.vue';
import { errorText, request, safeImage } from '../api';
import { mobileAssets } from '../assets';
const route = useRoute();
const isChat = computed(() => Boolean(route.params.id));
const loading = ref(true);
const error = ref('');
const services = ref<any[]>([]);
const current = computed(
() =>
services.value.find((item) => String(item.id) === String(route.params.id)) ||
services.value[0]
);
const safeServiceUrl = computed(() => {
const value = current.value?.service_url;
if (!value) return '';
try {
const u = new URL(value);
return u.protocol === 'https:' ? u.href : '';
} catch {
return '';
}
});
async function load() {
loading.value = true;
error.value = '';
try {
services.value = await request<any[]>({ url: '/v1/customer-services' });
} catch (err) {
error.value = errorText(err);
services.value = [];
} finally {
loading.value = false;
}
}
function statusText(status: string) {
if (status === 'online') return '在线咨询';
if (status === 'busy') return '忙碌';
return '离线留言';
}
onMounted(load);
</script>