|
|
@@ -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>
|