Przeglądaj źródła

feat(mind): add PsychCrisisBanner component for crisis alerts

Create reusable PsychCrisisBanner component with three severity levels (high/medium/low), dismiss functionality, and auto-loading from alert API. Supports childId prop and source context (screening/checkin).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Xiaogang Liao 4 miesięcy temu
rodzic
commit
e3210fc861
1 zmienionych plików z 186 dodań i 0 usunięć
  1. 186 0
      cfc-frontend/components/PsychCrisisBanner.vue

+ 186 - 0
cfc-frontend/components/PsychCrisisBanner.vue

@@ -0,0 +1,186 @@
+<template>
+  <view v-if="alerts.length > 0" class="crisis-container">
+    <view
+      v-for="alert in alerts"
+      :key="alert.id"
+      :class="['crisis-alert', 'severity-' + alert.severity]"
+      @click="onAlertTap(alert)">
+      <view class="alert-header">
+        <text class="alert-icon">{{ severityIcon(alert.severity) }}</text>
+        <text class="alert-title">{{ alert.title }}</text>
+        <text class="alert-dismiss" @click.stop="dismissAlert(alert)">&#x2716;</text>
+      </view>
+      <text class="alert-message">{{ alert.message }}</text>
+      <view class="alert-footer">
+        <text class="alert-time">{{ formatTime(alert.createdAt) }}</text>
+        <text class="alert-action">点击查看详情 &gt;</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import config from '../config'
+
+export default {
+  props: {
+    childId: { type: Number, default: 0 },
+    source: { type: String, default: 'screening' } // screening or checkin
+  },
+
+  data() {
+    return {
+      alerts: []
+    }
+  },
+
+  watch: {
+    childId: {
+      immediate: true,
+      handler(val) {
+        if (val) this.loadAlerts()
+      }
+    }
+  },
+
+  methods: {
+    loadAlerts() {
+      var self = this
+      var endpoint = this.source === 'checkin'
+        ? config.API_BASE_URL + '/api/mind/alert/checkin-alerts'
+        : config.API_BASE_URL + '/api/mind/alert/active'
+
+      uni.request({
+        url: endpoint,
+        method: 'POST',
+        data: { childId: this.childId },
+        success: function(res) {
+          if (res.data && res.data.code === 200 && res.data.data) {
+            self.alerts = res.data.data
+          }
+        }
+      })
+    },
+
+    dismissAlert(alert) {
+      var self = this
+      uni.request({
+        url: config.API_BASE_URL + '/api/mind/alert/resolve',
+        method: 'POST',
+        data: { alertId: alert.id, source: this.source },
+        success: function() {
+          var remaining = []
+          for (var i = 0; i < self.alerts.length; i++) {
+            if (self.alerts[i].id !== alert.id) {
+              remaining.push(self.alerts[i])
+            }
+          }
+          self.alerts = remaining
+        }
+      })
+    },
+
+    onAlertTap(alert) {
+      uni.showModal({
+        title: alert.title,
+        content: alert.message,
+        confirmText: '知道了',
+        showCancel: false
+      })
+    },
+
+    severityIcon(severity) {
+      if (severity === 'high') return '\u{1F6A8}'
+      if (severity === 'medium') return '\u26A1'
+      return '\u{1F4AC}'
+    },
+
+    formatTime(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      var m = d.getMonth() + 1
+      var day = d.getDate()
+      var h = d.getHours()
+      var min = d.getMinutes()
+      return m + '/' + day + ' ' + h + ':' + (min < 10 ? '0' : '') + min
+    }
+  }
+}
+</script>
+
+<style scoped>
+.crisis-container {
+  margin: 16rpx 32rpx;
+}
+
+.crisis-alert {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 20rpx;
+  margin-bottom: 12rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
+  border-left: 8rpx solid #FF6B9D;
+}
+
+.crisis-alert.severity-high {
+  border-left-color: #EF4444;
+  background: linear-gradient(135deg, #FFF5F5 0%, #FFFFFF 100%);
+}
+
+.crisis-alert.severity-medium {
+  border-left-color: #F59E0B;
+  background: linear-gradient(135deg, #FFFBEB 0%, #FFFFFF 100%);
+}
+
+.crisis-alert.severity-low {
+  border-left-color: #3B82F6;
+}
+
+.alert-header {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.alert-icon {
+  font-size: 28rpx;
+}
+
+.alert-title {
+  flex: 1;
+  font-size: 26rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.alert-dismiss {
+  font-size: 24rpx;
+  color: #ccc;
+  padding: 4rpx 8rpx;
+}
+
+.alert-message {
+  display: block;
+  font-size: 24rpx;
+  color: #666;
+  margin-top: 8rpx;
+  line-height: 1.5;
+}
+
+.alert-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 12rpx;
+}
+
+.alert-time {
+  font-size: 20rpx;
+  color: #bbb;
+}
+
+.alert-action {
+  font-size: 22rpx;
+  color: #FF6B9D;
+}
+</style>