| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009 |
- <template>
- <view class="container">
- <!-- 吸顶固定层:状态tab + 搜索 + 筛选 -->
- <view class="sticky-header">
- <!-- 顶部 Tab (待接送/服务中...) -->
- <view class="status-tabs">
- <view class="tab-item" v-for="(tab, index) in tabs" :key="index"
- :class="{ active: currentTab === index }" @click="currentTab = index">
- <text>{{ tab }}</text>
- <view class="indicator" v-if="currentTab === index"></view>
- </view>
- </view>
- <!-- 搜索栏 -->
- <view class="search-bar">
- <view class="search-input-box">
- <input class="search-input" v-model="searchContent" placeholder="搜索地址/电话/姓名"
- placeholder-class="ph-style" />
- </view>
- </view>
- <!-- 筛选栏 (支持自定义下拉) -->
- <view class="filter-wrapper">
- <view class="filter-bar">
- <!-- 订单类型下拉视图 -->
- <view class="filter-item" :class="{ 'active': activeDropdown === 1 }" @click="toggleDropdown(1)">
- <text :class="{ 'active-text': activeDropdown === 1 || currentTypeFilterIdx > 0 }">
- {{ currentTypeFilterIdx > 0 ? typeFilterOptions[currentTypeFilterIdx] : '全部类型' }}
- </text>
- <view class="triangle" :class="activeDropdown === 1 ? 'up' : 'down'"></view>
- </view>
- <!-- 服务时间下拉视图 -->
- <view class="filter-item" :class="{ 'active': activeDropdown === 2 }" @click="toggleDropdown(2)">
- <text :class="{ 'active-text': activeDropdown === 2 || hasTimeFilter }">服务时间</text>
- <view class="triangle" :class="activeDropdown === 2 ? 'up' : 'down'"></view>
- </view>
- </view>
- <!-- 下拉内容面板与遮罩 -->
- <view class="dropdown-mask" v-if="activeDropdown !== 0" @click="closeDropdown"></view>
- <view class="dropdown-panel" v-if="activeDropdown === 1">
- <view class="type-option" v-for="(item, index) in typeFilterOptions" :key="index"
- :class="{ 'selected': currentTypeFilterIdx === index }" @click="selectType(index)">
- <text>{{ item }}</text>
- </view>
- </view>
- <view class="dropdown-panel calendar-panel" v-if="activeDropdown === 2">
- <view class="custom-calendar-container">
- <!-- 头部 -->
- <view class="cal-header">
- <text class="cal-nav-btn" @click="prevMonth">‹</text>
- <text class="cal-title">{{ currentMonth }}</text>
- <text class="cal-nav-btn" @click="nextMonth">›</text>
- </view>
- <!-- 星期条 -->
- <view class="cal-weekdays">
- <text v-for="(wk, idx) in weekDays" :key="idx" class="wk-item">{{ wk }}</text>
- </view>
- <!-- 日期网格 -->
- <view class="cal-body">
- <view v-for="(day, idx) in calendarDays" :key="idx" class="cal-day-box"
- :class="day ? getDateClass(day) : ''" @click="day && selectDateItem(day)">
- <view class="cal-day-text" v-if="day">{{ day }}</view>
- </view>
- </view>
- </view>
- <view class="calendar-actions">
- <button class="cal-btn reset" @click="resetTimeFilter">重置</button>
- <button class="cal-btn confirm" @click="confirmTimeFilter">确定</button>
- </view>
- </view>
- </view><!-- end filter-wrapper -->
- </view><!-- end sticky-header -->
- <!-- 订单列表 -->
- <view class="order-list">
- <view class="order-card" v-for="(item, index) in orderList" :key="index" :class="{ 'disabled-card': !item.serviceFlag }">
- <view class="card-header">
- <view class="type-badge">
- <image class="type-icon" :src="item.typeIcon"></image>
- <text class="type-text">{{ item.typeText }}</text>
- </view>
- <text class="status-badge" :class="getStatusClass(item)">{{ getDisplayStatus(item) }}</text>
- </view>
- <view class="card-body">
- <view class="time-row">
- <view class="time-col">
- <text class="label">{{ item.timeLabel }}:</text>
- <text class="value">{{ item.time }}</text>
- </view>
- <text class="fulfillmentCommission">¥{{ item.fulfillmentCommission }}</text>
- </view>
- <!-- 宠物信息 -->
- <view class="pet-card">
- <image class="pet-avatar" :src="item.petAvatarUrl || item.petAvatar" mode="aspectFill"></image>
- <view class="pet-info">
- <text class="pet-name">{{ item.petName }}</text>
- <text class="pet-breed">品种: {{ item.petBreed }}</text>
- </view>
- </view>
- <!-- 路线信息 (完全复用 Home 样式) -->
- <view class="route-info">
- <template v-if="item.type === 1">
- <view class="route-item" @click.stop="openNavigation(item, 'start')">
- <view class="icon-circle start">起</view>
- <view class="route-line-vertical"></view>
- <view class="address-box">
- <text class="addr-title">{{ item.startLocation }}</text>
- <text class="addr-desc">{{ item.startAddress }}</text>
- </view>
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"
- style="flex-shrink: 0; align-self: center;"></image>
- </view>
- <view class="route-item" @click.stop="openNavigation(item, 'end')">
- <view class="icon-circle end">终</view>
- <view class="address-box">
- <text class="addr-title">{{ item.endLocation }}</text>
- <text class="addr-desc">{{ item.endAddress }}</text>
- </view>
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"
- style="flex-shrink: 0; align-self: center;"></image>
- </view>
- </template>
- <template v-else>
- <view class="route-item" @click.stop="openNavigation(item, 'end')">
- <view class="icon-circle service">服</view>
- <view class="address-box">
- <text class="addr-title">{{ item.endLocation }}</text>
- <text class="addr-desc">{{ item.endAddress }}</text>
- </view>
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"
- style="flex-shrink: 0; align-self: center;"></image>
- </view>
- <view class="service-content" v-if="item.serviceContent">
- <text class="content-label">服务内容:</text>
- <text>{{ item.serviceContent }}</text>
- </view>
- </template>
- </view>
- <!-- 备注 -->
- <view class="remark-box">
- <text>备注:{{ item.remark || '-' }}</text>
- </view>
- </view><!-- End of card-body -->
- <!-- 按钮组 (重新排版) -->
- <view class="action-btns" v-if="['接单', '到达', '出发', '开始', '送达', '结束'].includes(item.statusText)">
- <view class="action-row">
- <button class="btn normal danger" v-if="item.status === 2"
- @click.stop="handleCancelOrder(item)">取消订单</button>
- <button class="btn normal" @click.stop="reportAbnormal(item)">异常上报</button>
- <button class="btn normal" @click.stop="addOrUpdateService(item)">增改服务项</button>
- </view>
- <view class="action-row">
- <button class="btn normal" @click.stop="doCall('customer', item)">拨号</button>
- <button class="btn primary" @click.stop="mainAction(item)">到达打卡</button>
- </view>
- </view>
- </view>
- <!-- 已加载完提示文字 -->
- <view class="loading-text">已加载完</view>
- <view style="height: 160rpx;"></view>
- </view>
- <view class="call-mask" v-if="activeCallItem" @click="closeCallMenu"></view>
- </view>
- <!-- 宠物档案弹窗 (复用Home) -->
- <view class="pet-modal-mask" v-if="showPetModal" @click="closePetProfile">
- <view class="pet-modal-content" @click.stop>
- <view class="pet-modal-header">
- <text class="pet-modal-title">宠物档案</text>
- <view class="pm-header-actions">
- <view class="pm-remark-btn" @click="openRemarkInput">备注</view>
- <view class="close-icon-btn" @click="closePetProfile">×</view>
- </view>
- </view>
- <scroll-view scroll-y class="pet-modal-scroll">
- <!-- Basic Info -->
- <view class="pet-base-info">
- <image class="pm-avatar" :src="currentPetInfo.petAvatar" mode="aspectFill"></image>
- <view class="pm-info-text">
- <view class="pm-name-row">
- <text class="pm-name">{{ currentPetInfo.petName }}</text>
- <view class="pm-gender" v-if="currentPetInfo.petGender === 'M'">
- <text class="gender-icon">♂</text>
- <text>公</text>
- </view>
- <view class="pm-gender female" v-else-if="currentPetInfo.petGender === 'F'">
- <text class="gender-icon">♀</text>
- <text>母</text>
- </view>
- </view>
- <text class="pm-breed">品种:{{ currentPetInfo.petBreed }}</text>
- </view>
- </view>
- <!-- Details Grid -->
- <view class="pm-detail-grid">
- <view class="pm-grid-item half">
- <text class="pm-label">年龄</text>
- <text class="pm-val">{{ currentPetInfo.petAge || '未知' }}</text>
- </view>
- <view class="pm-grid-item half">
- <text class="pm-label">体重</text>
- <text class="pm-val">{{ currentPetInfo.petWeight || '未知' }}</text>
- </view>
- <view class="pm-grid-item full">
- <text class="pm-label">性格</text>
- <text class="pm-val">{{ currentPetInfo.petPersonality || '无' }}</text>
- </view>
- <view class="pm-grid-item full">
- <text class="pm-label">爱好</text>
- <text class="pm-val">{{ currentPetInfo.petHobby || '无' }}</text>
- </view>
- <view class="pm-grid-item full">
- <text class="pm-label">备注</text>
- <text class="pm-val">{{ currentPetInfo.petRemark || '无特殊过敏史' }}</text>
- </view>
- </view>
- <!-- Tags -->
- <view class="pm-tags" v-if="currentPetInfo.petTags && currentPetInfo.petTags.length > 0">
- <view class="pm-tag" v-for="(tag, index) in currentPetInfo.petTags" :key="index">{{ tag }}</view>
- </view>
- <!-- Log Section -->
- <view class="pm-section-title">
- <view class="orange-bar"></view>
- <text>备注日志</text>
- </view>
- <view class="pm-log-list">
- <view class="pm-log-item" v-for="(log, lIndex) in currentPetInfo.petLogs" :key="lIndex">
- <text class="pm-log-date">{{ log.date }}</text>
- <text class="pm-log-text">{{ log.content }}</text>
- <text class="pm-log-recorder">{{ log.recorder === '系统记录' ? '' : '记录人: ' }}{{ log.recorder
- }}</text>
- </view>
- </view>
- <view style="height: 30rpx;"></view>
- </scroll-view>
- </view>
- </view>
- <!-- 备注输入弹窗 -->
- <view class="remark-mask" v-if="showRemarkInput" @click="closeRemarkInput">
- <view class="remark-sheet" @click.stop>
- <view class="remark-sheet-header">
- <text class="remark-sheet-title">添加备注</text>
- <view class="close-icon-btn" @click="closeRemarkInput">×</view>
- </view>
- <textarea class="remark-textarea" v-model="remarkText" placeholder="请输入备注内容..." maxlength="500"
- auto-height />
- <view class="remark-submit-btn" @click="submitRemark">提交备注</view>
- </view>
- </view>
- <!-- 选择地图导航弹窗 (复用Home) -->
- <view class="nav-modal-mask" v-if="showNavModal" @click="closeNavModal">
- <view class="nav-action-sheet" @click.stop>
- <view class="nav-sheet-title">选择地图进行导航</view>
- <view class="nav-sheet-item" @click="chooseMap('高德')">高德地图</view>
- <view class="nav-sheet-item" @click="chooseMap('腾讯')">腾讯地图</view>
- <view class="nav-sheet-item" @click="chooseMap('百度')">百度地图</view>
- <view class="nav-sheet-gap"></view>
- <view class="nav-sheet-item cancel" @click="closeNavModal">取消</view>
- </view>
- </view>
- <!-- 取消订单确认弹窗 -->
- <view class="modal-mask" v-if="showCancelModal">
- <view class="custom-modal">
- <text class="modal-title">取消订单</text>
- <view class="textarea-container">
- <textarea class="reject-textarea" v-model="cancelReason" placeholder="请输入取消原因(必填)"
- maxlength="100"></textarea>
- <text class="char-count">{{ cancelReason.length }}/100</text>
- </view>
- <view class="modal-btns mt-30">
- <button class="modal-btn cancel" @click="closeCancelModal">再想想</button>
- <button class="modal-btn confirm" :class="{ 'disabled': !cancelReason.trim() }"
- @click="confirmCancel">确认取消</button>
- </view>
- </view>
- </view>
- <custom-tabbar currentPath="pages/orders/index"></custom-tabbar>
- </template>
- <script>
- import { getMyOrders, cancelOrderApi } from '@/api/order/subOrder'
- import { listAllService } from '@/api/service/list'
- import { reportGps } from '@/utils/gps'
- import customTabbar from '@/components/custom-tabbar/index.vue'
- export default {
- components: {
- customTabbar
- },
- data() {
- return {
- currentTab: 0,
- tabs: ['待接送/服务', '配送/服务中', '已完成', '已取消'],
- typeFilterOptions: ['全部类型'],
- currentTypeFilterIdx: 0,
- activeDropdown: 0,
- hasTimeFilter: false,
- currentMonth: '',
- viewDate: new Date(),
- weekDays: ['日', '一', '二', '三', '四', '五', '六'],
- calendarDays: [],
- selectedDateRange: [],
- allOrderList: [],
- serviceList: [],
- searchContent: '',
- startServiceTime: '',
- endServiceTime: '',
- showPetModal: false,
- currentPetInfo: {},
- showNavModal: false,
- navTargetItem: null,
- navTargetPointType: '',
- activeCallItem: null,
- showRemarkInput: false,
- remarkText: '',
- showCancelModal: false,
- cancelReason: '',
- currentOrder: null
- }
- },
- created() {
- this.initCalendar();
- },
- async onLoad() {
- await this.loadServiceList()
- await this.loadOrders()
- // 显式请求一次定位授权
- reportGps(true).catch(e => console.log('Init GPS check skipped', e));
- },
- onShow() {
- uni.hideTabBar()
- // 此处不需要重复调用,因为逻辑可能在onLoad已处理,
- // 或者如果需要每次进入都刷新,可以保留,但需确保顺序
- this.loadOrders()
- },
- async onPullDownRefresh() {
- try {
- await this.loadServiceList()
- await this.loadOrders()
- } finally {
- uni.stopPullDownRefresh()
- }
- },
- watch: {
- currentTab() {
- this.loadOrders()
- },
- currentTypeFilterIdx() {
- this.loadOrders()
- },
- searchContent() {
- // 搜索内容变化时,自动重新加载订单
- this.loadOrders()
- }
- },
- computed: {
- orderList() {
- return this.allOrderList;
- }
- },
- methods: {
- async loadServiceList() {
- try {
- const res = await listAllService()
- this.serviceList = res.data || []
- this.typeFilterOptions = ['全部类型', ...this.serviceList.map(s => s.name)]
- } catch (err) {
- console.error('获取服务类型失败:', err)
- uni.showToast({ title: err.message || err.msg || '获取服务失败', icon: 'none' })
- }
- },
- async loadOrders() {
- try {
- const statusMap = { 0: 2, 1: 3, 2: 4, 3: 5 }
- const serviceId = this.currentTypeFilterIdx > 0 ? this.serviceList[this.currentTypeFilterIdx - 1]?.id : undefined
- const params = {
- status: statusMap[this.currentTab],
- content: this.searchContent || undefined,
- service: serviceId,
- startServiceTime: this.startServiceTime || undefined,
- endServiceTime: this.endServiceTime || undefined
- }
- console.log('订单列表请求参数:', params)
- const res = await getMyOrders(params)
- console.log('订单列表响应:', res)
- const orders = res.rows || []
- console.log('订单数量:', orders.length)
- this.allOrderList = orders.map(order => this.transformOrder(order, this.currentTab))
- } catch (err) {
- console.error('获取订单列表失败:', err)
- this.allOrderList = []
- uni.showToast({ title: err.message || err.msg || '加载订单失败', icon: 'none' })
- }
- },
- transformOrder(order, tabIndex) {
- const service = this.serviceList.find(s => s.id === order.service)
- const serviceText = service?.name || '未知'
- const serviceIcon = service?.iconUrl || ''
- const mode = service?.mode || 0
- const isRoundTrip = mode === 1
- // 根据 Tab 索引强制指定状态文字,忽略后端缺失的 status 字段
- let statusText = '接单'
- if (tabIndex === 0) {
- statusText = '接单' // 待接送/服务
- } else if (tabIndex === 1) {
- statusText = isRoundTrip ? '出发' : '开始' // 配送/服务中
- } else if (tabIndex === 2) {
- statusText = '已完成' // 已完成
- } else if (tabIndex === 3) {
- statusText = '已拒绝' // 已拒绝
- }
- return {
- id: order.id,
- status: order.status, // 保存原始 status 用于判断权限
- type: isRoundTrip ? 1 : 2,
- typeText: serviceText,
- typeIcon: serviceIcon,
- statusText: statusText,
- fulfillmentCommission: (order.fulfillmentCommission / 100).toFixed(2),
- timeLabel: '服务时间',
- time: order.serviceTime || '',
- petAvatar: order.petAvatar || '/static/dog.png',
- petAvatarUrl: order.petAvatarUrl || '',
- petName: order.petName || '',
- petBreed: order.breed || '',
- startLocation: order.fromAddress || '暂无起点',
- startAddress: order.fromAddress || '',
- fromAddress: order.fromAddress || '',
- fromLat: order.fromLat,
- fromLng: order.fromLng,
- startDistance: '0km',
- endLocation: (order.customerName || '') + ' ' + (order.customerPhone || ''),
- endAddress: order.toAddress || '',
- toAddress: order.toAddress || '',
- toLat: order.toLat,
- toLng: order.toLng,
- customerPhone: order.customerPhone || '',
- endDistance: '0km',
- serviceContent: order.remark || '',
- remark: order.remark || '',
- serviceFlag: !!order.serviceFlag // 是否允许服务(点击跳转)
- }
- },
- getDisplayStatus(item) {
- if (item.statusText === '已完成') return '已完成';
- if (item.statusText === '已拒绝') return '已拒绝';
- if (item.statusText === '接单') {
- return item.type === 1 ? '待接送' : '待服务';
- }
- return item.type === 1 ? '配送中' : '服务中';
- },
- getStatusClass(item) {
- let display = this.getDisplayStatus(item);
- if (display === '已完成') return 'finish';
- if (display === '已拒绝') return 'reject';
- if (display === '配送中' || display === '服务中') return 'processing';
- return 'highlight';
- },
- goToDetail(item) {
- uni.navigateTo({ url: `/pages/orders/detail/index?id=${item.id}` });
- },
- showPetProfile(item) {
- this.currentPetInfo = {
- ...item,
- petGender: 'M',
- petAge: '2岁',
- petWeight: '15kg',
- petPersonality: '活泼亲人,精力旺盛',
- petHobby: '喜欢追飞盘,爱吃肉干',
- petRemark: '肠胃较弱,不能乱喂零食;出门易爆冲,请拉紧牵引绳。',
- petTags: ['拉响警报', '不能吃鸡肉', '精力旺盛'],
- petLogs: [
- { date: '2026-02-09 14:00', content: '今天遛弯拉了两次粑粑,精神状态很好。', recorder: '王阿姨' },
- { date: '2026-02-08 10:30', content: '有些挑食,剩了小半碗狗粮。', recorder: '李师傅' },
- { date: '2026-02-05 09:00', content: '建档。', recorder: '系统记录' }
- ]
- };
- this.showPetModal = true;
- },
- closePetProfile() {
- this.showPetModal = false;
- },
- openNavigation(item, pointType) {
- this.navTargetItem = item;
- this.navTargetPointType = pointType;
- this.showNavModal = true;
- },
- closeNavModal() {
- this.showNavModal = false;
- },
- chooseMap(mapType) {
- let item = this.navTargetItem;
- let pointType = this.navTargetPointType;
- // 起 -> fromAddress ; 终 -> toAddress
- let name = pointType === 'start' ? (item.fromAddress || '起点') : (item.toAddress || '终点');
- let address = pointType === 'start' ? (item.fromAddress || '起点地址') : (item.toAddress || '终点地址');
- let latitude = pointType === 'start' ? Number(item.fromLat) : Number(item.toLat);
- let longitude = pointType === 'start' ? Number(item.fromLng) : Number(item.toLng);
- this.showNavModal = false;
- // 统一定义打开地图的函数
- const navigateTo = (lat, lng, addrName, addrDesc) => {
- uni.openLocation({
- latitude: lat,
- longitude: lng,
- name: addrName,
- address: addrDesc || '无法获取详细地址',
- success: function () {
- console.log('打开导航成功: ' + mapType);
- },
- fail: function (err) {
- console.error('打开导航失败:', err);
- uni.showToast({ title: '打开地图失败', icon: 'none' });
- }
- });
- };
- // 如果有目标经纬度,直接打开
- if (latitude && longitude && !isNaN(latitude) && !isNaN(longitude)) {
- navigateTo(latitude, longitude, name, address);
- } else {
- // 如果没有经纬度,按照需求:使用自己当前的经纬度,然后搜索 fromAddress 或者 toAddress
- uni.showLoading({ title: '获取当前位置...', mask: true });
- reportGps(true).then(res => {
- uni.hideLoading();
- // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息
- navigateTo(res.latitude, res.longitude, name, address);
- }).catch(err => {
- uni.hideLoading();
- console.error('获取地理位置失败:', err);
- // 具体的授权引导已在 reportGps 内部处理
- });
- }
- },
- toggleCallMenu(item) {
- if (this.activeCallItem === item) {
- this.activeCallItem = null;
- } else {
- this.activeCallItem = item;
- }
- },
- closeCallMenu() {
- this.activeCallItem = null;
- },
- doCall(type, item) {
- let phoneNum = '';
- const targetItem = item || this.activeCallItem;
- // 1. 获取电话号码
- if (type === 'merchant') {
- phoneNum = '18900008451';
- } else if (type === 'customer') {
- phoneNum = targetItem?.customerPhone;
- }
- // 2. 基础校验
- if (!phoneNum) {
- uni.showToast({ title: '未找到电话号码', icon: 'none' });
- this.activeCallItem = null;
- return;
- }
- // 3. 清洗号码 (去除空格、横杠等非数字字符)
- phoneNum = phoneNum.replace(/[^\d]/g, '');
- // 二次校验:确保清洗后仍有数字
- if (phoneNum.length < 3) {
- uni.showToast({ title: '电话号码格式错误', icon: 'none' });
- this.activeCallItem = null;
- return;
- }
- console.log('正在发起直接呼叫:', phoneNum);
- // 4. 核心逻辑:区分环境处理
- // #ifdef APP-PLUS
- // App 端:使用 uni.makePhoneCall 直接发起呼叫
- uni.makePhoneCall({
- phoneNumber: phoneNum,
- success: () => {
- console.log('成功唤起系统拨号盘');
- },
- fail: (err) => {
- console.error('拨号失败:', err);
- // 常见错误:Permission denied (权限被拒) 或 Activity not found
- let msg = '拨号失败';
- if (err.message && err.message.includes('permission')) {
- msg = '请在手机设置中允许"电话"权限';
- }
- uni.showToast({ title: msg, icon: 'none', duration: 3000 });
- // 如果失败,尝试引导用户去设置页 (仅限 Android)
- // #ifdef APP-ANDROID
- if (err.message && err.message.includes('permission')) {
- uni.showModal({
- title: '权限提示',
- content: '拨打电话需要电话权限,是否前往设置开启?',
- success: (res) => {
- if (res.confirm) {
- plus.runtime.openURL("app-settings:");
- }
- }
- });
- }
- // #endif
- },
- complete: () => {
- this.activeCallItem = null; // 关闭弹窗
- }
- });
- // #endif
- // #ifdef H5
- // H5 端:使用 tel: 协议
- window.location.href = `tel:${phoneNum}`;
- this.activeCallItem = null;
- // #endif
- // #ifdef MP-WEIXIN
- // 小程序端:直接调用 makePhoneCall (微信小程序支持直接弹框确认拨打)
- uni.makePhoneCall({
- phoneNumber: phoneNum,
- fail: () => {
- uni.showToast({ title: '拨号失败', icon: 'none' });
- },
- complete: () => {
- this.activeCallItem = null;
- }
- });
- // #endif
- },
- reportAbnormal(item) {
- uni.navigateTo({ url: '/pages/orders/anomaly/index?orderId=' + (item.id || '') });
- },
- toggleDropdown(idx) {
- if (this.activeDropdown === idx) {
- this.activeDropdown = 0;
- } else {
- this.activeDropdown = idx;
- }
- },
- closeDropdown() {
- this.activeDropdown = 0;
- },
- selectType(index) {
- this.currentTypeFilterIdx = index;
- this.closeDropdown();
- },
- initCalendar() {
- const year = this.viewDate.getFullYear();
- const month = this.viewDate.getMonth();
- this.currentMonth = `${year}年${month + 1}月`;
- // 获取该月第一天是周几 (0-6)
- const firstDay = new Date(year, month, 1).getDay();
- // 获取该月有多少天
- const daysInMonth = new Date(year, month + 1, 0).getDate();
- let days = [];
- // 填充开头的空白
- for (let i = 0; i < firstDay; i++) {
- days.push(0);
- }
- // 填充真实日期
- for (let i = 1; i <= daysInMonth; i++) {
- days.push(i);
- }
- this.calendarDays = days;
- },
- prevMonth() {
- this.viewDate.setMonth(this.viewDate.getMonth() - 1);
- // 切换月份时强制重新创建 Date 对象以触发 Vue 响应式(如果需要)或者简单调用 init
- this.viewDate = new Date(this.viewDate);
- this.initCalendar();
- },
- nextMonth() {
- this.viewDate.setMonth(this.viewDate.getMonth() + 1);
- this.viewDate = new Date(this.viewDate);
- this.initCalendar();
- },
- selectDateItem(day) {
- if (this.selectedDateRange.length === 2) {
- this.selectedDateRange = [day];
- } else if (this.selectedDateRange.length === 1) {
- let start = this.selectedDateRange[0];
- if (day > start) {
- this.selectedDateRange = [start, day];
- } else if (day < start) {
- this.selectedDateRange = [day, start];
- } else {
- this.selectedDateRange = [];
- }
- } else {
- this.selectedDateRange = [day];
- }
- },
- getDateClass(day) {
- if (!day || this.selectedDateRange.length === 0) return '';
- if (this.selectedDateRange.length === 1) {
- return day === this.selectedDateRange[0] ? 'is-start' : '';
- }
- let start = this.selectedDateRange[0];
- let end = this.selectedDateRange[1];
- if (day === start) return 'is-start';
- if (day === end) return 'is-end';
- if (day > start && day < end) return 'is-between';
- return '';
- },
- resetTimeFilter() {
- this.hasTimeFilter = false;
- this.selectedDateRange = [];
- this.startServiceTime = '';
- this.endServiceTime = '';
- this.closeDropdown();
- this.loadOrders();
- },
- confirmTimeFilter() {
- if (this.selectedDateRange.length === 0) {
- uni.showToast({ title: '请先选择日期', icon: 'none' });
- return;
- }
- // 构建时间范围参数
- const year = this.currentMonth.replace(/[^0-9]/g, '').substring(0, 4);
- const month = this.currentMonth.replace(/[^0-9]/g, '').substring(4);
- const pad = (n) => String(n).padStart(2, '0');
- if (this.selectedDateRange.length === 2) {
- this.startServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 00:00:00`;
- this.endServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[1])} 23:59:59`;
- } else {
- this.startServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 00:00:00`;
- this.endServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 23:59:59`;
- }
- this.hasTimeFilter = true;
- this.closeDropdown();
- this.loadOrders();
- },
- getMainActionText(item) {
- return '查看详情';
- },
- mainAction(item) {
- uni.navigateTo({ url: `/pages/orders/detail/index?id=${item.id}` });
- },
- addOrUpdateService(item) {
- // 跳转到申诉(增改服务项)页面
- uni.navigateTo({ url: `/pages/orders/appeal/index?id=${item.id}` });
- },
- openRemarkInput() {
- this.remarkText = '';
- this.showRemarkInput = true;
- },
- closeRemarkInput() {
- this.showRemarkInput = false;
- this.remarkText = '';
- },
- submitRemark() {
- const text = this.remarkText.trim();
- if (!text) {
- uni.showToast({ title: '备注内容不能为空', icon: 'none' });
- return;
- }
- const now = new Date();
- const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
- if (!this.currentPetInfo.petLogs) {
- this.$set(this.currentPetInfo, 'petLogs', []);
- }
- this.currentPetInfo.petLogs.unshift({
- date: dateStr,
- content: text,
- recorder: '我'
- });
- uni.showToast({ title: '备注已添加', icon: 'success' });
- this.closeRemarkInput();
- },
- /**
- * 取消订单处理逻辑 - 打开弹窗
- * @param {Object} item - 订单项
- */
- handleCancelOrder(item) {
- this.currentOrder = item;
- this.cancelReason = '';
- this.showCancelModal = true;
- },
- closeCancelModal() {
- this.showCancelModal = false;
- this.currentOrder = null;
- },
- async confirmCancel() {
- if (!this.cancelReason.trim()) {
- uni.showToast({ title: '请输入取消原因', icon: 'none' });
- return;
- }
- try {
- uni.showLoading({ title: '取消中...', mask: true });
- await cancelOrderApi({
- orderId: this.currentOrder.id,
- reason: this.cancelReason
- });
- uni.showToast({ title: '订单已取消', icon: 'success' });
- this.showCancelModal = false;
- this.currentOrder = null;
- // 延时刷新列表,防止提示框闪现
- setTimeout(() => {
- this.loadOrders();
- }, 1000);
- } catch (err) {
- console.error('取消订单失败:', err);
- uni.showToast({ title: err.message || err.msg || '取消失败', icon: 'none' });
- } finally {
- uni.hideLoading();
- }
- }
- }
- }
- </script>
- <style>
- page {
- background-color: #F8F8F8;
- }
- .custom-nav-bar {
- padding: 80rpx 30rpx 20rpx;
- background-color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .nav-title {
- font-size: 34rpx;
- font-weight: bold;
- color: #333;
- }
- .sticky-header {
- position: sticky;
- top: 0;
- z-index: 999;
- background-color: #F8F8F8;
- }
- .container {
- background-color: #F8F8F8;
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- }
- .status-tabs {
- display: flex;
- background-color: #fff;
- padding: 0 30rpx;
- justify-content: space-between;
- }
- .tab-item {
- position: relative;
- padding: 20rpx 0;
- font-size: 26rpx;
- color: #666;
- font-weight: 500;
- }
- .tab-item.active {
- color: #FF5722;
- font-weight: bold;
- }
- .indicator {
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- width: 40rpx;
- height: 6rpx;
- background-color: #FF5722;
- border-radius: 3rpx;
- }
- .search-bar {
- padding: 10rpx 30rpx;
- background-color: #fff;
- }
- .search-input-box {
- display: flex;
- align-items: center;
- background-color: #F8F8F8;
- height: 64rpx;
- border-radius: 32rpx;
- padding: 0 30rpx;
- }
- .search-input {
- flex: 1;
- font-size: 26rpx;
- color: #333;
- padding-left: 20rpx;
- }
- .ph-style {
- font-size: 26rpx;
- color: #999;
- }
- .filter-wrapper {
- position: relative;
- z-index: 998;
- }
- .filter-bar {
- display: flex;
- background-color: #fff;
- padding: 5rpx 30rpx 10rpx 30rpx;
- justify-content: space-between;
- position: relative;
- z-index: 998;
- }
- .filter-item {
- width: 48%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26rpx;
- color: #666;
- background-color: #F8F8F8;
- height: 56rpx;
- border-radius: 12rpx;
- transition: all 0.2s;
- }
- .filter-item.active {
- background-color: #FFF3E0;
- }
- .active-text {
- color: #FF5722;
- font-weight: 500;
- }
- .triangle {
- width: 0;
- height: 0;
- border-left: 8rpx solid transparent;
- border-right: 8rpx solid transparent;
- margin-left: 10rpx;
- transition: all 0.2s;
- }
- .triangle.down {
- border-top: 10rpx solid #dcdcdc;
- }
- .filter-item.active .triangle.down,
- .active-text+.triangle.down {
- border-top-color: #FF5722;
- }
- .triangle.up {
- border-bottom: 10rpx solid #FF5722;
- }
- .dropdown-mask {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- height: 100vh;
- background-color: rgba(0, 0, 0, 0.4);
- z-index: 80;
- }
- .dropdown-panel {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- background-color: #fff;
- z-index: 90;
- border-radius: 0 0 20rpx 20rpx;
- box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.05);
- overflow: hidden;
- }
- .type-option {
- padding: 30rpx 40rpx;
- font-size: 28rpx;
- color: #333;
- border-bottom: 1px solid #f5f5f5;
- }
- .type-option:last-child {
- border-bottom: none;
- }
- .type-option.selected text {
- color: #FF5722;
- font-weight: bold;
- }
- .calendar-panel {
- padding-bottom: 30rpx;
- }
- .custom-calendar-container {
- padding: 20rpx 30rpx 0;
- }
- .cal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20rpx 0;
- }
- .cal-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .cal-weekdays {
- display: flex;
- justify-content: space-around;
- padding: 20rpx 0;
- border-bottom: 1px solid #f5f5f5;
- }
- .wk-item {
- font-size: 24rpx;
- color: #999;
- width: 14.28%;
- text-align: center;
- }
- .cal-body {
- display: flex;
- flex-wrap: wrap;
- padding-top: 20rpx;
- }
- .cal-day-box {
- width: 14.28%;
- height: 80rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 10rpx;
- position: relative;
- }
- .cal-day-text {
- width: 64rpx;
- height: 64rpx;
- line-height: 64rpx;
- text-align: center;
- font-size: 28rpx;
- color: #333;
- border-radius: 8rpx;
- position: relative;
- z-index: 2;
- }
- .cal-day-box.is-start .cal-day-text,
- .cal-day-box.is-end .cal-day-text {
- background-color: #FF5722;
- color: #fff;
- font-weight: bold;
- }
- .cal-day-box.is-start::after {
- content: '';
- position: absolute;
- right: 0;
- top: 8rpx;
- bottom: 8rpx;
- width: 50%;
- background-color: #FFF3E0;
- z-index: 1;
- }
- .cal-day-box.is-end::after {
- content: '';
- position: absolute;
- left: 0;
- top: 8rpx;
- bottom: 8rpx;
- width: 50%;
- background-color: #FFF3E0;
- z-index: 1;
- }
- .cal-day-box.is-start.is-end::after {
- display: none;
- }
- .cal-day-box.is-between {
- background-color: #FFF3E0;
- margin-top: 8rpx;
- height: 64rpx;
- margin-bottom: 18rpx;
- }
- .cal-day-box.is-between .cal-day-text {
- color: #FF5722;
- }
- .calendar-actions {
- display: flex;
- justify-content: space-between;
- padding: 0 30rpx;
- margin-top: 20rpx;
- }
- .cal-btn {
- width: 48%;
- height: 70rpx;
- line-height: 70rpx;
- text-align: center;
- border-radius: 10rpx;
- font-size: 28rpx;
- margin: 0;
- }
- .cal-btn.reset {
- background-color: #f5f5f5;
- color: #666;
- }
- .cal-btn.confirm {
- background-color: #FF5722;
- color: #fff;
- }
- .order-list {
- padding: 0 30rpx;
- width: 100%;
- box-sizing: border-box;
- }
- .order-card {
- background-color: #fff;
- border-radius: 24rpx;
- padding: 20rpx 20rpx;
- margin-bottom: 20rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
- }
- .order-card:first-child {
- margin-top: 20rpx;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15rpx;
- }
- .type-badge {
- display: flex;
- align-items: center;
- }
- .type-icon {
- width: 44rpx;
- height: 44rpx;
- margin-right: 15rpx;
- background-color: #FFF3E0;
- border-radius: 50%;
- padding: 6rpx;
- box-sizing: border-box;
- }
- .type-text {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- }
- .status-badge {
- font-size: 28rpx;
- }
- .status-badge.highlight {
- color: #FF5722;
- }
- .status-badge.processing {
- color: #2196F3;
- }
- .status-badge.finish {
- color: #4CAF50;
- }
- .status-badge.reject {
- color: #9E9E9E;
- }
- .time-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 25rpx;
- }
- .time-row .time-col {
- display: flex;
- align-items: center;
- font-size: 26rpx;
- color: #333;
- }
- .time-row .label {
- color: #666;
- margin-right: 10rpx;
- }
- .fulfillmentCommission {
- font-size: 36rpx;
- font-weight: bold;
- color: #FF5722;
- }
- .pet-card {
- background-color: #FFF8F0;
- border-radius: 16rpx;
- padding: 15rpx 20rpx;
- display: flex;
- align-items: center;
- margin-bottom: 20rpx;
- }
- .pet-avatar {
- width: 80rpx;
- height: 80rpx;
- border-radius: 50%;
- margin-right: 20rpx;
- }
- .pet-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .pet-name {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 5rpx;
- }
- .pet-breed {
- font-size: 24rpx;
- color: #999;
- }
- .pet-profile-btn {
- font-size: 24rpx;
- color: #FF9800;
- border: 1px solid #FF9800;
- padding: 6rpx 20rpx;
- border-radius: 50rpx;
- background-color: #fff;
- }
- .route-info {
- margin-bottom: 25rpx;
- }
- .route-item {
- display: flex;
- align-items: flex-start;
- padding-bottom: 12rpx;
- position: relative;
- width: 100%;
- }
- .route-item:not(:last-child) {
- margin-bottom: 5rpx;
- }
- .route-item:last-child {
- padding-bottom: 0;
- margin-bottom: 0;
- }
- .route-line-vertical {
- position: absolute;
- left: 19rpx;
- top: 46rpx;
- bottom: -15rpx;
- border-left: 2rpx dashed #E0E0E0;
- width: 0;
- z-index: 0;
- }
- .icon-circle {
- width: 40rpx;
- height: 40rpx;
- border-radius: 50%;
- color: #fff;
- font-size: 22rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 20rpx;
- flex-shrink: 0;
- font-weight: bold;
- margin-top: 6rpx;
- position: relative;
- z-index: 1;
- }
- .icon-circle.service {
- background-color: #81C784;
- }
- .icon-circle.start {
- background-color: #FFB74D;
- }
- .icon-circle.end {
- background-color: #81C784;
- }
- .address-box {
- flex: 1;
- display: flex;
- flex-direction: column;
- margin-right: 20rpx;
- }
- .addr-title {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 4rpx;
- }
- .addr-desc {
- font-size: 24rpx;
- color: #999;
- line-height: 1.4;
- }
- .distance-tag {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- flex-shrink: 0;
- min-width: 80rpx;
- }
- .distance-text {
- font-size: 24rpx;
- color: #FF5722;
- margin-right: 15rpx;
- font-weight: 500;
- }
- .nav-icon-circle {
- width: 48rpx;
- height: 48rpx;
- background-color: #FFF3E0;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .nav-arrow {
- width: 24rpx;
- height: 24rpx;
- }
- .service-content {
- margin-top: -10rpx;
- font-size: 24rpx;
- color: #666;
- padding-left: 60rpx;
- }
- .content-label {
- color: #999;
- margin-right: 10rpx;
- }
- .remark-box {
- background-color: #F8F8F8;
- padding: 15rpx 20rpx;
- border-radius: 8rpx;
- font-size: 24rpx;
- color: #666;
- margin-bottom: 20rpx;
- }
- .action-btns {
- display: flex;
- flex-direction: column;
- gap: 16rpx;
- margin-top: 20rpx;
- }
- .action-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- }
- .btn {
- height: 60rpx;
- line-height: 60rpx;
- border-radius: 30rpx;
- font-size: 26rpx;
- padding: 0 30rpx;
- margin: 0;
- }
- .action-right .btn:not(:last-child) {
- margin-right: 20rpx;
- }
- .btn::after {
- border: none;
- }
- .btn.normal {
- background-color: #F8F8F8;
- color: #666;
- border: none;
- }
- .btn.primary {
- background: linear-gradient(90deg, #FF9800 0%, #FF5722 100%);
- color: #fff;
- box-shadow: 0 4rpx 12rpx rgba(255, 87, 34, 0.2);
- border: none;
- }
- .btn.normal.danger {
- background-color: #FFF2F0;
- color: #F5222D;
- }
- .pet-modal-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.4);
- z-index: 1000;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .pet-modal-content {
- width: 680rpx;
- height: 85vh;
- background-color: #fff;
- border-radius: 20rpx;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .pet-modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 30rpx;
- border-bottom: 1rpx solid #F0F0F0;
- }
- .pet-modal-title {
- font-size: 34rpx;
- font-weight: bold;
- color: #333;
- }
- .pet-modal-scroll {
- flex: 1;
- height: 0;
- padding: 30rpx;
- box-sizing: border-box;
- }
- .pet-base-info {
- display: flex;
- align-items: center;
- margin-bottom: 40rpx;
- }
- .pm-avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- margin-right: 30rpx;
- border: 2rpx solid #f5f5f5;
- }
- .pm-info-text {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .pm-name-row {
- display: flex;
- align-items: center;
- margin-bottom: 15rpx;
- }
- .pm-name {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- margin-right: 20rpx;
- }
- .pm-gender {
- display: flex;
- align-items: center;
- background-color: #E3F2FD;
- padding: 4rpx 12rpx;
- border-radius: 20rpx;
- }
- .pm-gender text {
- font-size: 22rpx;
- color: #1E88E5;
- }
- .pm-gender .gender-icon {
- font-weight: bold;
- margin-right: 4rpx;
- }
- .pm-gender.female {
- background-color: #FCE4EC;
- }
- .pm-gender.female text {
- color: #D81B60;
- }
- .pm-breed {
- font-size: 26rpx;
- color: #999;
- }
- .pm-detail-grid {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- }
- .pm-grid-item {
- background-color: #F8F8F8;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 20rpx;
- display: flex;
- flex-direction: column;
- }
- .pm-grid-item.half {
- width: 48%;
- box-sizing: border-box;
- }
- .pm-grid-item.full {
- width: 100%;
- box-sizing: border-box;
- }
- .pm-label {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 10rpx;
- }
- .pm-val {
- font-size: 28rpx;
- color: #333;
- font-weight: 500;
- }
- .pm-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 20rpx;
- margin-bottom: 40rpx;
- }
- .pm-tag {
- background-color: #FFF8EB;
- border: 2rpx solid #FFCC80;
- color: #FF9800;
- font-size: 22rpx;
- padding: 8rpx 24rpx;
- border-radius: 30rpx;
- }
- .pm-section-title {
- display: flex;
- align-items: center;
- margin-bottom: 30rpx;
- padding-top: 30rpx;
- border-top: 2rpx dashed #F0F0F0;
- }
- .pm-section-title .orange-bar {
- width: 8rpx;
- height: 32rpx;
- background-color: #FF9800;
- margin-right: 16rpx;
- border-radius: 4rpx;
- }
- .pm-section-title text {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- }
- .pm-log-list {
- display: flex;
- flex-direction: column;
- }
- .pm-log-item {
- display: flex;
- flex-direction: column;
- padding: 24rpx 0;
- border-bottom: 1rpx solid #F0F0F0;
- }
- .pm-log-item:last-child {
- border-bottom: none;
- }
- .pm-log-date {
- font-size: 24rpx;
- color: #999;
- margin-bottom: 16rpx;
- }
- .pm-log-text {
- font-size: 28rpx;
- color: #333;
- line-height: 1.6;
- margin-bottom: 20rpx;
- }
- .pm-log-recorder {
- font-size: 24rpx;
- color: #FF9800;
- align-self: flex-end;
- }
- .pm-bottom-close {
- width: 100%;
- height: 80rpx;
- line-height: 80rpx;
- background-color: #F5F5F5;
- color: #666;
- border-radius: 40rpx;
- font-size: 30rpx;
- font-weight: bold;
- margin: 0;
- }
- .pm-bottom-close::after {
- border: none;
- }
- .close-icon-btn {
- font-size: 48rpx;
- color: #999;
- line-height: 1;
- padding: 0 10rpx;
- }
- .nav-modal-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 1000;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- }
- .nav-action-sheet {
- background-color: #fff;
- width: 100%;
- border-top-left-radius: 24rpx;
- border-top-right-radius: 24rpx;
- overflow: hidden;
- padding-bottom: constant(safe-area-inset-bottom);
- padding-bottom: env(safe-area-inset-bottom);
- }
- .nav-sheet-title {
- text-align: center;
- padding: 30rpx 0;
- font-size: 13px;
- color: #999;
- border-bottom: 1rpx solid #efefef;
- }
- .nav-sheet-item {
- text-align: center;
- padding: 30rpx 0;
- font-size: 13px;
- color: #333;
- background-color: #fff;
- border-bottom: 1rpx solid #efefef;
- }
- .nav-sheet-item.cancel {
- border-bottom: none;
- color: #666;
- }
- .nav-sheet-gap {
- height: 16rpx;
- background-color: #F8F8F8;
- }
- .order-list {
- flex: 1;
- overflow-y: auto;
- width: 100%;
- padding: 0 30rpx;
- box-sizing: border-box;
- }
- .loading-text {
- text-align: center;
- font-size: 24rpx;
- color: #999;
- padding: 30rpx 0;
- }
- .pm-header-actions {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .pm-remark-btn {
- font-size: 24rpx;
- color: #fff;
- background-color: #FF9800;
- padding: 6rpx 18rpx;
- border-radius: 20rpx;
- }
- .remark-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 3000;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .remark-sheet {
- width: 600rpx;
- background-color: #fff;
- border-radius: 24rpx;
- padding: 40rpx;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .remark-sheet-header {
- width: 100%;
- text-align: center;
- margin-bottom: 30rpx;
- position: relative;
- }
- .remark-sheet-header .close-icon-btn {
- position: absolute;
- right: 0;
- top: 50%;
- transform: translateY(-50%);
- }
- .remark-sheet-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .remark-textarea {
- width: 100%;
- height: 160rpx;
- border: 1rpx solid #eee;
- border-radius: 12rpx;
- padding: 20rpx;
- font-size: 28rpx;
- color: #333;
- box-sizing: border-box;
- margin-bottom: 40rpx;
- }
- .remark-submit-btn {
- width: 100%;
- background-color: #FF5722;
- color: #fff;
- font-size: 32rpx;
- font-weight: bold;
- text-align: center;
- padding: 24rpx 0;
- border-radius: 16rpx;
- }
- .action-left {
- position: relative;
- z-index: 10;
- }
- .action-left .btn.normal {
- font-size: 26rpx;
- }
- .call-popover {
- position: absolute;
- top: calc(100% + 10rpx);
- left: 0;
- background-color: #fff;
- border-radius: 12rpx;
- box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
- z-index: 999;
- display: flex;
- flex-direction: column;
- width: 200rpx;
- }
- .call-pop-item {
- font-size: 26rpx;
- color: #333;
- text-align: center;
- padding: 24rpx 0;
- border-bottom: 1rpx solid #eee;
- }
- .call-pop-item:last-child {
- border-bottom: none;
- }
- .call-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 900;
- background: transparent;
- }
- /* 全局通用对话框样式 (复用首页思路) */
- .modal-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 5000;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .custom-modal {
- width: 600rpx;
- background-color: #fff;
- border-radius: 24rpx;
- padding: 40rpx 50rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .modal-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 30rpx;
- }
- .modal-btns {
- width: 100%;
- display: flex;
- justify-content: space-between;
- }
- .modal-btn {
- width: 45%;
- height: 80rpx;
- line-height: 80rpx;
- border-radius: 40rpx;
- font-size: 30rpx;
- font-weight: bold;
- margin: 0;
- }
- .modal-btn::after {
- border: none;
- }
- .modal-btn.cancel {
- background-color: #F5F5F5;
- color: #999;
- }
- .modal-btn.confirm {
- background: linear-gradient(90deg, #FF9800 0%, #FF5722 100%);
- color: #fff;
- box-shadow: 0 5rpx 15rpx rgba(255, 87, 34, 0.3);
- }
- .textarea-container {
- padding: 0 4rpx;
- width: 100%;
- position: relative;
- margin-bottom: 20rpx;
- }
- .reject-textarea {
- width: 100%;
- height: 240rpx;
- background-color: #F9F9F9;
- border: 1rpx solid #E0E0E0;
- border-radius: 16rpx;
- padding: 24rpx;
- padding-bottom: 60rpx;
- font-size: 28rpx;
- line-height: 1.6;
- box-sizing: border-box;
- transition: all 0.3s;
- }
- .reject-textarea:focus {
- border-color: #FF9800;
- background-color: #fff;
- }
- .char-count {
- position: absolute;
- right: 44rpx;
- bottom: 24rpx;
- font-size: 22rpx;
- color: #999;
- }
- .modal-btn.confirm.disabled {
- background: #FFD180;
- box-shadow: none;
- opacity: 0.8;
- }
- .mt-30 {
- margin-top: 30rpx;
- }
- .disabled-card {
- opacity: 0.5; /* 降低透明度以示禁用 */
- pointer-events: none; /* 禁用该卡片内背景的一切交互 */
- filter: grayscale(80%); /* 增加灰度,使视觉效果更明显 */
- }
- .disabled-card .action-row {
- pointer-events: auto; /* 允许按钮即使在置灰状态下也能点击操作 */
- }
- /* 即使使用了 pointer-events: none,外层的 @click 也会失效,为了保险我们在 JS 中也做了判断 */
- </style>
|