|
|
@@ -0,0 +1,554 @@
|
|
|
+<template>
|
|
|
+ <!-- 家庭天盘卡片 — 心维度专属 (#FF6B9D 火) -->
|
|
|
+ <view class="tianpan-card" v-if="visible">
|
|
|
+ <!-- 卡片头部 -->
|
|
|
+ <view class="tp-header">
|
|
|
+ <view class="tp-header-left">
|
|
|
+ <view class="tp-icon-badge">
|
|
|
+ <text class="tp-icon-text">☷</text>
|
|
|
+ </view>
|
|
|
+ <view class="tp-header-info">
|
|
|
+ <text class="tp-title">家庭天盘</text>
|
|
|
+ <text class="tp-subtitle">五行能量 · 每日更新</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="tp-header-action" @click="goDetail">
|
|
|
+ <text class="tp-action-text">→</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 三栏展示 -->
|
|
|
+ <view class="tp-body">
|
|
|
+ <!-- 今日运势 -->
|
|
|
+ <view class="tp-col tp-col-fortune">
|
|
|
+ <text class="tp-col-icon">⭐</text>
|
|
|
+ <text class="tp-col-label">今日运势</text>
|
|
|
+ <view class="tp-fortune-badge" :class="'fortune-level-' + fortuneLevel">
|
|
|
+ <text class="tp-fortune-text">{{ fortuneText }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="tp-fortune-desc">{{ fortuneDesc }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 今日心情 -->
|
|
|
+ <view class="tp-col tp-col-mood">
|
|
|
+ <text class="tp-col-icon">{{ moodEmoji }}</text>
|
|
|
+ <text class="tp-col-label">今日心情</text>
|
|
|
+ <text class="tp-mood-value">{{ moodText }}</text>
|
|
|
+ <view class="tp-mood-bar-track">
|
|
|
+ <view class="tp-mood-bar-fill" :style="{ width: moodPercent + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 吉位 -->
|
|
|
+ <view class="tp-col tp-col-direction">
|
|
|
+ <text class="tp-col-icon">🧭</text>
|
|
|
+ <text class="tp-col-label">今日吉位</text>
|
|
|
+ <view class="tp-compass-wrap">
|
|
|
+ <canvas
|
|
|
+ canvas-id="tianpanCompass"
|
|
|
+ id="tianpanCompass"
|
|
|
+ type="2d"
|
|
|
+ class="tp-compass-canvas"
|
|
|
+ ></canvas>
|
|
|
+ </view>
|
|
|
+ <text class="tp-direction-value">{{ luckyDirection }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部周报提示 -->
|
|
|
+ <view class="tp-footer" v-if="weeklyTip">
|
|
|
+ <text class="tp-footer-icon">💡</text>
|
|
|
+ <text class="tp-footer-text">{{ weeklyTip }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 查看详情入口 -->
|
|
|
+ <view class="tp-detail-btn" @click="goDetail">
|
|
|
+ <text class="tp-detail-text">查看家庭天盘详情 →</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+/**
|
|
|
+ * FamilyTianpanCard — 家庭天盘概览卡片(嵌入心维度页面)
|
|
|
+ *
|
|
|
+ * Props:
|
|
|
+ * fortune — { level: 0-4, text, description }
|
|
|
+ * mood — { emoji, text, score: 0-100 }
|
|
|
+ * dominantElement — 'wood'|'fire'|'earth'|'metal'|'water'
|
|
|
+ * weeklyTip — string 本周提示
|
|
|
+ * visible — boolean 是否显示
|
|
|
+ *
|
|
|
+ * 吉位 Canvas 绘制五元素罗盘,高亮吉位方向。
|
|
|
+ */
|
|
|
+export default {
|
|
|
+ name: 'FamilyTianpanCard',
|
|
|
+ props: {
|
|
|
+ fortune: {
|
|
|
+ type: Object,
|
|
|
+ default: function() { return null }
|
|
|
+ },
|
|
|
+ mood: {
|
|
|
+ type: Object,
|
|
|
+ default: function() { return null }
|
|
|
+ },
|
|
|
+ dominantElement: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ weeklyTip: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ visible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data: function() {
|
|
|
+ return {
|
|
|
+ canvasCtx: null,
|
|
|
+ canvasReady: false,
|
|
|
+ // 五行 → 方位映射
|
|
|
+ elementDirMap: {
|
|
|
+ wood: '东',
|
|
|
+ fire: '南',
|
|
|
+ earth: '中',
|
|
|
+ metal: '西',
|
|
|
+ water: '北'
|
|
|
+ },
|
|
|
+ // 方位 → 颜色映射
|
|
|
+ dirColorMap: {
|
|
|
+ '东': '#10B981',
|
|
|
+ '南': '#FF6B9D',
|
|
|
+ '中': '#FF8C42',
|
|
|
+ '西': '#6366F1',
|
|
|
+ '北': '#3B82F6'
|
|
|
+ },
|
|
|
+ // 方位 → 角度(从北顺时针,Canvas 默认从右/东开始)
|
|
|
+ dirAngleMap: {
|
|
|
+ '东': Math.PI / 2,
|
|
|
+ '南': Math.PI,
|
|
|
+ '中': 0,
|
|
|
+ '西': -Math.PI / 2,
|
|
|
+ '北': 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ fortuneLevel: function() {
|
|
|
+ if (!this.fortune) return 2
|
|
|
+ return this.fortune.level !== undefined ? this.fortune.level : 2
|
|
|
+ },
|
|
|
+ fortuneText: function() {
|
|
|
+ if (!this.fortune || !this.fortune.text) return '平'
|
|
|
+ return this.fortune.text
|
|
|
+ },
|
|
|
+ fortuneDesc: function() {
|
|
|
+ if (!this.fortune || !this.fortune.description) return '今日宜静心养性'
|
|
|
+ return this.fortune.description
|
|
|
+ },
|
|
|
+ moodEmoji: function() {
|
|
|
+ if (this.mood && this.mood.emoji) return this.mood.emoji
|
|
|
+ return '\u{1F60A}' // 默认笑脸
|
|
|
+ },
|
|
|
+ moodText: function() {
|
|
|
+ if (this.mood && this.mood.text) return this.mood.text
|
|
|
+ return '平静'
|
|
|
+ },
|
|
|
+ moodPercent: function() {
|
|
|
+ if (this.mood && this.mood.score !== undefined) return this.mood.score
|
|
|
+ return 60
|
|
|
+ },
|
|
|
+ luckyDirection: function() {
|
|
|
+ if (this.dominantElement && this.elementDirMap[this.dominantElement]) {
|
|
|
+ return this.elementDirMap[this.dominantElement] + '方'
|
|
|
+ }
|
|
|
+ return '东方'
|
|
|
+ },
|
|
|
+ luckyElementColor: function() {
|
|
|
+ var dir = this.luckyDirection
|
|
|
+ return this.dirColorMap[dir] || '#FF6B9D'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ visible: function(val) {
|
|
|
+ if (val) {
|
|
|
+ var self = this
|
|
|
+ setTimeout(function() {
|
|
|
+ self.initCanvas()
|
|
|
+ }, 300)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ dominantElement: function() {
|
|
|
+ if (this.canvasReady) {
|
|
|
+ this.drawCompass()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted: function() {
|
|
|
+ if (this.visible) {
|
|
|
+ var self = this
|
|
|
+ setTimeout(function() {
|
|
|
+ self.initCanvas()
|
|
|
+ }, 500)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ goDetail: function() {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: '/pages/mind-detail/family-dashboard'
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ initCanvas: function() {
|
|
|
+ var self = this
|
|
|
+ var query = uni.createSelectorQuery().in(self)
|
|
|
+ query.select('#tianpanCompass')
|
|
|
+ .fields({ node: true, size: true })
|
|
|
+ .exec(function(res) {
|
|
|
+ if (!res || !res[0] || !res[0].node) return
|
|
|
+ var ctx = res[0].node.getContext('2d')
|
|
|
+ var dpr = uni.getSystemInfoSync().pixelRatio || 2
|
|
|
+ var w = res[0].width
|
|
|
+ var h = res[0].height
|
|
|
+ res[0].node.width = w * dpr
|
|
|
+ res[0].node.height = h * dpr
|
|
|
+ ctx.scale(dpr, dpr)
|
|
|
+ self.canvasCtx = ctx
|
|
|
+ self.canvasReady = true
|
|
|
+ self._compassW = w
|
|
|
+ self._compassH = h
|
|
|
+ self.drawCompass()
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ drawCompass: function() {
|
|
|
+ var ctx = this.canvasCtx
|
|
|
+ if (!ctx) return
|
|
|
+
|
|
|
+ var w = this._compassW || 100
|
|
|
+ var h = this._compassH || 100
|
|
|
+ var cx = w / 2
|
|
|
+ var cy = h / 2
|
|
|
+ var outerR = Math.min(cx, cy) - 4
|
|
|
+
|
|
|
+ // 清空
|
|
|
+ ctx.clearRect(0, 0, w, h)
|
|
|
+
|
|
|
+ // 五个方位: 北(上), 东(右), 南(下), 西(左), 中(中心)
|
|
|
+ var directions = [
|
|
|
+ { label: '北', element: 'water', angle: -Math.PI / 2 },
|
|
|
+ { label: '东', element: 'wood', angle: 0 },
|
|
|
+ { label: '南', element: 'fire', angle: Math.PI / 2 },
|
|
|
+ { label: '西', element: 'metal', angle: Math.PI },
|
|
|
+ { label: '中', element: 'earth', angle: null }
|
|
|
+ ]
|
|
|
+ var elementColors = {
|
|
|
+ wood: '#10B981',
|
|
|
+ fire: '#FF6B9D',
|
|
|
+ earth: '#FF8C42',
|
|
|
+ metal: '#6366F1',
|
|
|
+ water: '#3B82F6'
|
|
|
+ }
|
|
|
+
|
|
|
+ var luckyDir = this.luckyDirection.replace('方', '')
|
|
|
+ var luckyColor = this.luckyElementColor
|
|
|
+
|
|
|
+ // 外圈背景
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(cx, cy, outerR, 0, Math.PI * 2)
|
|
|
+ ctx.fillStyle = '#FFF5F7'
|
|
|
+ ctx.fill()
|
|
|
+ ctx.strokeStyle = luckyColor + '40'
|
|
|
+ ctx.lineWidth = 1.5
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // 绘制四个方位的扇区
|
|
|
+ for (var i = 0; i < 4; i++) {
|
|
|
+ var d = directions[i]
|
|
|
+ var color = elementColors[d.element]
|
|
|
+
|
|
|
+ // 扇区线
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(cx, cy)
|
|
|
+ var ex = cx + outerR * Math.cos(d.angle)
|
|
|
+ var ey = cy + outerR * Math.sin(d.angle)
|
|
|
+ ctx.lineTo(ex, ey)
|
|
|
+ ctx.strokeStyle = color + '30'
|
|
|
+ ctx.lineWidth = 1
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // 方位标签
|
|
|
+ var labelR = outerR * 0.7
|
|
|
+ var lx = cx + labelR * Math.cos(d.angle)
|
|
|
+ var ly = cy + labelR * Math.sin(d.angle)
|
|
|
+
|
|
|
+ var isLucky = d.label === luckyDir
|
|
|
+ if (isLucky) {
|
|
|
+ // 吉位高亮圆
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(lx, ly, 12, 0, Math.PI * 2)
|
|
|
+ ctx.fillStyle = luckyColor + '20'
|
|
|
+ ctx.fill()
|
|
|
+ ctx.strokeStyle = luckyColor
|
|
|
+ ctx.lineWidth = 1.5
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.fillStyle = isLucky ? luckyColor : '#9CA3AF'
|
|
|
+ ctx.font = (isLucky ? 'bold ' : '') + '10px sans-serif'
|
|
|
+ ctx.textAlign = 'center'
|
|
|
+ ctx.textBaseline = 'middle'
|
|
|
+ ctx.fillText(d.label, lx, ly)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 中心点
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(cx, cy, 5, 0, Math.PI * 2)
|
|
|
+ ctx.fillStyle = luckyColor
|
|
|
+ ctx.fill()
|
|
|
+ ctx.strokeStyle = '#FFFFFF'
|
|
|
+ ctx.lineWidth = 1.5
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.tianpan-card {
|
|
|
+ margin: 20rpx 30rpx;
|
|
|
+ background: #FFFFFF;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ /* Claymorphism 双阴影 */
|
|
|
+ box-shadow: 0 4rpx 16rpx rgba(255, 107, 157, 0.12),
|
|
|
+ 0 2rpx 4rpx rgba(0, 0, 0, 0.04);
|
|
|
+ border: 1rpx solid rgba(255, 107, 157, 0.08);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 头部 ===== */
|
|
|
+.tp-header {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ padding-bottom: 20rpx;
|
|
|
+ border-bottom: 1rpx solid rgba(255, 107, 157, 0.10);
|
|
|
+}
|
|
|
+.tp-header-left {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.tp-icon-badge {
|
|
|
+ width: 64rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ background: linear-gradient(135deg, #FF6B9D, #FF8FB1);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.30);
|
|
|
+}
|
|
|
+.tp-icon-text {
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #FFFFFF;
|
|
|
+}
|
|
|
+.tp-header-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.tp-title {
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1E293B;
|
|
|
+}
|
|
|
+.tp-subtitle {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ margin-top: 2rpx;
|
|
|
+}
|
|
|
+.tp-header-action {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ border-radius: 28rpx;
|
|
|
+ background: #FFF0F3;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.tp-action-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #FF6B9D;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.tp-header-action:active {
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 三栏 body ===== */
|
|
|
+.tp-body {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+.tp-col {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 8rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ background: #FAFBFC;
|
|
|
+}
|
|
|
+.tp-col-fortune {
|
|
|
+ margin-right: 12rpx;
|
|
|
+}
|
|
|
+.tp-col-mood {
|
|
|
+ margin-left: 6rpx;
|
|
|
+ margin-right: 6rpx;
|
|
|
+}
|
|
|
+.tp-col-direction {
|
|
|
+ margin-left: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 列通用 */
|
|
|
+.tp-col-icon {
|
|
|
+ font-size: 36rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.tp-col-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 运势 */
|
|
|
+.tp-fortune-badge {
|
|
|
+ padding: 6rpx 20rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.fortune-level-0 {
|
|
|
+ background: #FFEBEE;
|
|
|
+}
|
|
|
+.fortune-level-0 .tp-fortune-text {
|
|
|
+ color: #D32F2F;
|
|
|
+}
|
|
|
+.fortune-level-1 {
|
|
|
+ background: #FFF3E0;
|
|
|
+}
|
|
|
+.fortune-level-1 .tp-fortune-text {
|
|
|
+ color: #E65100;
|
|
|
+}
|
|
|
+.fortune-level-2 {
|
|
|
+ background: #FFFDE7;
|
|
|
+}
|
|
|
+.fortune-level-2 .tp-fortune-text {
|
|
|
+ color: #F9A825;
|
|
|
+}
|
|
|
+.fortune-level-3 {
|
|
|
+ background: #F3E5F5;
|
|
|
+}
|
|
|
+.fortune-level-3 .tp-fortune-text {
|
|
|
+ color: #7B1FA2;
|
|
|
+}
|
|
|
+.fortune-level-4 {
|
|
|
+ background: #FFCDD2;
|
|
|
+}
|
|
|
+.fortune-level-4 .tp-fortune-text {
|
|
|
+ color: #B71C1C;
|
|
|
+}
|
|
|
+.tp-fortune-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+.tp-fortune-desc {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+/* 心情 */
|
|
|
+.tp-mood-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1E293B;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.tp-mood-bar-track {
|
|
|
+ width: 100%;
|
|
|
+ height: 10rpx;
|
|
|
+ background: #E2E8F0;
|
|
|
+ border-radius: 5rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.tp-mood-bar-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #FF6B9D, #FF8FB1);
|
|
|
+ border-radius: 5rpx;
|
|
|
+ transition: width 0.5s;
|
|
|
+}
|
|
|
+
|
|
|
+/* 吉位罗盘 */
|
|
|
+.tp-compass-wrap {
|
|
|
+ width: 120rpx;
|
|
|
+ height: 120rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.tp-compass-canvas {
|
|
|
+ width: 120rpx;
|
|
|
+ height: 120rpx;
|
|
|
+}
|
|
|
+.tp-direction-value {
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #FF6B9D;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 底部提示 ===== */
|
|
|
+.tp-footer {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-top: 20rpx;
|
|
|
+ padding: 16rpx;
|
|
|
+ background: #FFF5F0;
|
|
|
+ border-radius: 14rpx;
|
|
|
+}
|
|
|
+.tp-footer-icon {
|
|
|
+ font-size: 24rpx;
|
|
|
+ margin-right: 8rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.tp-footer-text {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #8B6914;
|
|
|
+ line-height: 1.5;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 查看详情按钮 ===== */
|
|
|
+.tp-detail-btn {
|
|
|
+ margin-top: 16rpx;
|
|
|
+ text-align: center;
|
|
|
+ padding: 14rpx;
|
|
|
+ background: linear-gradient(135deg, #FFF0F3, #FFE0E8);
|
|
|
+ border-radius: 14rpx;
|
|
|
+}
|
|
|
+.tp-detail-btn:active {
|
|
|
+ opacity: 0.7;
|
|
|
+}
|
|
|
+.tp-detail-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #FF6B9D;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+</style>
|