zengy 3 dagar sedan
förälder
incheckning
84de4102ad
6 ändrade filer med 81 tillägg och 6 borttagningar
  1. 4 1
      index.html
  2. 28 0
      src/App.vue
  3. 2 1
      src/manifest.json
  4. 2 2
      src/pages/contacts/index.vue
  5. 1 1
      src/pages/emergency/plan-detail.vue
  6. 44 1
      src/styles/global.scss

+ 4 - 1
index.html

@@ -2,7 +2,10 @@
 <html lang="zh-CN">
   <head>
     <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
+    <meta name="theme-color" content="#ffffff" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="default" />
     <title>城市地下管网管控平台</title>
   </head>
   <body>

+ 28 - 0
src/App.vue

@@ -1,9 +1,37 @@
 <script setup lang="ts">
 import { onLaunch } from '@dcloudio/uni-app'
+import { onBeforeUnmount, onMounted } from 'vue'
+
+// #ifdef H5
+function preventPageZoom(event: Event) {
+  event.preventDefault()
+}
+
+function preventPinchTouch(event: TouchEvent) {
+  if (event.touches.length > 1) event.preventDefault()
+}
+// #endif
 
 onLaunch(() => {
   // 第一阶段仅初始化静态应用;鉴权、字典和用户信息将在接口联调阶段接入。
 })
+
+// #ifdef H5
+onMounted(() => {
+  // WKWebView 部分版本会忽略 user-scalable=no,补充拦截系统手势;单指滚动不受影响。
+  document.addEventListener('gesturestart', preventPageZoom, { passive: false })
+  document.addEventListener('gesturechange', preventPageZoom, { passive: false })
+  document.addEventListener('gestureend', preventPageZoom, { passive: false })
+  document.addEventListener('touchmove', preventPinchTouch, { passive: false })
+})
+
+onBeforeUnmount(() => {
+  document.removeEventListener('gesturestart', preventPageZoom)
+  document.removeEventListener('gesturechange', preventPageZoom)
+  document.removeEventListener('gestureend', preventPageZoom)
+  document.removeEventListener('touchmove', preventPinchTouch)
+})
+// #endif
 </script>
 
 <style lang="scss">

+ 2 - 1
src/manifest.json

@@ -9,7 +9,8 @@
     "usingComponents": true,
     "nvueStyleCompiler": "uni-app",
     "compilerVersion": 3,
-    "splashscreen": { "alwaysShowBeforeRender": true, "waiting": true, "autoclose": true }
+    "splashscreen": { "alwaysShowBeforeRender": true, "waiting": true, "autoclose": true },
+    "statusbar": { "immersed": false, "style": "dark", "background": "#ffffff" }
   },
   "h5": { "title": "城市地下管网管控平台", "router": { "mode": "hash" } },
   "mp-weixin": { "appid": "", "setting": { "urlCheck": false }, "usingComponents": true },

+ 2 - 2
src/pages/contacts/index.vue

@@ -139,7 +139,7 @@ onPullDownRefresh(refreshContacts)
     <view class="contact-head"><text class="title">通讯录</text><view class="search"><text>⌕</text><input v-model="keyword" placeholder="搜索联系人" /></view></view>
     <view v-if="loaded && loadError && !contactCount" class="state error" @tap="loadContacts">{{ loadError }},点击重试</view>
     <view v-else class="contact-list">
-      <view v-for="group in grouped" :key="group.letter" class="group"><view class="letter">{{ group.letter }}</view><view v-for="item in group.items" :key="item.userId" class="contact"><view class="avatar" :style="{ background: getColor(item) }">{{ getName(item).slice(0, 1) }}</view><view class="info"><text class="name">{{ getName(item) }}</text><text class="meta">{{ item.deptName || '未设置部门' }} · {{ getRole(item) }}</text><text v-if="item.phonenumber" class="number">{{ item.phonenumber }}</text></view><view class="phone" @tap.stop="call(item.phonenumber)">☎</view></view></view>
+      <view v-for="group in grouped" :key="group.letter" class="group"><view class="letter">{{ group.letter }}</view><view v-for="item in group.items" :key="item.userId" class="contact"><view class="avatar" :style="{ background: getColor(item) }">{{ getName(item).slice(0, 1) }}</view><view class="info"><text class="name">{{ getName(item) }}</text><text class="meta">{{ item.deptName || '未设置部门' }} · {{ getRole(item) }}</text><text v-if="item.phonenumber" class="number">{{ item.phonenumber }}</text></view><view class="phone" @tap.stop="call(item.phonenumber)"><svg class="phone-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M6.6 3.8 9 3.2a1.5 1.5 0 0 1 1.7.8l1.1 2.5a1.5 1.5 0 0 1-.4 1.8L9.8 9.7a14.7 14.7 0 0 0 4.5 4.5l1.4-1.6a1.5 1.5 0 0 1 1.8-.4l2.5 1.1a1.5 1.5 0 0 1 .8 1.7l-.6 2.4a2 2 0 0 1-2 1.5A16.2 16.2 0 0 1 5.1 5.8a2 2 0 0 1 1.5-2Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg></view></view></view>
       <view v-if="loaded && !grouped.length && !loading" class="empty">{{ keyword ? '未找到联系人' : '暂无可查看的联系人' }}</view>
       <view v-else-if="loading" class="state load-state">正在加载通讯录...</view>
       <view v-else-if="loadError" class="state error" @tap="loadContacts">{{ loadError }},点击重试</view>
@@ -148,5 +148,5 @@ onPullDownRefresh(refreshContacts)
 </template>
 
 <style scoped lang="scss">
-.contact-head{padding:calc(40rpx + env(safe-area-inset-top)) 26rpx 28rpx;color:#fff;background:linear-gradient(135deg,#061b34,#174e76)}.title{display:block;margin:0 4rpx 27rpx;font-size:38rpx;font-weight:650}.search{display:flex;align-items:center;height:76rpx;padding:0 23rpx;border-radius:16rpx;background:rgba(255,255,255,.95);color:#8190a2}.search text{margin-right:13rpx;font-size:34rpx}.search input{flex:1;color:#33465d;font-size:26rpx}.contact-list{padding-bottom:calc(25rpx + env(safe-area-inset-bottom))}.letter{height:68rpx;padding:0 28rpx;color:#8b99aa;background:#f3f6fa;font-size:25rpx;font-weight:700;line-height:68rpx}.contact{display:flex;align-items:center;padding:23rpx 28rpx;background:#fff;border-bottom:1rpx solid #edf1f5}.avatar{display:flex;align-items:center;justify-content:center;width:82rpx;height:82rpx;margin-right:21rpx;color:#fff;border-radius:50%;font-size:31rpx;font-weight:650}.info{display:flex;flex:1;flex-direction:column;min-width:0}.name{color:#293d55;font-size:29rpx;font-weight:650}.meta{margin-top:10rpx;color:#8997a8;font-size:23rpx;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.number{margin-top:7rpx;color:#60758b;font-size:22rpx}.phone{display:flex;align-items:center;justify-content:center;width:65rpx;height:65rpx;margin-left:14rpx;color:#08264c;border-radius:50%;background:#e7edf3;font-family:Arial,sans-serif;font-size:33rpx}.state,.empty{padding:120rpx 28rpx;color:#98a4b2;text-align:center}.state.error{color:#c35c60}.state.load-state{padding:28rpx}.empty{padding-top:100rpx}
+.contact-head{padding:calc(40rpx + env(safe-area-inset-top)) 26rpx 28rpx;color:#fff;background:linear-gradient(135deg,#061b34,#174e76)}.title{display:block;margin:0 4rpx 27rpx;font-size:38rpx;font-weight:650}.search{display:flex;align-items:center;height:76rpx;padding:0 23rpx;border-radius:16rpx;background:rgba(255,255,255,.95);color:#8190a2}.search text{margin-right:13rpx;font-size:34rpx}.search input{flex:1;color:#33465d;font-size:26rpx}.contact-list{padding-bottom:calc(25rpx + env(safe-area-inset-bottom))}.letter{height:68rpx;padding:0 28rpx;color:#8b99aa;background:#f3f6fa;font-size:25rpx;font-weight:700;line-height:68rpx}.contact{display:flex;align-items:center;padding:23rpx 28rpx;background:#fff;border-bottom:1rpx solid #edf1f5}.avatar{display:flex;align-items:center;justify-content:center;width:82rpx;height:82rpx;margin-right:21rpx;color:#fff;border-radius:50%;font-size:31rpx;font-weight:650}.info{display:flex;flex:1;flex-direction:column;min-width:0}.name{color:#293d55;font-size:29rpx;font-weight:650}.meta{margin-top:10rpx;color:#8997a8;font-size:23rpx;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.number{margin-top:7rpx;color:#60758b;font-size:22rpx}.phone{display:flex;align-items:center;justify-content:center;width:65rpx;height:65rpx;margin-left:14rpx;color:#08264c;border-radius:50%;background:#e7edf3}.phone-icon{display:block;width:34rpx;height:34rpx}.state,.empty{padding:120rpx 28rpx;color:#98a4b2;text-align:center}.state.error{color:#c35c60}.state.load-state{padding:28rpx}.empty{padding-top:100rpx}
 </style>

+ 1 - 1
src/pages/emergency/plan-detail.vue

@@ -85,7 +85,7 @@ async function submit() {
           <view class="choice-field"><text class="field-label"><text class="required-mark">*</text>审核结果</text><view class="choice-row"><button :disabled="submitting" :class="{ active: auditResult === '通过' }" @tap="auditResult = '通过'">审核通过</button><button :disabled="submitting" :class="{ active: auditResult === '驳回' }" @tap="auditResult = '驳回'">审核驳回</button></view></view>
           <FormField v-model="note" label="审核意见" type="textarea" :required="auditResult === '驳回'" placeholder="通过可不填;驳回必填" />
         </view>
-        <view class="drawer-actions"><button :disabled="submitting" @tap="drawer=false">取消</button><button class="approve" :disabled="submitting" @tap="submit">{{ submitting ? '提交中...' : '确认提交' }}</button></view>
+        <view class="drawer-actions"><button class="cancel" :disabled="submitting" @tap="drawer=false">取消</button><button class="approve" :disabled="submitting" @tap="submit">{{ submitting ? '提交中...' : '确认提交' }}</button></view>
       </view>
     </view>
   </view>

+ 44 - 1
src/styles/global.scss

@@ -10,6 +10,19 @@ html, body, #app, uni-app, uni-page, uni-page-wrapper, uni-page-body {
   overflow-x: clip;
 }
 
+// iOS WebView 安全区:状态栏区域保持不被滚动内容覆盖,避免时间/Wi-Fi 等系统信息与页面叠加。
+body::before {
+  content: '';
+  position: fixed;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 10000;
+  height: env(safe-area-inset-top, 0px);
+  pointer-events: none;
+  background: #fff;
+}
+
 page {
   min-height: 100%;
   background: #f3f6fa;
@@ -54,7 +67,23 @@ button::after { border: 0; }
   line-height: 84rpx;
 }
 .primary-btn { color: #fff; background: linear-gradient(135deg, #123f68, #08264c); box-shadow: 0 10rpx 24rpx rgba(8, 38, 76, .22); }
-.ghost-btn { color: #4e6075; background: #fff; border: 1rpx solid #dbe3ec; }
+.ghost-btn { color: #4e6075; background: #fff; border: 1rpx solid #d7e0e9; }
+
+// 白底取消按钮统一采用“退出登录”按钮的边框处理,避免 iOS 原生 button 外观遮住圆角两侧边框。
+button.ghost-btn,
+button.cancel {
+  border: 1rpx solid #d7e0e9;
+  background: #fff;
+  box-shadow: none;
+  outline: none;
+  -webkit-appearance: none;
+  appearance: none;
+}
+button.ghost-btn::after,
+button.cancel::after {
+  border: 0 !important;
+  box-shadow: none;
+}
 .two-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx; margin-top: 30rpx; }
 .status-pill {
   display: inline-flex;
@@ -75,3 +104,17 @@ button::after { border: 0; }
 .status-pill.gray { color: #718096; background: #eef2f6; }
 .status-pill.purple { color: #7057c7; background: #f0edff; }
 .empty-safe { height: env(safe-area-inset-bottom); }
+
+// H5 TabBar 在 iPhone 底部安全区内需要额外高度,否则 safe-area padding 会挤压文字,首屏只显示图标。
+.uni-tabbar-bottom .uni-tabbar {
+  height: calc(56px + constant(safe-area-inset-bottom)) !important;
+  height: calc(56px + env(safe-area-inset-bottom, 0px)) !important;
+  min-height: calc(56px + constant(safe-area-inset-bottom)) !important;
+  min-height: calc(56px + env(safe-area-inset-bottom, 0px)) !important;
+  padding-bottom: constant(safe-area-inset-bottom) !important;
+  padding-bottom: env(safe-area-inset-bottom, 0px) !important;
+}
+.uni-tabbar-bottom .uni-tabbar__item {
+  height: 56px;
+  min-height: 56px;
+}