Browse Source

logo图片修改。

ligao 2 weeks ago
parent
commit
beb7472059

+ 1 - 0
data/patients.csv

@@ -0,0 +1 @@
+patientId,name,gender,birthDate,maritalStatus,idCardNumber,address,phone,createTime

+ 3 - 0
medical-card-demo/frontend/.env.production

@@ -0,0 +1,3 @@
+# 生产 / 打 APK 用:H5 从 file:// 或任意域名打开时,主对话与挂号等接口走此完整基址
+# 本机/模拟器可临时改 .env.production 或本地 .env.production.local 后重新 npm run build
+VUE_APP_API_BASE=https://rmcdemo.emoon.com/api/v1

BIN
medical-card-demo/frontend/src/assets/images/emoonloge.png


BIN
medical-card-demo/frontend/src/assets/images/lsxlz.jpg


+ 505 - 0
medical-card-demo/frontend/src/components/HarmonyHomeDesktop.vue

@@ -0,0 +1,505 @@
+<template>
+  <div
+    class="harmony-home"
+    role="application"
+    aria-label="模拟系统桌面"
+  >
+    <div
+      class="harmony-bg"
+      aria-hidden="true"
+    />
+
+    <header class="harmony-status">
+      <div class="harmony-status__left" />
+      <div class="harmony-status__center">
+        <span class="harmony-dots" aria-hidden="true">
+          <span
+            v-for="i in 3"
+            :key="i"
+            :class="['harmony-dots__dot', i === 1 ? 'is-active' : '']"
+          />
+        </span>
+      </div>
+      <div
+        class="harmony-status__right"
+        :aria-label="`时间 ${timeText}`"
+      >
+        <span class="harmony-battery" aria-hidden="true">
+          <span class="harmony-battery__inner" />
+        </span>
+        <span class="harmony-pct">{{ timeText }}</span>
+        <span class="harmony-signal" aria-hidden="true">
+          <span class="harmony-signal__bar" />
+          <span class="harmony-signal__bar" />
+          <span class="harmony-signal__bar" />
+        </span>
+      </div>
+    </header>
+
+    <section
+      class="harmony-hero"
+      aria-hidden="true"
+    >
+      <div class="harmony-hero__time">
+        {{ timeText }}
+      </div>
+      <div class="harmony-hero__date">
+        {{ clockDateLine }}
+      </div>
+    </section>
+
+    <div class="harmony-fade-top" />
+    <main class="harmony-launcher">
+      <div
+        v-for="(row, ri) in appRows"
+        :key="ri"
+        class="harmony-launcher__row"
+      >
+        <button
+          v-for="app in row"
+          :key="app.id"
+          type="button"
+          class="harmony-tile"
+          :class="{
+            'is-primary': app.primary,
+            'is-decoy': !app.open
+          }"
+          :aria-label="app.label + (app.open ? ',打开' : ',演示图标')"
+          @click="onAppClick(app)"
+        >
+          <div
+            class="harmony-ico"
+            :style="app.primary ? { '--ico-g1': app.g1, '--ico-g2': app.g2 } : {}"
+          >
+            <span
+              v-if="!app.svg"
+              class="harmony-ico__glyph"
+            >{{ app.char }}</span>
+            <span
+              v-else
+              class="harmony-ico__svg"
+              v-html="app.svg"
+            />
+          </div>
+          <span class="harmony-tile__label">{{ app.label }}</span>
+        </button>
+      </div>
+    </main>
+
+    <div class="harmony-dock-wrap">
+      <div class="harmony-dock">
+        <button
+          v-for="(d, di) in dockItems"
+          :key="di"
+          type="button"
+          class="harmony-dock__item"
+          aria-hidden="true"
+          tabindex="-1"
+          @click.prevent.stop
+        >
+          <div
+            class="harmony-ico harmony-ico--sm"
+            :style="{ '--ico-g1': d.g1, '--ico-g2': d.g2 }"
+          >
+            <span class="harmony-ico__glyph">{{ d.ch }}</span>
+          </div>
+        </button>
+      </div>
+    </div>
+
+    <p
+      class="harmony-hint"
+      aria-hidden="true"
+    >
+      向上轻扫打开搜索
+    </p>
+  </div>
+</template>
+
+<script>
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+
+const wk = ['日', '一', '二', '三', '四', '五', '六']
+
+function pad2 (n) {
+  return n < 10 ? '0' + n : '' + n
+}
+
+export default {
+  name: 'HarmonyHomeDesktop',
+  emits: ['open-triage'],
+  setup (_props, { emit }) {
+    const now = ref(new Date())
+    let timer = 0
+    onMounted(() => {
+      timer = window.setInterval(() => {
+        now.value = new Date()
+      }, 1000)
+    })
+    onUnmounted(() => {
+      if (timer) {
+        clearInterval(timer)
+      }
+    })
+    const timeText = computed(() => {
+      const d = now.value
+      return pad2(d.getHours()) + ':' + pad2(d.getMinutes())
+    })
+    const clockDateLine = computed(() => {
+      const d = now.value
+      const m = d.getMonth() + 1
+      const day = d.getDate()
+      return `${m}月${day}日 星期${wk[d.getDay()]}`
+    })
+    const triageIconSvg = [
+      '<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">',
+      '<rect x="8" y="6" width="32" height="30" rx="6" fill="rgba(255,255,255,0.95)"/>',
+      '<path d="M24 16v6M20 20h8" stroke="#1565C0" stroke-width="2.2" stroke-linecap="round"/>',
+      '<path d="M16 32c2-2 3.5-3 8-3s6 1 8 3" stroke="rgba(21,101,192,0.5)" fill="none" stroke-width="1.5" stroke-linecap="round"/>',
+      '</svg>'
+    ].join('')
+
+    const appRows = ref([
+      [
+        { id: 'set', label: '设置', char: '⚙', open: false, g1: '#8b95a5', g2: '#5c6b7d' },
+        { id: 'cam', label: '相机', char: '◎', open: false, g1: '#3d8bfa', g2: '#1e5fd4' },
+        { id: 'gal', label: '图库', char: '▣', open: false, g1: '#5eb8ff', g2: '#2d7cff' },
+        { id: 'mus', label: '音乐', char: '♪', open: false, g1: '#ff6b6b', g2: '#e03131' }
+      ],
+      [
+        { id: 'thm', label: '主题', char: '◐', open: false, g1: '#9b7edc', g2: '#6741a5' },
+        { id: 'wth', label: '天气', char: '☀', open: false, g1: '#4fc3f7', g2: '#0288d1' },
+        { id: 'mkt', label: '应用市场', char: '▤', open: false, g1: '#7cb342', g2: '#448a0e' },
+        { id: 'brw', label: '浏览器', char: '⟡', open: false, g1: '#5c6bc0', g2: '#303f9f' }
+      ],
+      [
+        { id: 'cal', label: '日历', char: '▦', open: false, g1: '#ff9800', g2: '#e65100' },
+        { id: 'clk', label: '时钟', char: '⏰', open: false, g1: '#00acc1', g2: '#006978' },
+        { id: 'nt', label: '备忘录', char: '☰', open: false, g1: '#ffe066', g2: '#f0b429' },
+        {
+          id: 'triage',
+          label: '导诊助手',
+          svg: triageIconSvg,
+          char: '',
+          open: true,
+          primary: true,
+          g1: '#1e6fd4',
+          g2: '#0d47a1'
+        }
+      ]
+    ])
+
+    const dockItems = ref([
+      { g1: '#43a047', g2: '#1b5e20', ch: '☎' },
+      { g1: '#29b6f6', g2: '#0277bd', ch: '✉' },
+      { g1: '#3d8bfa', g2: '#0d47a1', ch: '◎' },
+      { g1: '#ab47bc', g2: '#6a1b9a', ch: '◎' }
+    ])
+
+    const onAppClick = (app) => {
+      if (app.open && app.id === 'triage') {
+        emit('open-triage')
+        return
+      }
+    }
+
+    return {
+      timeText,
+      clockDateLine,
+      appRows,
+      dockItems,
+      onAppClick
+    }
+  }
+}
+</script>
+
+<style scoped>
+.harmony-home {
+  --harmony-safe-top: max(8px, env(safe-area-inset-top, 0px));
+  --harmony-safe-bottom: max(8px, env(safe-area-inset-bottom, 0px));
+  position: fixed;
+  inset: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  color: #1a1a1a;
+  font-family: 'HarmonyOS Sans', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  -webkit-font-smoothing: antialiased;
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
+}
+
+.harmony-bg {
+  position: absolute;
+  inset: 0;
+  z-index: 0;
+  background:
+    radial-gradient(ellipse 90% 70% at 20% 15%, rgba(120, 180, 255, 0.45), transparent 55%),
+    radial-gradient(ellipse 80% 60% at 85% 30%, rgba(200, 160, 255, 0.32), transparent 50%),
+    radial-gradient(ellipse 100% 80% at 50% 100%, rgba(100, 200, 220, 0.25), transparent 45%),
+    linear-gradient(168deg, #b8d4ff 0%, #c8d8f8 18%, #dde8fd 40%, #e8f0ff 65%, #eef4fb 100%);
+  pointer-events: none;
+}
+
+.harmony-home::after {
+  content: '';
+  position: absolute;
+  inset: 0;
+  z-index: 0;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Ccircle cx='2' cy='2' r='0.5' fill='%23fff' fill-opacity='0.12'/%3E%3C/svg%3E");
+  background-size: 80px 80px;
+  opacity: 0.4;
+  pointer-events: none;
+  mix-blend-mode: soft-light;
+}
+
+.harmony-status {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: var(--harmony-safe-top) 12px 8px;
+  font-size: 11px;
+  color: rgba(0, 0, 0, 0.72);
+  font-weight: 600;
+  letter-spacing: 0.02em;
+}
+
+.harmony-dots {
+  display: flex;
+  gap: 3px;
+  align-items: center;
+}
+
+.harmony-dots__dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background: rgba(0, 0, 0, 0.25);
+  transition: transform 0.2s, background 0.2s;
+}
+
+.harmony-dots__dot.is-active {
+  width: 7px;
+  border-radius: 2px;
+  background: linear-gradient(90deg, #1e6fd4, #42a5f5);
+  transform: scale(1.05);
+}
+
+.harmony-status__right {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.harmony-battery {
+  width: 18px;
+  height: 9px;
+  border: 1px solid rgba(0, 0, 0, 0.4);
+  border-radius: 2px;
+  position: relative;
+  padding: 1px;
+}
+.harmony-battery::after {
+  content: '';
+  position: absolute;
+  right: -2px;
+  top: 2px;
+  width: 1px;
+  height: 4px;
+  background: rgba(0, 0, 0, 0.4);
+  border-radius: 0 1px 1px 0;
+}
+.harmony-battery__inner {
+  display: block;
+  width: 75%;
+  height: 100%;
+  border-radius: 1px;
+  background: linear-gradient(90deg, #1e6fd4, #4fc3f7);
+}
+
+.harmony-signal {
+  display: flex;
+  align-items: flex-end;
+  gap: 1.5px;
+  margin-left: 2px;
+}
+.harmony-signal__bar {
+  width: 2px;
+  background: #333;
+  border-radius: 1px;
+  opacity: 0.5;
+}
+.harmony-signal__bar:nth-child(1) { height: 3px; }
+.harmony-signal__bar:nth-child(2) { height: 5px; opacity: 0.7; }
+.harmony-signal__bar:nth-child(3) { height: 7px; opacity: 0.9; }
+
+.harmony-hero {
+  position: relative;
+  z-index: 1;
+  text-align: center;
+  padding: 2px 16px 4px;
+  color: rgba(20, 40, 80, 0.9);
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
+}
+.harmony-hero__time {
+  font-size: 56px;
+  font-weight: 200;
+  letter-spacing: 0.04em;
+  line-height: 1.1;
+  font-variant-numeric: tabular-nums;
+}
+.harmony-hero__date {
+  margin-top: 2px;
+  font-size: 12px;
+  font-weight: 500;
+  opacity: 0.8;
+  letter-spacing: 0.08em;
+}
+.harmony-fade-top {
+  height: 8px;
+  position: relative;
+  z-index: 1;
+  background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), transparent);
+  pointer-events: none;
+}
+.harmony-launcher {
+  position: relative;
+  z-index: 1;
+  padding: 0 14px 10px;
+  max-width: 520px;
+  margin: 0 auto;
+  flex: 1;
+  overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
+}
+.harmony-launcher__row {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 4px 6px;
+  margin-bottom: 10px;
+}
+.harmony-tile {
+  border: none;
+  background: transparent;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 5px;
+  padding: 6px 2px 8px;
+  border-radius: 12px;
+  color: inherit;
+  cursor: pointer;
+  transition: transform 0.15s ease, filter 0.15s;
+}
+.harmony-tile:active {
+  transform: scale(0.96);
+}
+.harmony-tile.is-decoy {
+  filter: grayscale(0.04);
+  opacity: 0.95;
+}
+.harmony-tile.is-primary .harmony-ico {
+  box-shadow:
+    0 6px 16px rgba(30, 111, 212, 0.38),
+    0 0 0 0.5px rgba(255, 255, 255, 0.35) inset;
+}
+.harmony-ico {
+  --ico-g1: #5c6bc0;
+  --ico-g2: #303f9f;
+  width: 52px;
+  height: 52px;
+  border-radius: 20%;
+  background: linear-gradient(145deg, var(--ico-g1), var(--ico-g2));
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow:
+    0 4px 10px rgba(0, 0, 0, 0.12),
+    0 0 0 0.5px rgba(255, 255, 255, 0.25) inset;
+}
+.harmony-ico--sm {
+  width: 40px;
+  height: 40px;
+  border-radius: 20%;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+}
+.harmony-ico__glyph {
+  font-size: 22px;
+  line-height: 1;
+  color: #fff;
+  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
+}
+.harmony-ico__svg {
+  display: block;
+  width: 30px;
+  height: 30px;
+}
+.harmony-ico__svg :deep(svg) {
+  display: block;
+  width: 100%;
+  height: 100%;
+}
+.harmony-tile__label {
+  font-size: 11px;
+  font-weight: 500;
+  color: rgba(0, 0, 0, 0.7);
+  text-align: center;
+  line-height: 1.2;
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+.harmony-dock-wrap {
+  position: relative;
+  z-index: 2;
+  padding: 0 18px var(--harmony-safe-bottom);
+  margin-top: auto;
+  flex-shrink: 0;
+}
+.harmony-dock {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  max-width: 420px;
+  margin: 0 auto;
+  padding: 8px 12px 10px;
+  border-radius: 22px;
+  background: rgba(255, 255, 255, 0.4);
+  backdrop-filter: blur(20px) saturate(1.2);
+  -webkit-backdrop-filter: blur(20px) saturate(1.2);
+  box-shadow:
+    0 4px 24px rgba(40, 80, 120, 0.12),
+    0 0 0 0.5px rgba(255, 255, 255, 0.55) inset;
+}
+.harmony-dock__item {
+  border: none;
+  background: transparent;
+  padding: 4px;
+  cursor: default;
+  opacity: 0.9;
+  pointer-events: auto;
+}
+.harmony-hint {
+  position: relative;
+  z-index: 1;
+  text-align: center;
+  font-size: 10px;
+  color: rgba(0, 0, 0, 0.28);
+  margin: 0 0 6px;
+  padding-bottom: max(2px, env(safe-area-inset-bottom, 0px));
+  letter-spacing: 0.12em;
+}
+
+.harmony-home {
+  display: flex;
+  flex-direction: column;
+  min-height: 100%;
+  background: #dbe8fc;
+}
+</style>

+ 77 - 0
medical-card-demo/frontend/src/utils/robotNavBridge.js

@@ -0,0 +1,77 @@
+/**
+ * Android WebView 中由 {@code MedicalTriageWebActivity} 注入的 JS 桥,与
+ * 示例项目 {@code NavigationFragment} 使用同一套 {@code RobotApi#startNavigation}。
+ *
+ * 浏览器直接打开时无 RobotNav,需安全降级,仅作对话、不弹错。
+ */
+export function isRobotNavAvailable () {
+  if (typeof window === 'undefined') {
+    return false
+  }
+  const r = window.RobotNav
+  return !!(r && typeof r.startNavigation === 'function' && typeof r.stopNavigation === 'function')
+}
+
+export function isStopNavPhrase (text) {
+  const s = (text || '').trim()
+  return /^(?:停止|结束|取消)导航\s*$/.test(s)
+}
+
+/**
+ * 从用户自然语言中解析目标地点,供 {@link requestStartNavigation} 传给原底盘(须与猎户地图里点位名一致)。
+ * 支持打字与「语音转文字后同一句发送」;说法可持续往 patterns 里加。
+ * @returns {string|null} 位置点名
+ */
+export function extractNavDestination (t) {
+  const s = (t || '').trim()
+  if (!s) {
+    return null
+  }
+  const patterns = [
+    /请\s*(?:带|送)我(?:到|往)?\s*(.+?)(?:\s*去|。|!|!)?$/,
+    /(?:带|送)我(?:到|往)?\s*(.+?)\s*去\s*$/,
+    /(?:带|送)我\s*去\s*(.+)$/,
+    /带\s*我\s*到\s*(.+?)(?:\s*去|。|!|!|\?)?$/,
+    /带\s*我\s*(\S+?)\s*去$/,
+    /送\s*我\s*到\s*(.+?)(?:\s*去|。|!|!)?$/
+  ]
+  for (let i = 0; i < patterns.length; i++) {
+    const m = s.match(patterns[i])
+    if (m && m[1]) {
+      return m[1]
+        .replace(/^[到往去]\s*/, '')
+        .trim()
+    }
+  }
+  return null
+}
+
+export function requestStopNavigation () {
+  if (!isRobotNavAvailable()) {
+    return false
+  }
+  try {
+    window.RobotNav.stopNavigation()
+    return true
+  } catch (e) {
+    console.warn('RobotNav.stopNavigation failed', e)
+    return false
+  }
+}
+
+/**
+ * @param {string} placeName 地图点名称
+ * @returns {boolean} 是否已发起原生调用
+ */
+export function requestStartNavigation (placeName) {
+  if (!placeName || !isRobotNavAvailable()) {
+    return false
+  }
+  try {
+    window.RobotNav.startNavigation(String(placeName).trim())
+    return true
+  } catch (e) {
+    console.warn('RobotNav.startNavigation failed', e)
+    return false
+  }
+}

+ 0 - 0
统一入口客户.txt