| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878 |
- <template>
- <view class="page-monitor">
- <!-- Header: subordinate user info -->
- <view class="monitor-header">
- <view class="header-info">
- <text class="header-user">用户 {{ userId }}</text>
- <text class="header-subtitle">AI 咨询记录</text>
- </view>
- </view>
- <!-- Loading skeleton -->
- <view v-if="loading" class="loading-msgs">
- <view class="skeleton-msg left-msg">
- <view class="skeleton-label"></view>
- <view class="skeleton-bubble w-60"></view>
- </view>
- <view class="skeleton-msg right-msg">
- <view class="skeleton-bubble w-40"></view>
- </view>
- <view class="skeleton-msg left-msg">
- <view class="skeleton-label"></view>
- <view class="skeleton-bubble w-75"></view>
- </view>
- <view class="skeleton-msg right-msg">
- <view class="skeleton-bubble w-55"></view>
- </view>
- <view class="skeleton-msg left-msg">
- <view class="skeleton-label"></view>
- <view class="skeleton-bubble w-65"></view>
- </view>
- <view class="skeleton-msg right-msg">
- <view class="skeleton-bubble w-45"></view>
- </view>
- </view>
- <template v-else>
- <!-- Chat display area -->
- <scroll-view class="chat-box" scroll-y :scroll-top="scrollTop">
- <view v-for="(msg, idx) in messages" :key="msg.id || idx" class="msg-wrapper">
- <!-- System message: centered, italic, no bubble -->
- <view v-if="msg.senderType === 'system'" class="msg-system">
- <text class="msg-system-text">{{ msg.content }}</text>
- </view>
- <!-- Practitioner message: green bubble left-aligned with label -->
- <view v-else-if="msg.senderType === 'practitioner'" class="msg-row practitioner">
- <view class="msg-label label-practitioner">👤 能量师</view>
- <view class="msg-bubble bubble-practitioner">
- <text>{{ msg.content }}</text>
- </view>
- </view>
- <!-- User message: dark blue bubble right-aligned -->
- <view v-else-if="msg.senderType === 'user'" class="msg-row user">
- <view class="msg-bubble bubble-user">
- <text>{{ msg.content }}</text>
- </view>
- </view>
- <!-- AI message (default): gray bubble left-aligned with label -->
- <view v-else class="msg-row ai">
- <view class="msg-label label-ai">AI</view>
- <view class="msg-bubble bubble-ai">
- <text>{{ msg.content }}</text>
- </view>
- </view>
- </view>
- <!-- Empty state -->
- <view v-if="messages.length === 0" class="empty-chat">
- <text class="empty-chat-text">暂无聊天记录</text>
- </view>
- </scroll-view>
- <!-- Negotiation logs -->
- <view v-if="negotiationLogs.length > 0" class="negotiation-section">
- <view class="negotiation-title">📋 协商记录</view>
- <view v-for="log in negotiationLogs" :key="log.id" class="negotiation-entry">
- <view class="negotiation-entry-header">
- <text class="negotiation-entry-action">{{ getActionLabel(log.action) }}</text>
- <text class="negotiation-entry-time">{{ formatTime(log.createdAt) }}</text>
- </view>
- <view v-if="log.oldPrice != null || log.newPrice != null" class="negotiation-entry-price">
- <text class="price-old" v-if="log.oldPrice">¥{{ log.oldPrice }}</text>
- <text class="price-arrow" v-if="log.oldPrice != null && log.newPrice != null"> → </text>
- <text class="price-new" v-if="log.newPrice">¥{{ log.newPrice }}</text>
- </view>
- <text v-if="log.message" class="negotiation-entry-msg">{{ log.message }}</text>
- </view>
- </view>
- <!-- Bottom action area -->
- <view class="bottom-bar">
- <!-- Proposal: status is "accepted" and not yet submitted -->
- <template v-if="requestStatus === 'accepted' && !submitted">
- <view class="proposal-form-title">📋 提交方案</view>
- <view class="proposal-price-row">
- <text class="proposal-label">方案价格</text>
- <input
- class="proposal-input proposal-price-input"
- v-model="proposalPrice"
- type="digit"
- placeholder="输入方案价格..."
- />
- <text class="proposal-unit">元</text>
- </view>
- <textarea
- class="proposal-textarea"
- v-model="proposalMessage"
- placeholder="描述方案内容..."
- />
- <button
- class="action-btn proposal-submit-btn"
- @click="submitProposal"
- :disabled="submitting"
- >
- {{ submitting ? '提交中...' : '提交方案' }}
- </button>
- </template>
- <!-- Proposal submitted or negotiating -->
- <template v-else-if="submitted || requestStatus === 'negotiating'">
- <view class="status-bar-submitted">
- <text class="status-icon">✅</text>
- <text class="status-text">方案已提交,等待用户回应</text>
- </view>
- </template>
- <!-- Pending payment: waiting for user to pay -->
- <template v-else-if="requestStatus === 'pending_payment'">
- <view class="status-bar-submitted">
- <text class="status-icon">⏳</text>
- <text class="status-text">等待用户支付</text>
- </view>
- </template>
- <!-- Paid: delivery form for practitioners -->
- <template v-else-if="requestStatus === 'paid'">
- <template v-if="deliverySubmitted">
- <view class="status-bar-delivered">
- <text class="status-icon">✅</text>
- <text class="status-text">方案已交付</text>
- </view>
- </template>
- <template v-else>
- <view class="delivery-form-title">📦 交付方案</view>
- <textarea
- class="delivery-textarea"
- v-model="deliveryContent"
- placeholder="输入方案交付内容..."
- />
- <button
- class="action-btn delivery-submit-btn"
- @click="submitDelivery"
- :disabled="delivering || !deliveryContent.trim()"
- >
- {{ delivering ? '提交中...' : '提交交付' }}
- </button>
- </template>
- </template>
- <!-- Completed: show completed status -->
- <template v-else-if="requestStatus === 'completed'">
- <view class="status-bar-delivered">
- <text class="status-icon">✅</text>
- <text class="status-text">已完成</text>
- </view>
- </template>
- <!-- Default: existing intervention UI -->
- <template v-else>
- <button
- v-if="!interventionMode"
- class="action-btn intervene-btn"
- :disabled="intervening"
- @click="startIntervention"
- >
- {{ intervening ? '介入中...' : '介入会话' }}
- </button>
- <template v-else>
- <view class="input-row">
- <input
- class="chat-input"
- v-model="chatInput"
- placeholder="输入消息..."
- @confirm="sendMessage"
- :disabled="sending"
- />
- <button
- class="action-btn send-btn"
- @click="sendMessage"
- :disabled="sending || !chatInput.trim()"
- >
- {{ sending ? '发送中...' : '发送' }}
- </button>
- </view>
- <button class="action-btn exit-btn" @click="confirmExit">退出介入</button>
- </template>
- </template>
- </view>
- </template>
- </view>
- </template>
- <script>
- // Page lifecycle for WeChat share compatibility
- export default {}
- </script>
- <script setup>
- import { ref, computed, nextTick } from 'vue'
- import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
- import { interventionApi, proposalApi, deliveryApi } from '@/utils/api'
- // ── Query params ──
- const chartRecordId = ref('')
- const userId = ref('')
- const requestId = ref('')
- const requestStatus = ref('')
- const practitionerId = ref('')
- // ── State ──
- const loading = ref(true)
- const messages = ref([])
- const scrollTop = ref(0)
- const interventionMode = ref(false)
- const interventionId = ref(null)
- const intervening = ref(false)
- const sending = ref(false)
- const chatInput = ref('')
- // ── Proposal state ──
- const proposalPrice = ref('')
- const proposalMessage = ref('')
- const submitting = ref(false)
- const submitted = ref(false)
- const negotiationLogs = ref([])
- // ── Delivery state ──
- const deliveryContent = ref('')
- const deliverySubmitted = ref(false)
- const delivering = ref(false)
- const deliveryData = ref(null)
- // ── Lifecycle ──
- onLoad((options) => {
- chartRecordId.value = options.chartRecordId || ''
- userId.value = options.userId || ''
- requestId.value = options.requestId || ''
- requestStatus.value = options.status || ''
- practitionerId.value = options.practitionerId || ''
- fetchHistory()
- // Fetch negotiation logs if status indicates ongoing negotiation
- if (requestStatus.value === 'negotiating' || requestStatus.value === 'accepted') {
- fetchLogs()
- }
- // Fetch delivery data if status is paid or completed
- if (requestStatus.value === 'paid' || requestStatus.value === 'completed') {
- loadDelivery()
- fetchLogs()
- }
- })
- onPullDownRefresh(async () => {
- await fetchHistory()
- uni.stopPullDownRefresh()
- })
- // ── Data fetching ──
- async function fetchHistory() {
- if (!chartRecordId.value) {
- loading.value = false
- return
- }
- loading.value = true
- try {
- const data = await interventionApi.history(chartRecordId.value)
- // Sort by timestamp or id for chronological order
- const msgs = data || []
- msgs.sort((a, b) => (a.createdAt || a.id || 0) - (b.createdAt || b.id || 0))
- messages.value = msgs
- // Scroll to bottom after rendering
- nextTick(() => {
- scrollTop.value = 99999
- })
- } catch (e) {
- uni.showToast({ title: '加载聊天记录失败', icon: 'none' })
- } finally {
- loading.value = false
- }
- }
- // ── Intervention actions ──
- async function startIntervention() {
- intervening.value = true
- try {
- const result = await interventionApi.start(chartRecordId.value)
- interventionId.value = result.interventionId || result.id || result
- interventionMode.value = true
- uni.showToast({ title: '已介入会话', icon: 'success' })
- // Append a system message to indicate intervention started
- messages.value.push({
- id: Date.now(),
- senderType: 'system',
- content: '能量师已介入会话'
- })
- nextTick(() => {
- scrollTop.value = 99999
- })
- } catch (e) {
- uni.showToast({ title: e.message || '介入失败', icon: 'none' })
- } finally {
- intervening.value = false
- }
- }
- async function sendMessage() {
- const content = chatInput.value.trim()
- if (!content || sending.value) return
- // Optimistically add practitioner message to local list
- const localMsg = {
- id: Date.now(),
- senderType: 'practitioner',
- content,
- createdAt: Date.now()
- }
- messages.value.push(localMsg)
- chatInput.value = ''
- sending.value = true
- try {
- await interventionApi.send({
- chartRecordId: chartRecordId.value,
- content
- })
- // Scroll to bottom
- nextTick(() => {
- scrollTop.value = 99999
- })
- } catch (e) {
- // Remove the optimistically added message on failure
- messages.value = messages.value.filter(m => m.id !== localMsg.id)
- uni.showToast({ title: e.message || '发送失败', icon: 'none' })
- } finally {
- sending.value = false
- }
- }
- function confirmExit() {
- uni.showModal({
- title: '退出介入',
- content: '确定要退出当前会话介入吗?',
- success: async (res) => {
- if (res.confirm) {
- try {
- await interventionApi.end(interventionId.value)
- interventionMode.value = false
- interventionId.value = null
- uni.showToast({ title: '已退出介入', icon: 'success' })
- // Append system message
- messages.value.push({
- id: Date.now(),
- senderType: 'system',
- content: '能量师已退出介入'
- })
- nextTick(() => {
- scrollTop.value = 99999
- })
- } catch (e) {
- uni.showToast({ title: e.message || '退出失败', icon: 'none' })
- }
- }
- }
- })
- }
- // ── Proposal actions ──
- async function fetchLogs() {
- if (!requestId.value) return
- try {
- const logs = await proposalApi.logs(requestId.value)
- negotiationLogs.value = logs || []
- } catch (e) {
- console.error('获取协商记录失败', e)
- }
- }
- async function submitProposal() {
- const price = Number(proposalPrice.value)
- if (!price || price <= 0) {
- uni.showToast({ title: '请输入有效的方案价格', icon: 'none' })
- return
- }
- if (!proposalMessage.value.trim()) {
- uni.showToast({ title: '请描述方案内容', icon: 'none' })
- return
- }
- submitting.value = true
- try {
- await proposalApi.create({
- requestId: requestId.value,
- price,
- message: proposalMessage.value.trim()
- })
- submitted.value = true
- uni.showToast({ title: '方案已提交', icon: 'success' })
- } catch (e) {
- uni.showToast({ title: e.message || '提交失败', icon: 'none' })
- } finally {
- submitting.value = false
- }
- }
- function getActionLabel(action) {
- const labels = {
- create: '出价',
- counter: '还价',
- accept: '接受',
- reject: '拒绝'
- }
- return labels[action] || action
- }
- function formatTime(timestamp) {
- if (!timestamp) return ''
- const d = new Date(timestamp)
- const pad = (n) => String(n).padStart(2, '0')
- return `${d.getMonth() + 1}/${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
- }
- // ── Delivery actions ──
- async function loadDelivery() {
- if (!requestId.value) return
- try {
- const data = await deliveryApi.list(requestId.value)
- if (data && data.length > 0) {
- deliveryData.value = data[0]
- deliveryContent.value = data[0].textContent || ''
- deliverySubmitted.value = true
- }
- } catch (e) {
- // Delivery may not exist yet — that's fine
- }
- }
- async function submitDelivery() {
- const content = deliveryContent.value.trim()
- if (!content || delivering.value) return
- delivering.value = true
- try {
- await deliveryApi.create({
- planRequestId: requestId.value,
- textContent: content
- })
- deliverySubmitted.value = true
- uni.showToast({ title: '交付成功', icon: 'success' })
- } catch (e) {
- uni.showToast({ title: e.message || '交付失败', icon: 'none' })
- } finally {
- delivering.value = false
- }
- }
- </script>
- <style scoped lang="scss">
- .page-monitor {
- display: flex;
- flex-direction: column;
- height: 100vh;
- overflow: hidden;
- background: linear-gradient(180deg, #0c0a1a 0%, #1a1232 100%);
- }
- /* ── Header ── */
- .monitor-header {
- flex-shrink: 0;
- padding: 16px 16px 12px;
- }
- .header-info {
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .header-user {
- font-size: 17px;
- font-weight: 700;
- color: rgba(255, 255, 255, 0.9);
- }
- .header-subtitle {
- font-size: 12px;
- color: rgba(255, 255, 255, 0.4);
- }
- /* ── Loading Skeleton ── */
- .loading-msgs {
- flex: 1;
- overflow-y: auto;
- padding: 0 16px;
- }
- .skeleton-msg {
- display: flex;
- flex-direction: column;
- margin-bottom: 18px;
- }
- .skeleton-msg.right-msg {
- align-items: flex-end;
- }
- .skeleton-label {
- width: 32px;
- height: 12px;
- background: rgba(255, 255, 255, 0.06);
- border-radius: 4px;
- margin-bottom: 6px;
- animation: pulse 1.5s ease-in-out infinite;
- }
- .skeleton-bubble {
- height: 40px;
- background: rgba(255, 255, 255, 0.04);
- border-radius: 12px;
- animation: pulse 1.5s ease-in-out infinite;
- }
- .w-40 { width: 40%; }
- .w-45 { width: 45%; }
- .w-55 { width: 55%; }
- .w-60 { width: 60%; }
- .w-65 { width: 65%; }
- .w-75 { width: 75%; }
- @keyframes pulse {
- 0%, 100% { opacity: 0.3; }
- 50% { opacity: 0.6; }
- }
- /* ── Chat Box ── */
- .chat-box {
- flex: 1;
- overflow-y: auto;
- margin: 0 16px;
- background: rgba(255, 255, 255, 0.02);
- border-radius: 16px;
- padding: 14px;
- }
- /* ── Message Row ── */
- .msg-wrapper {
- margin-bottom: 16px;
- }
- /* System message */
- .msg-system {
- display: flex;
- justify-content: center;
- padding: 6px 0;
- }
- .msg-system-text {
- font-size: 12px;
- color: rgba(255, 255, 255, 0.4);
- font-style: italic;
- text-align: center;
- }
- /* Regular message (left by default) */
- .msg-row {
- display: flex;
- flex-direction: column;
- max-width: 85%;
- }
- .msg-row.user {
- align-items: flex-end;
- margin-left: auto;
- }
- .msg-row.practitioner,
- .msg-row.ai {
- align-items: flex-start;
- margin-right: auto;
- }
- /* Message label */
- .msg-label {
- font-size: 11px;
- margin-bottom: 4px;
- padding-left: 4px;
- }
- .label-ai {
- color: rgba(255, 255, 255, 0.35);
- }
- .label-practitioner {
- color: rgba(16, 185, 129, 0.7);
- }
- /* Message bubble */
- .msg-bubble {
- padding: 10px 14px;
- border-radius: 14px;
- font-size: 14px;
- line-height: 1.5;
- word-break: break-word;
- }
- /* AI bubble: gray, left-aligned */
- .bubble-ai {
- background: rgba(255, 255, 255, 0.08);
- color: rgba(255, 255, 255, 0.85);
- }
- /* User bubble: dark blue, right-aligned */
- .bubble-user {
- background: #1E3A5F;
- color: rgba(255, 255, 255, 0.9);
- }
- /* Practitioner bubble: green, left-aligned */
- .bubble-practitioner {
- background: rgba(16, 185, 129, 0.15);
- border: 1px solid rgba(16, 185, 129, 0.3);
- color: rgba(255, 255, 255, 0.9);
- }
- /* ── Empty state ── */
- .empty-chat {
- display: flex;
- justify-content: center;
- padding: 60px 20px;
- }
- .empty-chat-text {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.3);
- }
- /* ── Bottom bar ── */
- .bottom-bar {
- flex-shrink: 0;
- padding: 12px 16px calc(env(safe-area-inset-bottom) + 12px);
- border-top: 1px solid rgba(255, 255, 255, 0.06);
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- /* Action buttons base */
- .action-btn {
- height: 44px;
- border: none;
- border-radius: 12px;
- font-size: 15px;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- line-height: 1;
- }
- /* Intervene button: green gradient */
- .intervene-btn {
- width: 100%;
- background: linear-gradient(135deg, #10b981, #059669);
- color: #fff;
- }
- .intervene-btn[disabled] {
- opacity: 0.5;
- }
- /* Send button */
- .send-btn {
- flex-shrink: 0;
- padding: 0 24px;
- background: linear-gradient(135deg, #10b981, #059669);
- color: #fff;
- }
- .send-btn[disabled] {
- opacity: 0.4;
- }
- /* Exit button: red outline */
- .exit-btn {
- width: 100%;
- background: transparent;
- border: 1px solid #ef4444;
- color: #ef4444;
- }
- /* Input row */
- .input-row {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .chat-input {
- flex: 1;
- height: 42px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 10px;
- padding: 0 14px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- }
- /* ── Negotiation Logs Section ── */
- .negotiation-section {
- flex-shrink: 0;
- margin: 0 16px 8px;
- padding: 14px;
- background: rgba(255, 255, 255, 0.02);
- border-radius: 16px;
- max-height: 200px;
- overflow-y: auto;
- }
- .negotiation-title {
- font-size: 14px;
- font-weight: 600;
- color: rgba(255, 255, 255, 0.8);
- margin-bottom: 10px;
- }
- .negotiation-entry {
- padding: 10px 0;
- border-bottom: 1px solid rgba(255, 255, 255, 0.04);
- }
- .negotiation-entry:last-child {
- border-bottom: none;
- }
- .negotiation-entry-header {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 4px;
- }
- .negotiation-entry-action {
- font-size: 13px;
- font-weight: 600;
- color: rgba(255, 255, 255, 0.7);
- }
- .negotiation-entry-time {
- font-size: 11px;
- color: rgba(255, 255, 255, 0.3);
- }
- .negotiation-entry-price {
- margin-bottom: 4px;
- }
- .price-old {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.4);
- text-decoration: line-through;
- }
- .price-arrow {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.3);
- }
- .price-new {
- font-size: 15px;
- font-weight: 700;
- color: #fbbf24;
- }
- .negotiation-entry-msg {
- font-size: 12px;
- color: rgba(255, 255, 255, 0.5);
- display: block;
- }
- /* ── Proposal Form ── */
- .proposal-form-title {
- font-size: 16px;
- font-weight: 700;
- color: rgba(255, 255, 255, 0.9);
- margin-bottom: 12px;
- text-align: center;
- }
- .proposal-price-row {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 10px;
- }
- .proposal-label {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- flex-shrink: 0;
- }
- .proposal-input {
- flex: 1;
- height: 42px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 10px;
- padding: 0 14px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- }
- .proposal-price-input {
- text-align: right;
- }
- .proposal-unit {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.5);
- flex-shrink: 0;
- }
- .proposal-textarea {
- width: 100%;
- min-height: 80px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 10px;
- padding: 10px 14px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- margin-bottom: 10px;
- box-sizing: border-box;
- }
- .proposal-submit-btn {
- width: 100%;
- background: linear-gradient(135deg, #a78bfa, #7c3aed);
- color: #fff;
- }
- .proposal-submit-btn[disabled] {
- opacity: 0.5;
- }
- /* ── Submitted Status Bar ── */
- .status-bar-submitted {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 12px 16px;
- background: rgba(16, 185, 129, 0.1);
- border-radius: 12px;
- }
- .status-icon {
- font-size: 16px;
- }
- .status-text {
- font-size: 14px;
- color: #10b981;
- font-weight: 500;
- }
- /* ── Delivered Status Bar ── */
- .status-bar-delivered {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 12px 16px;
- background: rgba(16, 185, 129, 0.1);
- border-radius: 12px;
- }
- /* ── Delivery Form ── */
- .delivery-form-title {
- font-size: 16px;
- font-weight: 700;
- color: rgba(255, 255, 255, 0.9);
- margin-bottom: 12px;
- text-align: center;
- }
- .delivery-textarea {
- width: 100%;
- min-height: 100px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 10px;
- padding: 10px 14px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.85);
- background: rgba(255, 255, 255, 0.04);
- margin-bottom: 10px;
- box-sizing: border-box;
- }
- .delivery-submit-btn {
- width: 100%;
- background: linear-gradient(135deg, #f59e0b, #d97706);
- color: #fff;
- }
- .delivery-submit-btn[disabled] {
- opacity: 0.5;
- }
- </style>
|