Просмотр исходного кода

feat: admin-h5 全局文本可选 + 关键字段一键复制

- App.vue: page/text 全局启用 user-select: text,支持手机长按选中
- utils/index.js: 新增 copyText() 工具函数,封装 uni.setClipboardData
- 订单/用户/设备/分账页面关键字段加复制按钮(订单号、手机号、设备编号、交易流水号等)

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 1 месяц назад
Родитель
Сommit
45873f8b5d

+ 8 - 0
admin-h5/src/App.vue

@@ -31,6 +31,14 @@ page {
   background-color: $uni-page-bg-color;
   background-color: $uni-page-bg-color;
   -webkit-font-smoothing: antialiased;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: text;
+  user-select: text;
+}
+
+/* 全局启用文本选择,uni-app H5 模式下默认不可选中 */
+text {
+  -webkit-user-select: text;
+  user-select: text;
 }
 }
 
 
 /* ===== Global Keyframes ===== */
 /* ===== Global Keyframes ===== */

+ 24 - 3
admin-h5/src/pages/device/detail.vue

@@ -6,7 +6,10 @@
       <view class="device-main-info">
       <view class="device-main-info">
         <view class="device-name-section">
         <view class="device-name-section">
           <text class="device-name">{{ deviceDetail.deviceName || '未知设备' }}</text>
           <text class="device-name">{{ deviceDetail.deviceName || '未知设备' }}</text>
-          <text class="device-id">设备编号: {{ deviceDetail.shortId || '无' }}</text>
+          <view class="device-id-row">
+            <text class="device-id">设备编号: {{ deviceDetail.shortId || '无' }}</text>
+            <AppIcon v-if="deviceDetail.shortId" name="copy" :size="26" color="#C6171E" class="copy-icon" @click.stop="copyText(deviceDetail.shortId)" />
+          </view>
         </view>
         </view>
         <view class="device-status" :style="getDeviceStatusStyle(deviceDetail.state)">
         <view class="device-status" :style="getDeviceStatusStyle(deviceDetail.state)">
           <text class="status-dot"></text>
           <text class="status-dot"></text>
@@ -20,7 +23,10 @@
       <view class="info-list">
       <view class="info-list">
         <view class="info-item">
         <view class="info-item">
           <text class="item-label">产品Key</text>
           <text class="item-label">产品Key</text>
-          <text class="item-value">{{ deviceDetail.productKey || '无' }}</text>
+          <view class="item-value-row">
+            <text class="item-value">{{ deviceDetail.productKey || '无' }}</text>
+            <AppIcon v-if="deviceDetail.productKey" name="copy" :size="26" color="#C6171E" class="copy-icon" @click.stop="copyText(deviceDetail.productKey)" />
+          </view>
         </view>
         </view>
         <view class="info-item">
         <view class="info-item">
           <text class="item-label">工位编号</text>
           <text class="item-label">工位编号</text>
@@ -109,7 +115,7 @@
 <script setup>
 <script setup>
 import { ref, onMounted, computed } from 'vue'
 import { ref, onMounted, computed } from 'vue'
 import { getDeviceDetail, stopDevice } from '../../api/device.js'
 import { getDeviceDetail, stopDevice } from '../../api/device.js'
-import { formatTime, showToast, fmtDictName, getDictColor } from '../../utils/index.js'
+import { formatTime, showToast, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 
 
 const deviceId = ref('')
 const deviceId = ref('')
@@ -246,6 +252,11 @@ onMounted(async () => {
   margin-bottom: 10rpx;
   margin-bottom: 10rpx;
 }
 }
 
 
+.device-id-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
 .device-id {
 .device-id {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #999999;
   color: #999999;
@@ -314,6 +325,16 @@ onMounted(async () => {
   text-align: right;
   text-align: right;
 }
 }
 
 
+.item-value-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.copy-icon {
+  flex-shrink: 0;
+}
+
 .item-value-danger {
 .item-value-danger {
   color: #F44336;
   color: #F44336;
 }
 }

+ 13 - 2
admin-h5/src/pages/device/list.vue

@@ -47,7 +47,10 @@
           <view class="device-info">
           <view class="device-info">
             <text class="device-name">{{ device.name }}</text>
             <text class="device-name">{{ device.name }}</text>
             <view class="device-meta">
             <view class="device-meta">
-              <text class="device-id">ID: {{ device.shortId }}</text>
+              <view class="device-id-row">
+                <text class="device-id">ID: {{ device.shortId }}</text>
+                <AppIcon v-if="device.shortId" name="copy" :size="24" color="#C6171E" class="copy-icon" @click.stop="copyText(device.shortId)" />
+              </view>
               <view class="device-status" :style="getDeviceStatusStyle(getDeviceStatusValue(device))">
               <view class="device-status" :style="getDeviceStatusStyle(getDeviceStatusValue(device))">
                 <text class="status-dot"></text>
                 <text class="status-dot"></text>
                 <text>{{ getDeviceStatusText(getDeviceStatusValue(device)) }}</text>
                 <text>{{ getDeviceStatusText(getDeviceStatusValue(device)) }}</text>
@@ -132,7 +135,7 @@ import { ref, onMounted, computed } from 'vue'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { getDeviceList, stopDevice, getDeviceConfigList, batchModifyDeviceConfig } from '../../api/device.js'
 import { getDeviceList, stopDevice, getDeviceConfigList, batchModifyDeviceConfig } from '../../api/device.js'
 import { getStationList } from '../../api/station.js'
 import { getStationList } from '../../api/station.js'
-import { showToast, storage, fmtDictName, getDictColor } from '../../utils/index.js'
+import { showToast, storage, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 
 
 const deviceList = ref([])
 const deviceList = ref([])
@@ -460,10 +463,18 @@ const isDeviceOnline = (status) => {
   flex-wrap: wrap;
   flex-wrap: wrap;
 }
 }
 
 
+.device-id-row {
+  display: flex;
+  align-items: center;
+  gap: 6rpx;
+}
 .device-id {
 .device-id {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #999999;
   color: #999999;
 }
 }
+.copy-icon {
+  flex-shrink: 0;
+}
 
 
 .device-status {
 .device-status {
   display: flex;
   display: flex;

+ 13 - 2
admin-h5/src/pages/finance/split-record.vue

@@ -54,7 +54,10 @@
             </view>
             </view>
             <view class="info-row">
             <view class="info-row">
               <text class="info-label">分账交易流水号</text>
               <text class="info-label">分账交易流水号</text>
-              <text class="info-value trade-no">{{ item.tradeNo || '-' }}</text>
+              <view class="info-value-row">
+                <text class="info-value trade-no">{{ item.tradeNo || '-' }}</text>
+                <AppIcon v-if="item.tradeNo" name="copy" :size="26" color="#C6171E" class="copy-icon" @click.stop="copyText(item.tradeNo)" />
+              </view>
             </view>
             </view>
             <view class="info-row">
             <view class="info-row">
               <text class="info-label">创建时间</text>
               <text class="info-label">创建时间</text>
@@ -96,7 +99,7 @@
 import { ref, onMounted } from 'vue'
 import { ref, onMounted } from 'vue'
 import { getSplitRecords } from '../../api/finance.js'
 import { getSplitRecords } from '../../api/finance.js'
 import { getStationList } from '../../api/station.js'
 import { getStationList } from '../../api/station.js'
-import { formatTime, formatAmount, fmtDictName, getDictColor, showToast } from '../../utils/index.js'
+import { formatTime, formatAmount, fmtDictName, getDictColor, showToast, copyText } from '../../utils/index.js'
 import { loadDicts } from '../../utils/dict.js'
 import { loadDicts } from '../../utils/dict.js'
 
 
 const list = ref([])
 const list = ref([])
@@ -329,6 +332,11 @@ onMounted(async () => {
   font-size: 26rpx;
   font-size: 26rpx;
   color: #999999;
   color: #999999;
 }
 }
+.info-value-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
 .info-value {
 .info-value {
   font-size: 26rpx;
   font-size: 26rpx;
   color: #1A1A1A;
   color: #1A1A1A;
@@ -339,6 +347,9 @@ onMounted(async () => {
   font-size: 24rpx;
   font-size: 24rpx;
   word-break: break-all;
   word-break: break-all;
 }
 }
+.copy-icon {
+  flex-shrink: 0;
+}
 
 
 .load-more {
 .load-more {
   display: flex;
   display: flex;

+ 31 - 4
admin-h5/src/pages/order/detail.vue

@@ -6,7 +6,10 @@
     <view class="status-card">
     <view class="status-card">
       <view class="order-number">
       <view class="order-number">
         <text class="number-label">订单号</text>
         <text class="number-label">订单号</text>
-        <text class="number-value">{{ orderDetail.orderId }}</text>
+        <view class="number-value-row">
+          <text class="number-value">{{ orderDetail.orderId }}</text>
+          <AppIcon name="copy" :size="28" color="#C6171E" class="copy-icon" @click.stop="copyText(orderDetail.orderId)" />
+        </view>
       </view>
       </view>
       <view class="status-row">
       <view class="status-row">
         <text
         <text
@@ -29,11 +32,17 @@
         </view>
         </view>
         <view class="info-item">
         <view class="info-item">
           <text class="item-label">用户手机号</text>
           <text class="item-label">用户手机号</text>
-          <text class="item-value">{{ orderDetail.mobilePhone || '匿名用户' }}</text>
+          <view class="item-value-row">
+            <text class="item-value">{{ orderDetail.mobilePhone || '匿名用户' }}</text>
+            <AppIcon v-if="orderDetail.mobilePhone" name="copy" :size="28" color="#C6171E" class="copy-icon" @click.stop="copyText(orderDetail.mobilePhone)" />
+          </view>
         </view>
         </view>
         <view class="info-item">
         <view class="info-item">
           <text class="item-label">设备编号</text>
           <text class="item-label">设备编号</text>
-          <text class="item-value">{{ orderDetail.shortId || '未知设备' }}</text>
+          <view class="item-value-row">
+            <text class="item-value">{{ orderDetail.shortId || '未知设备' }}</text>
+            <AppIcon v-if="orderDetail.shortId" name="copy" :size="28" color="#C6171E" class="copy-icon" @click.stop="copyText(orderDetail.shortId)" />
+          </view>
         </view>
         </view>
         <view class="info-item">
         <view class="info-item">
           <text class="item-label">消费站点</text>
           <text class="item-label">消费站点</text>
@@ -132,7 +141,7 @@
 <script setup>
 <script setup>
 import { ref, onMounted, computed } from 'vue'
 import { ref, onMounted, computed } from 'vue'
 import { getOrderDetail, handleRefund as refundApi } from '../../api/order.js'
 import { getOrderDetail, handleRefund as refundApi } from '../../api/order.js'
-import { formatTime, showToast, formatAmount, fmtDictName, getDictColor } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 
 
 const orderId = ref('')
 const orderId = ref('')
@@ -392,6 +401,24 @@ onMounted(async () => {
   text-align: right;
   text-align: right;
 }
 }
 
 
+.item-value-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.number-value-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+  flex: 1;
+  justify-content: flex-end;
+}
+
+.copy-icon {
+  flex-shrink: 0;
+}
+
 /* 金额列表 */
 /* 金额列表 */
 .amount-list {
 .amount-list {
   padding: 20rpx 30rpx 24rpx;
   padding: 20rpx 30rpx 24rpx;

+ 16 - 9
admin-h5/src/pages/order/list.vue

@@ -68,11 +68,17 @@
         <view class="order-core-info">
         <view class="order-core-info">
           <view class="info-item">
           <view class="info-item">
             <text class="info-label">用户手机号</text>
             <text class="info-label">用户手机号</text>
-            <text class="info-value">{{ order.mobilePhone || '匿名用户' }}</text>
+            <view class="info-value-row">
+              <text class="info-value">{{ order.mobilePhone || '匿名用户' }}</text>
+              <AppIcon v-if="order.mobilePhone" name="copy" :size="28" color="#C6171E" class="copy-icon" @click.stop="copyText(order.mobilePhone)" />
+            </view>
           </view>
           </view>
           <view class="info-item">
           <view class="info-item">
             <text class="info-label">设备编号</text>
             <text class="info-label">设备编号</text>
-            <text class="info-value">{{ order.shortId || '未知设备' }}</text>
+            <view class="info-value-row">
+              <text class="info-value">{{ order.shortId || '未知设备' }}</text>
+              <AppIcon v-if="order.shortId" name="copy" :size="28" color="#C6171E" class="copy-icon" @click.stop="copyText(order.shortId)" />
+            </view>
           </view>
           </view>
         </view>
         </view>
         
         
@@ -154,7 +160,7 @@ import CustomTabBar from '../../components/CustomTabBar.vue'
 import { ref, onMounted, reactive } from 'vue'
 import { ref, onMounted, reactive } from 'vue'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { getOrderList, handleRefund as refundApi } from '../../api/order.js'
 import { getOrderList, handleRefund as refundApi } from '../../api/order.js'
-import { formatTime, showToast, formatAmount, fmtDictName, getDictColor } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 
 
 const orderList = ref([])
 const orderList = ref([])
@@ -278,12 +284,7 @@ const handleSearch = () => {
 
 
 
 
 const copyOrderId = (orderId) => {
 const copyOrderId = (orderId) => {
-  uni.setClipboardData({
-    data: String(orderId),
-    success: () => {
-      showToast('订单号已复制', 'success')
-    }
-  })
+  copyText(orderId)
 }
 }
 
 
 const viewOrderDetail = (orderId) => {
 const viewOrderDetail = (orderId) => {
@@ -550,6 +551,12 @@ const onPullDownRefresh = () => {
   word-break: break-all;
   word-break: break-all;
 }
 }
 
 
+.info-value-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
 /* 金额信息 */
 /* 金额信息 */
 .order-amount-info {
 .order-amount-info {
   display: flex;
   display: flex;

+ 23 - 6
admin-h5/src/pages/user/list.vue

@@ -15,8 +15,14 @@
       <view class="user-item" v-for="(item, index) in list" :key="index">
       <view class="user-item" v-for="(item, index) in list" :key="index">
         <view class="item-header">
         <view class="item-header">
           <view class="item-left">
           <view class="item-left">
-            <text class="item-phone">{{ item.mobilePhone || '-' }}</text>
-            <text class="item-id">UID: {{ item.userId || '-' }}</text>
+            <view class="item-phone-row">
+              <text class="item-phone">{{ item.mobilePhone || '-' }}</text>
+              <AppIcon v-if="item.mobilePhone" name="copy" :size="26" color="#C6171E" class="copy-icon" @click.stop="copyText(item.mobilePhone)" />
+            </view>
+            <view class="item-id-row">
+              <text class="item-id">UID: {{ item.userId || '-' }}</text>
+              <AppIcon v-if="item.userId" name="copy" :size="26" color="#C6171E" class="copy-icon" @click.stop="copyText(item.userId)" />
+            </view>
           </view>
           </view>
           <view class="item-header-right">
           <view class="item-header-right">
             <text class="status-tag" :style="getStatusStyle(item.status)">{{ fmtDictName('User.status', item.status) }}</text>
             <text class="status-tag" :style="getStatusStyle(item.status)">{{ fmtDictName('User.status', item.status) }}</text>
@@ -124,7 +130,7 @@ import { ref, onMounted } from 'vue'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { getAppUserList } from '../../api/user.js'
 import { getAppUserList } from '../../api/user.js'
 import { applyRefund } from '../../api/finance.js'
 import { applyRefund } from '../../api/finance.js'
-import { formatTime, showToast, formatAmount, fmtDictName, getDictColor } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import { loadDicts } from '../../utils/dict.js'
 import { loadDicts } from '../../utils/dict.js'
 
 
 const list = ref([])
 const list = ref([])
@@ -328,17 +334,28 @@ onMounted(async () => {
   border-bottom: 1rpx solid #F0F0F0;
   border-bottom: 1rpx solid #F0F0F0;
   margin-bottom: 16rpx;
   margin-bottom: 16rpx;
 }
 }
+.item-phone-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
 .item-phone {
 .item-phone {
   font-size: 30rpx;
   font-size: 30rpx;
   font-weight: 600;
   font-weight: 600;
   color: #1A1A1A;
   color: #1A1A1A;
-  display: block;
+}
+.item-id-row {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+  margin-top: 4rpx;
 }
 }
 .item-id {
 .item-id {
   font-size: 22rpx;
   font-size: 22rpx;
   color: #999999;
   color: #999999;
-  margin-top: 4rpx;
-  display: block;
+}
+.copy-icon {
+  flex-shrink: 0;
 }
 }
 .item-header-right {
 .item-header-right {
   display: flex;
   display: flex;

+ 18 - 0
admin-h5/src/utils/index.js

@@ -28,3 +28,21 @@ export const formatAmount = (amount, isCent = true) => {
   const yuan = isCent ? numAmount / 100 : numAmount
   const yuan = isCent ? numAmount / 100 : numAmount
   return yuan.toFixed(2)
   return yuan.toFixed(2)
 }
 }
+
+/**
+ * 复制文本到剪贴板
+ * @param {string} text - 要复制的文本
+ */
+export const copyText = async (text) => {
+  if (!text && text !== 0) {
+    showToast('内容为空,无法复制')
+    return
+  }
+  try {
+    await uni.setClipboardData({ data: String(text) })
+    showToast('已复制', 'success')
+  } catch (e) {
+    console.error('复制失败:', e)
+    showToast('复制失败')
+  }
+}