| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786 |
- <template>
- <!-- #ifdef APP -->
- <scroll-view style="flex:1">
- <!-- #endif -->
- <view class="container">
- <!-- 背景图改为absolute,避免fixed在scroll-view内的问题 -->
- <image class="bg-image" src="@/static/imagesInfo/bg-color.png" mode="aspectFill" />
- <scroll-view style="flex: 1" scroll-y>
- <view class="page">
- <!-- 城市信息 -->
- <view class="city-info">
- <text class="city-text-box">当前城市:{{ state.cityInfo }}</text>
- <u-icon name="notification" :size="24" />
- </view>
- <!-- 用户卡片 -->
- <view class="user-card">
- <image class="user-bg" src="@/static/imagesInfo/bg-icon.png" mode="scaleToFill" />
- <view class="user-content" style="padding: 20rpx;">
- <!-- 左侧信息 -->
- <view class="user-left">
- <view class="user-row">
- <text class="user-name">{{ coachInfo.nickname }}</text>
- <view class="tags">
- <text class="tag-new">新人实习</text>
- </view>
- <u-icon name="edit" :size="18" @click="jumpMasterInfo" />
- <text class="tag">编辑</text>
- </view>
- <view class="user-row">
- <u-icon name="customer-interests" :size="18" />
- <text class="user-role">{{ coachInfo.role_name }}</text>
- <u-icon name="store" :size="18" />
- <text class="user-shop">{{ coachInfo.shop_name }}</text>
- </view>
- <view class="online-switch">
- <u-switch :checked="state.isOnline" @change="toggleOnline" />
- </view>
- </view>
- <!-- 右侧头像 -->
- <view class="user-right" @click="jumpMasterInfo">
- <text class="time-text">入驻时间</text>
- <text class="time-text" style="margin-top: 5rpx;">{{ displayCreatedAt }}</text>
- <image class="avatar-img" :src="coachInfo.avatar" mode="aspectFit" />
- </view>
- </view>
- <!-- 定位栏 -->
- <view class="location-bar">
- <u-icon name="navigation" :size="18" />
- <text class="location-text">当前定位:{{ userLocation }}</text>
- <text class="location-btn" @click="updateLocation">手动更新</text>
- </view>
- </view>
- <!-- 本月数据 -->
- <view class="stats-row">
- <view class="stat-item" v-for="(item, index) in monthStats" :key="index">
- <!-- {{item?.["label"]}} -->
- <text class="stat-label">{{ item?.["label"] }}</text>
- <text class="stat-value">{{ item?.["value"]}}</text>
- </view>
- </view>
- <!-- 功能按钮 -->
- <view class="func-grid">
- <view class="func-item" v-for="(item, index) in funcList" :key="index"
- @click="jumpSetProject(item.label)">
- <image class="func-icon" :src="item.iconUrl" mode="aspectFit" />
- <text class="func-label">{{ item.label }}</text>
- </view>
- </view>
- <!-- 数据统计 -->
- <view class="data-section">
- <view class="section-header">
- <text class="section-title">数据统计</text>
- <text class="section-more" @click="jumpStatisics">查看全部 ></text>
- </view>
- <view class="data-grid">
- <view class="data-item" v-for="(item, index) in coachData" :key="index">
- <text class="data-value">{{ item?.["value"] }}</text>
- <text class="data-label">{{ item?.["label"] }}</text>
- </view>
- </view>
- </view>
- <!-- 客户评价 -->
- <view class="eval-section">
- <view class="section-header">
- <text class="section-title">客户评价</text>
- <text class="section-more" @click="jumpAppraise">查看全部 ></text>
- </view>
- <scroll-view class="eval-tags" scroll-x>
- <text class="eval-tag" v-for="(tag, index) in evalTags" :key="index">
- {{ tag?.["text"] }} {{ (tag?.["count"])as number > 0 ? tag?.["count"] : '' }}
- </text>
- </scroll-view>
- <view class="eval-item" v-for="(item, index) in evalList" :key="index">
- <image class="eval-avatar" :src='(item["user_info"] as UTSJSONObject)?.["avatar"] as string'
- mode="aspectFill" />
- <view class="eval-content">
- <view class="eval-top">
- <text
- class="eval-name">{{(item['user_info'] as UTSJSONObject)?.['nickname'] as string}}</text>
- <text class="eval-date">{{item?.["created_at"] as string}}</text>
- </view>
- <view class="eval-stars">
- <text>⭐⭐⭐⭐⭐</text>
- <text class="eval-service">{{item?.["project_name"]}}</text>
- </view>
- <view style="flex-direction: row;">
- <text class="eval-comment" v-for='(tag, index) in (item?.["tags_list"]) as string[]'
- :key="index">
- {{tag}}
- </text>
- </view>
- </view>
- </view>
- </view>
- <!-- 底部留白,避免悬浮球遮挡 -->
- <!-- <view style="height: 100rpx;"></view> -->
- </view>
- </scroll-view>
- <!-- 悬浮球移到scroll-view外 -->
- <u-floating @click="callPolice" />
- </view>
- <!-- #ifdef APP -->
- </scroll-view>
- <!-- #endif -->
- </template>
- <script setup lang="uts">
- import { ref, computed, reactive, } from 'vue'
- import { getCoachDetaile } from '@/utils/api/masterInfoApi'
- import { getCommentList } from '@/utils/api/order'
- import { getMonthData, getAllData, getCoachWorkState, editCoachWorkState, editWorkTimeSetting } from '@/utils/api/workbenches'
- // ==================== 类型定义 ====================
- type CoachInfo = {
- nickname : string
- role_name : string
- shop_name : string
- created_at : string
- avatar : string
- coach : string
- }
- type State = {
- isOnline : boolean
- cityInfo : string
- }
- type FuncItem = {
- label : string
- iconUrl : string
- }
- type EvalTag = {
- text : string
- count : number
- }
- type StatItem = {
- label : string,
- value : string | number
- }
- type UserInfo = {
- nickname ?: string
- avatar ?: string
- }
- type EvalItem = {
- user_info ?: UserInfo
- created_at ?: string
- project_name ?: string
- tags_list ?: string[]
- }
- // ==================== 响应式数据(全部使用reactive/ref,避免shallowRef)====================
- const state = ref<State>({
- isOnline: true,
- cityInfo: '定位中...'
- })
- const coachInfo = reactive<CoachInfo>({
- nickname: '',
- role_name: '小丁理疗师',
- shop_name: '未绑定门店',
- created_at: '',
- avatar: '/static/testInfo/boy-nickname.png',
- coach: ''
- })
- const userLocation = ref<string>('')
- const displayCreatedAt = computed(() : string => {
- const date = coachInfo.created_at
- if (date == null || date.length == 0) {
- return '--'
- }
- return date
- })
- // ==================== 静态数据 ====================
- const monthStats = ref<UTSArray<UTSJSONObject>>([
- { label: '本月收益(元)', value: '2234.88' },
- { label: '本月接单量(单)', value: '2234.88' },
- { label: '本月退单率', value: '30%' }
- ])
- const funcList : FuncItem[] = [
- { iconUrl: '/static/imagesInfo/cx-shop.png', label: '接单时间' },
- { iconUrl: '/static/imagesInfo/item-icon.png', label: '服务项目' },
- { iconUrl: '/static/imagesInfo/jied-time.png', label: '重选店铺' },
- { iconUrl: '/static/imagesInfo/gengxin-wz.png', label: '位置更新' }
- ]
- const coachData = ref<UTSArray<UTSJSONObject>>([
- { value: '456', label: '接单量' },
- { value: '10%', label: '加钟率' },
- { value: '70%', label: '好评率' },
- { value: '3%', label: '复购率' },
- { value: '19%', label: '退单率' }
- ])
- const evalTags = ref<UTSArray<UTSJSONObject>>([
- { text: '不良引导', count: 0 },
- { text: '手法不好', count: 101 },
- { text: '性格温柔', count: 198 },
- { text: '服务到位', count: 10 }
- ])
- const evalList = ref<UTSJSONObject[]>([])
- // ==================== 方法 ====================
- const toggleOnline = async () => {
- state.value.isOnline = !state.value.isOnline
- console.log('Status changed:', state.value.isOnline)
- const status = state.value.isOnline ? 2 : 1
- try {
- const response = await editCoachWorkState({ status: status }) as UTSJSONObject
- console.log('Set work state result:', response)
- const code = response["code"] as number
- const message = response["message"] as string
- if (code !== 200) {
- uni.showToast({ title: message, icon: 'none' })
- return false
- }
- uni.showToast({ title: '状态切换成功', icon: 'success' })
- return true
- } catch (err : any) {
- console.error('设置技师工作状态失败:', err)
- uni.showToast({ title: '网络错误', icon: 'none' })
- return false
- }
- }
- const httpGetMonthData = async () => {
- try {
- const response = await getMonthData() as UTSJSONObject
- console.log('Month data:', response)
- const code = response["code"] as number
- const dataObj = response["data"] as UTSJSONObject | null
- if (code != 200 || dataObj == null) {
- return
- }
- monthStats.value = [
- { label: '本月收益(元)', value: dataObj["commission_amount"] ?? 0 },
- { label: '本月接单量(单)', value: dataObj["total_count"] ?? 0 },
- {
- label: '本月退单率',
- value: dataObj["refund_rate"] != ''
- ? ((dataObj["refund_rate"] as number) * 100).toFixed(0) + '%'
- : '0%'
- }
- ]
- console.log(monthStats.value, 'monthStats.value')
- } catch (err : any) {
- console.error('获取技师本月数据失败:', err)
- }
- }
- const httpGetAllData = async () => {
- try {
- const response = await getAllData({}) as UTSJSONObject
- console.log('All stats:', response)
- const code = response["code"] as number
- const dataObj = response["data"] as UTSJSONObject | null
- if (code != 200 || dataObj == null) {
- return
- }
- // coachData.value = [
- // {
- // value: dataObj["receiving"] ?? 0,
- // label: '接单量'
- // },
- // {
- // value: ((dataObj["additional"] as UTSJSONObject)?.["rate"] ?? 0) * 100 + '%',
- // label: '加钟率'
- // },
- // {
- // value: ((dataObj["comment"] as UTSJSONObject)?.["goodrate"] ?? 0) * 100 + '%',
- // label: '好评率'
- // },
- // {
- // value: ((dataObj["renewal"] as UTSJSONObject)?.["rate"] ?? 0) * 100 + '%',
- // label: '复购率'
- // },
- // {
- // value: ((dataObj["refund"] as UTSJSONObject)?.["rate"] ?? 0) * 100 + '%',
- // label: '退单率'
- // }
- // ]
- } catch (err : any) {
- console.error('获取技师统计数据失败:', err)
- }
- }
- const httpGetCoachWorkState = async () => {
- try {
- const response = await getCoachWorkState() as UTSJSONObject
- const code = response["code"] as number
- const dataObj = response["data"] as UTSJSONObject | null
- if (code != 200 || dataObj == null) {
- return
- }
- // 根据接口返回值修改技师工作状态
- // 通常work_status字段:1为在线,2为离线等
- state.value.isOnline = (dataObj["work_status"] as number) === 1
- } catch (err : any) {
- console.error('获取技师工作状态失败:', err)
- }
- }
- // 获取客户评价接口
- const httpGetEvaluations = async () => {
- try {
- const response = await getCommentList({
- page: 1,
- per_page: 5
- }) as UTSJSONObject
- const code = response["code"] as number
- const data = response["data"] as UTSJSONObject
- if (code !== 200) return
- evalTags.value = data?.tags_list as UTSArray<UTSJSONObject>;
- evalList.value = data?.items as UTSArray<UTSJSONObject>;
- } catch (err : any) {
- console.error('获取客户评价接口异常', err)
- }
- }
- const httpGetCoachDetail = async () => {
- const response = await getCoachDetaile() as UTSJSONObject
- // console.log('Coach detail:', response)
- const code = response["code"] as number
- const dataObj = response["data"] as UTSJSONObject | null
- if (code != 200 || dataObj == null) {
- return
- }
- const coach = dataObj["coach"] as UTSJSONObject | null
- const coachValue = (coach?.["info_records"] as UTSArray<UTSJSONObject>)[0] as UTSJSONObject | null
- // console.log(coachValue, 'coachValue')
- // console.log(coachValue?.state_text, 'value');
- if (coach == null) {
- uni.navigateTo({
- url: '/pages/login/merchantRecuitment'
- })
- return
- }
- else if (coachValue?.state_text == '审核中' || coachValue?.state_text == '审核拒绝') {
- uni.navigateTo({
- url: `/pages/login/checkMiddle?stateNum=${coachValue?.state_text == '审核拒绝' ? 3 : 1}&remark=${coachValue?.audit_remark}`
- })
- }
- coachInfo.nickname = dataObj["nickname"] as string
- const spaceIndex = (dataObj["created_at"] as string).lastIndexOf(" ")
- coachInfo.created_at = (dataObj["created_at"] as string).substring(0, spaceIndex)
- // coachInfo.created_at = dataObj["created_at"] as string
- coachInfo.avatar = dataObj["avatar"] as string
- state.value.isOnline = coach?.["work_status"] == 1 ? true : false as boolean
- console.log(coach["work_status"], state.value.isOnline, 'work_status')
- httpGetMonthData();
- httpGetAllData();
- httpGetCoachWorkState();
- httpGetEvaluations();
- }
- const updateLocation = async () => {
- // try {
- // const location = await new Promise<UniApp.GetLocationSuccess>((resolve, reject) => {
- // uni.getLocation({
- // type: 'gcj02',
- // success: resolve,
- // fail: reject
- // })
- // })
- // console.log('Location:', location)
- // // 简化处理,实际需要逆地理编码
- // userLocation.value = `${location.latitude.toFixed(2)}, ${location.longitude.toFixed(2)}`
- // } catch (e) {
- // console.error('定位错误:', e)
- // uni.showToast({ title: '定位失败', icon: 'none' })
- // userLocation.value = '定位失败'
- // }
- }
- const jumpMasterInfo = () => {
- uni.navigateTo({
- url: '/pages/myEdit/myEdit'
- });
- }
- const jumpSetProject = (label : string) => {
- console.log('Navigate to:', label)
- switch (label) {
- case '接单时间':
- uni.navigateTo({ url: '/pages/homepage/setOrderTime' })
- break
- case '服务项目':
- uni.navigateTo({ url: '/pages/homepage/serviceProject' })
- break
- }
- }
- const callPolice = () => {
- console.log('Emergency call triggered')
- }
- const jumpStatisics = () => {
- uni.navigateTo({ url: '/pages/order/orderStatisics' })
- }
- const jumpAppraise = () => {
- uni.navigateTo({ url: '/pages/order/appraise' })
- }
- onLoad(() => {
- // httpGetCoachDetail()
- })
- onReady(() => {
- httpGetCoachDetail();
- })
- </script>
- <style>
- .container {
- position: relative;
- }
- /* 修复9:背景图改为absolute,避免fixed在scroll-view内的问题 */
- .bg-image {
- position: absolute;
- top: 0;
- left: 0;
- width: 750rpx;
- height: 900rpx;
- z-index: 0;
- }
- .page {
- padding: 20rpx;
- position: relative;
- z-index: 1;
- }
- .city-info {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .city-text-box {
- font-size: 28rpx;
- color: #333;
- }
- /* 用户卡片 */
- .user-card {
- border-radius: 16rpx;
- margin-bottom: 32rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
- position: relative;
- overflow: hidden;
- background-color: #fff;
- }
- .user-bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- }
- .user-content {
- flex-direction: row;
- justify-content: space-between;
- position: relative;
- z-index: 1;
- }
- .user-left {
- flex: 1;
- flex-direction: column;
- }
- .user-row {
- flex-direction: row;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .user-name {
- font-size: 40rpx;
- font-weight: bold;
- color: #333;
- }
- .tags {
- flex-direction: row;
- margin-left: 16rpx;
- }
- .tag-new {
- background: linear-gradient(180deg, rgba(207, 221, 62, 0.69) 0%, rgba(162, 184, 29, 1) 100%);
- color: #fff;
- border-radius: 24rpx;
- font-size: 24rpx;
- padding: 6rpx 16rpx;
- }
- .tag {
- font-size: 24rpx;
- color: #666;
- margin-left: 8rpx;
- }
- .user-role,
- .user-shop {
- font-size: 26rpx;
- color: #999;
- margin-left: 8rpx;
- margin-right: 24rpx;
- }
- .online-switch {
- margin-top: 24rpx;
- }
- .user-right {
- width: 210rpx;
- align-items: center;
- }
- .time-text {
- font-size: 24rpx;
- color: #fff;
- text-align: center;
- margin-bottom: 10rpx;
- }
- .avatar-img {
- width: 138rpx;
- height: 138rpx;
- border-radius: 69rpx;
- border: 4rpx solid #fff;
- }
- .location-bar {
- flex-direction: row;
- align-items: center;
- background: linear-gradient(to right, rgba(255, 249, 225, 1), rgba(255, 241, 191, 1));
- padding: 16rpx 20rpx;
- border-radius: 18rpx;
- margin: 20rpx;
- position: relative;
- z-index: 1;
- margin-top: 32rpx;
- }
- .location-text {
- flex: 1;
- font-size: 26rpx;
- color: #333;
- margin-left: 8rpx;
- }
- .location-btn {
- font-size: 24rpx;
- color: #333;
- border: 2rpx solid #333;
- padding: 8rpx 16rpx;
- border-radius: 16rpx;
- }
- /* 本月数据 */
- .stats-row {
- flex-direction: row;
- background-color: #fff;
- margin-bottom: 32rpx;
- border-radius: 24rpx;
- padding: 32rpx 0;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
- }
- .stat-item {
- flex: 1;
- align-items: center;
- border-right-width: 2rpx;
- border-right-color: #eee;
- border-right-style: solid;
- }
- .stat-item:last-child {
- border-right-width: 0;
- }
- .stat-label {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 12rpx;
- }
- .stat-value {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- }
- /* 功能网格 */
- .func-grid {
- flex-direction: row;
- justify-content: space-around;
- background-color: #fff;
- margin-bottom: 32rpx;
- border-radius: 24rpx;
- padding: 32rpx 0;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
- }
- .func-item {
- align-items: center;
- }
- .func-icon {
- width: 92rpx;
- height: 92rpx;
- }
- .func-label {
- font-size: 24rpx;
- color: #666;
- margin-top: 16rpx;
- }
- /* 数据统计 */
- .data-section,
- .eval-section {
- background-color: #fff;
- margin-bottom: 32rpx;
- border-radius: 24rpx;
- padding: 32rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
- }
- .section-header {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 24rpx;
- }
- .section-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .section-more {
- font-size: 26rpx;
- color: #999;
- }
- .data-grid {
- flex-direction: row;
- justify-content: space-between;
- }
- .data-item {
- align-items: center;
- }
- .data-value {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 8rpx;
- }
- .data-label {
- font-size: 24rpx;
- color: #999;
- }
- /* 评价 */
- .eval-tags {
- flex-direction: row;
- margin-bottom: 24rpx;
- }
- .eval-tag {
- font-size: 24rpx;
- padding: 8rpx 20rpx;
- background-color: #f5f5f5;
- border-radius: 24rpx;
- margin-right: 16rpx;
- color: #666;
- }
- .eval-item {
- flex-direction: row;
- }
- .eval-avatar {
- width: 80rpx;
- height: 80rpx;
- border-radius: 40rpx;
- margin-right: 20rpx;
- }
- .eval-content {
- flex: 1;
- flex-direction: column;
- }
- .eval-top {
- flex-direction: row;
- justify-content: space-between;
- margin-bottom: 8rpx;
- }
- .eval-name {
- font-size: 28rpx;
- color: #333;
- }
- .eval-date {
- font-size: 24rpx;
- color: #999;
- }
- .eval-stars {
- flex-direction: row;
- align-items: center;
- margin-bottom: 8rpx;
- }
- .eval-service {
- font-size: 24rpx;
- color: #999;
- margin-left: 16rpx;
- }
- .eval-comment {
- font-size: 26rpx;
- color: #333;
- margin-right: 10rpx;
- }
- </style>
|