| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070 |
- <template>
- <view class="checkin-container">
- <!-- 顶部:日期 + 连续打卡 -->
- <view class="checkin-header">
- <text class="checkin-date">{{ todayDisplay }}</text>
- <view class="streak-badge" v-if="stats && stats.currentStreak > 0">
- <text class="streak-icon">🔥</text>
- <text class="streak-text">连续{{ stats.currentStreak }}天</text>
- </view>
- </view>
- <!-- ===== Phase 1.1: 2D 效价-激活度网格 ===== -->
- <view class="section">
- <text class="section-title">在方格中点击你的情绪位置</text>
- <view class="affect-grid" @tap="onAffectGridTap">
- <!-- 象限标签 -->
- <text class="grid-label label-nw">烦躁/紧张</text>
- <text class="grid-label label-ne">兴奋/愉快</text>
- <text class="grid-label label-sw">低落/疲惫</text>
- <text class="grid-label label-se">平静/放松</text>
- <!-- 坐标轴 -->
- <text class="axis-label axis-bottom">消极 ← 情绪效价 → 积极</text>
- <text class="axis-label axis-left">激<br/>活<br/>度</text>
- <!-- 选中的圆点 -->
- <view class="affect-dot" :style="{ left: affectDotX + 'rpx', top: affectDotY + 'rpx' }">
- <view class="affect-dot-inner"></view>
- </view>
- </view>
- <view class="affect-values">
- <text class="affect-val">效价: {{ affectValence }}/10</text>
- <text class="affect-val">激活度: {{ affectArousal }}/10</text>
- </view>
- </view>
- <!-- ===== Plutchik 情绪轮 ===== -->
- <view class="section">
- <text class="section-title">选择你的核心情绪</text>
- <view class="emotion-wheel">
- <view
- v-for="em in plutchikEmotions"
- :key="em.type"
- :class="['emotion-btn', selectedEmotionType === em.type ? 'emotion-active' : '']"
- :style="{ borderColor: selectedEmotionType === em.type ? em.color : '#eee' }"
- @click="selectedEmotionType = em.type">
- <text class="emotion-icon">{{ em.icon }}</text>
- <text class="emotion-label">{{ em.label }}</text>
- </view>
- </view>
- </view>
- <!-- ===== 强度滑条 ===== -->
- <view class="section">
- <text class="section-title">情绪强度</text>
- <view class="slider-row">
- <text class="slider-label">弱</text>
- <slider class="custom-slider" min="1" max="10" :value="intensityLevel" @change="onIntensityChange" activeColor="#FF6B9D" block-size="20" />
- <text class="slider-value">{{ intensityLevel }}</text>
- </view>
- <view class="intensity-hint">{{ intensityHint }}</view>
- </view>
- <!-- ===== 精力水平 ===== -->
- <view class="section">
- <text class="section-title">精力水平</text>
- <view class="slider-row">
- <text class="slider-label">低</text>
- <slider class="custom-slider" min="1" max="10" :value="energyLevel" @change="onEnergyChange" activeColor="#10B981" block-size="20" />
- <text class="slider-value">{{ energyLevel }}</text>
- </view>
- </view>
- <!-- ===== 压力水平 ===== -->
- <view class="section">
- <text class="section-title">压力水平</text>
- <view class="slider-row">
- <text class="slider-label">无压力</text>
- <slider class="custom-slider" min="1" max="10" :value="stressLevel" @change="onStressChange" activeColor="#F59E0B" block-size="20" />
- <text class="slider-value">{{ stressLevel }}</text>
- </view>
- </view>
- <!-- ===== 情绪标签(精细标签) ===== -->
- <view class="section">
- <text class="section-title">情绪标签(可多选)</text>
- <view class="tag-grid">
- <view
- v-for="tag in emotionTags"
- :key="tag"
- :class="['tag-btn', selectedTags.indexOf(tag) !== -1 ? 'tag-active' : '']"
- @click="toggleTag(tag)">
- <text>{{ tag }}</text>
- </view>
- </view>
- <view class="custom-tag-row" v-if="showCustomTagInput">
- <input class="custom-tag-input" v-model="customTag" placeholder="输入自定义情绪" />
- <text class="custom-tag-add" @click="addCustomTag">添加</text>
- </view>
- <text class="custom-tag-trigger" v-if="!showCustomTagInput" @click="showCustomTagInput = true">+ 自定义标签</text>
- </view>
- <!-- 拍照 -->
- <view class="section">
- <text class="section-title">拍一张照片记录心情</text>
- <view class="photo-area" @click="takePhoto">
- <image v-if="photoPath" :src="photoPath" mode="aspectFill" class="photo-preview" />
- <view v-else class="photo-placeholder">
- <text class="photo-icon">📷</text>
- <text class="photo-hint">点击拍照</text>
- </view>
- </view>
- <text class="photo-note">照片仅对自己可见</text>
- </view>
- <!-- 情绪识别 -->
- <view class="section" v-if="photoPath">
- <text class="section-title">\uD83E\uDD16 情绪识别</text>
- <view class="ai-recognize-btn" v-if="!aiResult && !aiAnalyzing" @click="analyzeEmotion">
- <text class="ai-btn-icon">\uD83D\uDD0D</text>
- <text class="ai-btn-text">识别照片中的情绪</text>
- </view>
- <view class="ai-analyzing" v-if="aiAnalyzing">
- <text class="loading-text">分析中...</text>
- </view>
- <view class="ai-result" v-if="aiResult">
- <text class="ai-result-title">识别结果</text>
- <view class="ai-emotion-list">
- <view class="ai-emotion-item" v-for="em in aiResult" :key="em.label">
- <text class="ai-emotion-label">{{ em.label }}</text>
- <view class="ai-emotion-bar-track">
- <view class="ai-emotion-bar-fill" :style="{ width: em.confidence + '%', background: em.color }"></view>
- </view>
- <text class="ai-emotion-value">{{ em.confidence }}%</text>
- </view>
- </view>
- <view class="ai-apply-tags" @click="applyAiTags">
- <text class="ai-apply-text">应用为情绪标签</text>
- </view>
- </view>
- </view>
- <!-- 文字记录 -->
- <view class="section">
- <text class="section-title">记录一下(选填)</text>
- <textarea
- class="note-input"
- v-model="note"
- placeholder="今天发生了什么?"
- maxlength="500" />
- </view>
- <!-- 提交按钮 -->
- <view class="submit-area">
- <button
- class="submit-btn"
- :class="{ 'submit-disabled': !canSubmit }"
- :disabled="!canSubmit"
- @click="submitCheckin">
- <text class="submit-icon">❤</text>
- <text>记录心情</text>
- </button>
- </view>
- <!-- 提交中 loading -->
- <view class="loading-mask" v-if="submitting">
- <view class="loading-box">
- <text class="loading-text">记录中...</text>
- </view>
- </view>
- </view>
- </template>
- <script>
- import config from '../../config'
- export default {
- data() {
- return {
- // --- 2D 效价-激活度网格 ---
- affectValence: 5, // 1-10
- affectArousal: 5, // 1-10
- gridWidth: 360, // rpx, 会在 onReady 中动态计算
- gridHeight: 360,
- // --- Plutchik 情绪轮 ---
- selectedEmotionType: '',
- // --- 滑条 ---
- intensityLevel: 5,
- energyLevel: 6,
- stressLevel: 4,
- // --- 旧字段保留兼容 ---
- selectedWeather: 'sunny', // 自动从效价映射
- selectedTags: [],
- customTag: '',
- showCustomTagInput: false,
- photoPath: '',
- photoUrl: '',
- note: '',
- submitting: false,
- aiAnalyzing: false,
- aiResult: null,
- aiEmotionColors: {
- '开心': '#10B981',
- '平静': '#3B82F6',
- '惊喜': '#8B5CF6',
- '难过': '#6B9BD2',
- '生气': '#EF4444',
- '紧张': '#F59E0B',
- '疲惫': '#9CA3AF',
- '害怕': '#7C3AED'
- },
- stats: null,
- // Plutchik 8 基本情绪
- plutchikEmotions: [
- { type: 'joy', icon: '\uD83D\uDE04', label: '开心', color: '#FFD93D' },
- { type: 'trust', icon: '\uD83E\uDD1D', label: '信任', color: '#6BCB77' },
- { type: 'fear', icon: '\uD83D\uDE31', label: '恐惧', color: '#9B59B6' },
- { type: 'surprise', icon: '\uD83D\uDE32', label: '惊讶', color: '#FF9FF3' },
- { type: 'sadness', icon: '\uD83D\uDE22', label: '悲伤', color: '#6B9BD2' },
- { type: 'disgust', icon: '\uD83E\uDD2E', label: '厌恶', color: '#A0A0A0' },
- { type: 'anger', icon: '\uD83D\uDE21', label: '愤怒', color: '#FF6B6B' },
- { type: 'anticipation',icon: '\uD83E\uDD29', label: '期待', color: '#FF8C42' }
- ],
- emotionTags: ['开心', '期待', '平静', '惊喜', '难过', '生气', '紧张', '无聊', '疲惫', '害怕'],
- // 强度提示文案
- intensityHints: {
- 1: '几乎感觉不到',
- 2: '很微弱',
- 3: '有一点',
- 4: '稍微明显',
- 5: '中等强度',
- 6: '比较明显',
- 7: '相当强烈',
- 8: '很强烈',
- 9: '非常强烈',
- 10: '前所未有的强烈'
- }
- }
- },
- computed: {
- todayDisplay() {
- const now = new Date()
- const y = now.getFullYear()
- const m = now.getMonth() + 1
- const d = now.getDate()
- const weekdays = ['日', '一', '二', '三', '四', '五', '六']
- return y + '年' + m + '月' + d + '日 周' + weekdays[now.getDay()]
- },
- canSubmit() {
- return this.affectValence > 0 && this.selectedEmotionType
- },
- // 圆点位置(rpx)
- affectDotX() {
- // valence 1-10 → 0 to gridWidth
- // 留出边距: 从 20 到 gridWidth-20
- const margin = 20
- const usable = this.gridWidth - 2 * margin
- return margin + ((this.affectValence - 1) / 9) * usable
- },
- affectDotY() {
- // arousal 1-10 → gridHeight to 0 (Y轴反向)
- const margin = 20
- const usable = this.gridHeight - 2 * margin
- return margin + (1 - (this.affectArousal - 1) / 9) * usable
- },
- intensityHint() {
- return this.intensityHints[this.intensityLevel] || ''
- }
- },
- onLoad() {
- this.loadStats()
- },
- onReady() {
- // 估算 grid 尺寸(基于屏幕宽度)
- var sysInfo = uni.getWindowInfo()
- // 页面左右margin=32rpx, section padding=28rpx
- // gridWidth ≈ screenWidth - (32+28)*2 = screenWidth - 120
- var screenWidth = sysInfo.windowWidth
- var gridRpx = screenWidth - 120 // 估算 rpx
- if (gridRpx < 300) gridRpx = 300
- this.gridWidth = gridRpx
- this.gridHeight = gridRpx
- },
- methods: {
- loadStats() {
- const memberId = uni.getStorageSync('currentChildId')
- if (!memberId) return
- uni.request({
- url: config.API_BASE_URL + '/api/mind/checkin/stats',
- method: 'POST',
- data: { memberId },
- success: (res) => {
- if (res.data && res.data.code === 200 && res.data.data) {
- this.stats = res.data.data
- }
- }
- })
- },
- // ===== 2D 效价-激活度网格 =====
- onAffectGridTap(e) {
- // 获取点击相对位置
- var rect = e.target
- // 使用 touches 或 detail
- var x = e.detail && e.detail.x ? e.detail.x : 0
- var y = e.detail && e.detail.y ? e.detail.y : 0
- // fallback: 从 pageX/pageY 计算(推荐)
- if (e.detail && e.detail.x !== undefined) {
- // uni-app 在 @tap 事件中 detail 有 x/y 表示相对于目标元素的偏移
- x = e.detail.x
- y = e.detail.y
- }
- // px → rpx 转换
- var sysInfo = uni.getWindowInfo()
- var scale = 750 / sysInfo.windowWidth
- // 直接使用事件中的偏移(px),然后转 rpx
- // 注意: 如果 e.detail 没有 x/y,尝试从 e.touches 获取
- if ((x === 0 && y === 0) || !e.detail.x) {
- var touch = e.touches ? e.touches[0] : null
- if (touch) {
- // 需要根据 grid 的位置计算相对偏移
- x = touch.x || 0
- y = touch.y || 0
- }
- }
- // 直接用 rpx 计算,简化版:click 的位置相对 grid 容器
- // uni-app 的 @tap 的 detail = {x, y} 是相对于被点击元素的 px 值
- var pxX = e.detail.x || 0
- var pxY = e.detail.y || 0
- var rpxX = pxX * scale
- var rpxY = pxY * scale
- // 约束在有效范围内
- var margin = 20
- var maxRpx = this.gridWidth - margin
- if (rpxX < margin) rpxX = margin
- if (rpxX > maxRpx) rpxX = maxRpx
- if (rpxY < margin) rpxY = margin
- if (rpxY > this.gridHeight - margin) rpxY = this.gridHeight - margin
- // 映射到 1-10
- var usable = this.gridWidth - 2 * margin
- var val = Math.round(1 + ((rpxX - margin) / usable) * 9)
- var aro = Math.round(10 - ((rpxY - margin) / usable) * 9)
- this.affectValence = Math.max(1, Math.min(10, val))
- this.affectArousal = Math.max(1, Math.min(10, aro))
- // 自动映射 moodWeather 用于向后兼容
- this.updateWeatherFromAffect()
- },
- updateWeatherFromAffect() {
- // 效价高 + 激活度高 = rainbow
- if (this.affectValence >= 7 && this.affectArousal >= 7) {
- this.selectedWeather = 'rainbow'
- } else if (this.affectValence >= 6) {
- // 效价高 = sunny
- this.selectedWeather = 'sunny'
- } else if (this.affectValence <= 4 && this.affectArousal >= 7) {
- // 效价低 + 激活度高 = stormy
- this.selectedWeather = 'stormy'
- } else if (this.affectValence <= 4 && this.affectArousal <= 4) {
- // 效价低 + 激活度低 = rainy
- this.selectedWeather = 'rainy'
- } else {
- this.selectedWeather = 'cloudy'
- }
- },
- // ===== 滑条变化 =====
- onIntensityChange(e) {
- this.intensityLevel = e.detail.value
- },
- onEnergyChange(e) {
- this.energyLevel = e.detail.value
- },
- onStressChange(e) {
- this.stressLevel = e.detail.value
- },
- // ===== 情绪标签 =====
- toggleTag(tag) {
- const idx = this.selectedTags.indexOf(tag)
- if (idx !== -1) {
- this.selectedTags.splice(idx, 1)
- } else {
- this.selectedTags.push(tag)
- }
- },
- addCustomTag() {
- const tag = this.customTag.trim()
- if (tag && this.emotionTags.indexOf(tag) === -1 && this.selectedTags.indexOf(tag) === -1) {
- this.selectedTags.push(tag)
- }
- this.customTag = ''
- this.showCustomTagInput = false
- },
- // ===== 拍照 =====
- takePhoto() {
- uni.chooseImage({
- count: 1,
- sourceType: ['camera', 'album'],
- success: (res) => {
- this.photoPath = res.tempFilePaths[0]
- this.aiResult = null
- }
- })
- },
- analyzeEmotion() {
- var self = this
- if (!self.photoPath) {
- uni.showToast({ title: '请先拍照', icon: 'none' })
- return
- }
- self.aiAnalyzing = true
- self.aiResult = null
- uni.uploadFile({
- url: config.API_BASE_URL + '/api/media/upload',
- filePath: self.photoPath,
- name: 'file',
- success: function(uploadRes) {
- var photoUrl = ''
- try {
- var data = JSON.parse(uploadRes.data)
- photoUrl = data.data || data.url || ''
- self.photoUrl = photoUrl
- } catch (e) {
- photoUrl = uploadRes.data || ''
- self.photoUrl = photoUrl
- }
- if (!photoUrl) {
- self.aiAnalyzing = false
- uni.showToast({ title: '照片上传失败', icon: 'none' })
- return
- }
- uni.request({
- url: config.API_BASE_URL + '/api/mind/checkin/emotion/recognize',
- method: 'POST',
- data: { image_url: photoUrl },
- header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
- success: function(res) {
- self.aiAnalyzing = false
- if (res.data && res.data.code === 200 && res.data.data && res.data.data.length > 0) {
- self.aiResult = res.data.data
- } else {
- self.aiResult = []
- uni.showToast({ title: '识别失败,请重试', icon: 'none' })
- }
- },
- fail: function() {
- self.aiAnalyzing = false
- uni.showToast({ title: '网络错误', icon: 'none' })
- }
- })
- },
- fail: function() {
- self.aiAnalyzing = false
- uni.showToast({ title: '照片上传失败', icon: 'none' })
- }
- })
- },
- applyAiTags() {
- if (!this.aiResult) return
- var detected = []
- for (var i = 0; i < this.aiResult.length; i++) {
- var em = this.aiResult[i]
- if (em.confidence >= 20 && this.emotionTags.indexOf(em.label) !== -1) {
- detected.push(em.label)
- }
- }
- var merged = this.selectedTags.slice()
- for (var j = 0; j < detected.length; j++) {
- if (merged.indexOf(detected[j]) === -1) {
- merged.push(detected[j])
- }
- }
- this.selectedTags = merged
- uni.showToast({ title: '已应用情绪标签', icon: 'success' })
- },
- // ===== 提交 =====
- submitCheckin() {
- if (!this.canSubmit || this.submitting) return
- this.submitting = true
- const memberId = uni.getStorageSync('currentChildId')
- const doSubmit = (photoUrl) => {
- uni.request({
- url: config.API_BASE_URL + '/api/mind/checkin/create',
- method: 'POST',
- data: {
- memberId: memberId,
- // 旧字段向后兼容
- moodWeather: this.selectedWeather,
- emotionTags: JSON.stringify(this.selectedTags),
- photoUrl: photoUrl || '',
- note: this.note,
- // Phase 1.1 精细情绪字段
- moodScore: this.affectValence,
- emotionType: this.selectedEmotionType,
- stressLevel: this.stressLevel,
- arousalLevel: this.affectArousal,
- energyLevel: this.energyLevel
- },
- success: (res) => {
- this.submitting = false
- if (res.data && res.data.code === 200) {
- uni.showToast({
- title: '+5 心能量',
- icon: 'none',
- duration: 2000
- })
- setTimeout(() => {
- uni.navigateTo({ url: '/pages/mind/emotion-trend' })
- }, 1500)
- } else {
- uni.showToast({ title: res.data && res.data.message || '提交失败', icon: 'none' })
- }
- },
- fail: () => {
- this.submitting = false
- uni.showToast({ title: '网络错误', icon: 'none' })
- }
- })
- }
- if (this.photoUrl) {
- doSubmit(this.photoUrl)
- } else if (this.photoPath) {
- uni.uploadFile({
- url: config.API_BASE_URL + '/api/media/upload',
- filePath: this.photoPath,
- name: 'file',
- success: (uploadRes) => {
- let photoUrl = ''
- try {
- const data = JSON.parse(uploadRes.data)
- photoUrl = data.data || data.url || ''
- } catch (e) {
- photoUrl = uploadRes.data
- }
- this.photoUrl = photoUrl
- doSubmit(photoUrl)
- },
- fail: () => doSubmit('')
- })
- } else {
- doSubmit('')
- }
- }
- }
- }
- </script>
- <style scoped>
- .checkin-container {
- min-height: 100vh;
- background: linear-gradient(180deg, #FFF0F5 0%, #FFFFFF 100%);
- padding-bottom: 40rpx;
- }
- .checkin-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 30rpx 32rpx;
- }
- .checkin-date {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- }
- .streak-badge {
- display: flex;
- align-items: center;
- background: linear-gradient(135deg, #FF6B9D, #FF8CB3);
- border-radius: 30rpx;
- padding: 8rpx 24rpx;
- }
- .streak-icon {
- font-size: 28rpx;
- margin-right: 8rpx;
- }
- .streak-text {
- font-size: 24rpx;
- color: #fff;
- font-weight: 500;
- }
- .section {
- margin: 20rpx 32rpx;
- background: #fff;
- border-radius: 20rpx;
- padding: 28rpx;
- box-shadow: 0 2rpx 12rpx rgba(255, 107, 157, 0.08);
- }
- .section-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 20rpx;
- display: block;
- }
- /* ===== 2D 效价-激活度网格 ===== */
- .affect-grid {
- position: relative;
- width: 100%;
- /* height is set dynamically equal to width via aspect-ratio or inline style */
- aspect-ratio: 1;
- background: linear-gradient(135deg, #FFF5F5 0%, #FFF0FF 25%, #F0F0FF 50%, #F0FFF4 75%, #FFFFF0 100%);
- border-radius: 16rpx;
- border: 2rpx solid #E8E8E8;
- margin-bottom: 16rpx;
- /* Ensure the grid has a defined height — fallback */
- min-height: 300rpx;
- /* Overlap grid lines for visual clarity */
- overflow: hidden;
- }
- /* 网格线:用伪元素或背景渐变 */
- .affect-grid::before {
- content: '';
- position: absolute;
- left: 50%;
- top: 0;
- bottom: 0;
- width: 2rpx;
- background: rgba(0,0,0,0.06);
- transform: translateX(-50%);
- pointer-events: none;
- }
- .affect-grid::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 0;
- right: 0;
- height: 2rpx;
- background: rgba(0,0,0,0.06);
- transform: translateY(-50%);
- pointer-events: none;
- }
- .grid-label {
- position: absolute;
- font-size: 22rpx;
- color: rgba(0,0,0,0.25);
- pointer-events: none;
- }
- .label-nw { top: 20rpx; left: 20rpx; }
- .label-ne { top: 20rpx; right: 20rpx; }
- .label-sw { bottom: 20rpx; left: 20rpx; }
- .label-se { bottom: 20rpx; right: 20rpx; }
- .axis-label {
- position: absolute;
- font-size: 20rpx;
- color: rgba(0,0,0,0.2);
- pointer-events: none;
- }
- .axis-bottom {
- bottom: 4rpx;
- left: 50%;
- transform: translateX(-50%);
- white-space: nowrap;
- }
- .axis-left {
- left: 4rpx;
- top: 50%;
- transform: translateY(-50%);
- font-size: 18rpx;
- line-height: 1.4;
- text-align: center;
- }
- .affect-dot {
- position: absolute;
- width: 44rpx;
- height: 44rpx;
- margin-left: -22rpx;
- margin-top: -22rpx;
- pointer-events: none;
- z-index: 2;
- }
- .affect-dot-inner {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: #FF6B9D;
- box-shadow: 0 0 12rpx rgba(255, 107, 157, 0.5);
- border: 4rpx solid #fff;
- box-sizing: border-box;
- }
- .affect-values {
- display: flex;
- justify-content: center;
- gap: 40rpx;
- }
- .affect-val {
- font-size: 24rpx;
- color: #888;
- }
- /* ===== Plutchik 情绪轮 ===== */
- .emotion-wheel {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- justify-content: center;
- }
- .emotion-btn {
- display: flex;
- flex-direction: column;
- align-items: center;
- width: 130rpx;
- padding: 16rpx 8rpx;
- border-radius: 20rpx;
- border: 3rpx solid #eee;
- background: #FAFAFA;
- transition: all 0.2s;
- }
- .emotion-active {
- background: #FFF0F5;
- transform: scale(1.05);
- }
- .emotion-icon {
- font-size: 48rpx;
- margin-bottom: 6rpx;
- }
- .emotion-label {
- font-size: 24rpx;
- color: #555;
- }
- /* ===== 滑条 ===== */
- .slider-row {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .slider-label {
- font-size: 24rpx;
- color: #999;
- white-space: nowrap;
- width: 64rpx;
- }
- .custom-slider {
- flex: 1;
- }
- .slider-value {
- font-size: 28rpx;
- font-weight: 600;
- color: #FF6B9D;
- width: 40rpx;
- text-align: center;
- }
- .intensity-hint {
- font-size: 22rpx;
- color: #aaa;
- text-align: center;
- margin-top: 4rpx;
- }
- /* ===== 情绪标签 ===== */
- .tag-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- }
- .tag-btn {
- padding: 12rpx 28rpx;
- border-radius: 30rpx;
- background: #F5F5F5;
- font-size: 26rpx;
- color: #666;
- border: 2rpx solid transparent;
- }
- .tag-active {
- background: #FFF0F5;
- color: #FF6B9D;
- border-color: #FF6B9D;
- }
- .custom-tag-row {
- display: flex;
- align-items: center;
- margin-top: 16rpx;
- gap: 16rpx;
- }
- .custom-tag-input {
- flex: 1;
- height: 60rpx;
- border: 2rpx solid #E0E0E0;
- border-radius: 12rpx;
- padding: 0 16rpx;
- font-size: 26rpx;
- }
- .custom-tag-add {
- padding: 12rpx 24rpx;
- background: #FF6B9D;
- color: #fff;
- border-radius: 12rpx;
- font-size: 26rpx;
- }
- .custom-tag-trigger {
- display: inline-block;
- margin-top: 16rpx;
- color: #FF6B9D;
- font-size: 26rpx;
- }
- /* ===== 拍照 ===== */
- .photo-area {
- width: 200rpx;
- height: 200rpx;
- border-radius: 16rpx;
- overflow: hidden;
- background: #F5F5F5;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .photo-preview {
- width: 100%;
- height: 100%;
- }
- .photo-placeholder {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .photo-icon {
- font-size: 60rpx;
- }
- .photo-hint {
- font-size: 24rpx;
- color: #999;
- margin-top: 8rpx;
- }
- .photo-note {
- display: block;
- font-size: 22rpx;
- color: #999;
- margin-top: 12rpx;
- }
- /* ===== 情绪识别 ===== */
- .ai-recognize-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 20rpx;
- background: linear-gradient(135deg, #8B5CF6, #A78BFA);
- border-radius: 16rpx;
- cursor: pointer;
- }
- .ai-btn-icon {
- font-size: 32rpx;
- margin-right: 10rpx;
- }
- .ai-btn-text {
- font-size: 26rpx;
- color: #fff;
- font-weight: 500;
- }
- .ai-recognize-btn:active {
- opacity: 0.8;
- }
- .ai-analyzing {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 30rpx 0;
- }
- .loading-text {
- font-size: 26rpx;
- color: #8B5CF6;
- }
- .ai-result {
- margin-top: 16rpx;
- }
- .ai-result-title {
- font-size: 24rpx;
- color: #666;
- margin-bottom: 12rpx;
- display: block;
- }
- .ai-emotion-list {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
- }
- .ai-emotion-item {
- display: flex;
- align-items: center;
- gap: 12rpx;
- }
- .ai-emotion-label {
- font-size: 24rpx;
- color: #333;
- width: 64rpx;
- flex-shrink: 0;
- }
- .ai-emotion-bar-track {
- flex: 1;
- height: 16rpx;
- background: #F0F0F0;
- border-radius: 8rpx;
- overflow: hidden;
- }
- .ai-emotion-bar-fill {
- height: 100%;
- border-radius: 8rpx;
- transition: width 0.5s;
- }
- .ai-emotion-value {
- font-size: 22rpx;
- color: #999;
- width: 56rpx;
- text-align: right;
- }
- .ai-apply-tags {
- margin-top: 20rpx;
- padding: 16rpx;
- background: #F5F0FF;
- border-radius: 12rpx;
- text-align: center;
- cursor: pointer;
- }
- .ai-apply-text {
- font-size: 26rpx;
- color: #8B5CF6;
- font-weight: 500;
- }
- .ai-apply-tags:active {
- opacity: 0.7;
- }
- .note-input {
- width: 100%;
- min-height: 140rpx;
- background: #F9F9F9;
- border-radius: 12rpx;
- padding: 20rpx;
- font-size: 26rpx;
- color: #333;
- box-sizing: border-box;
- }
- .submit-area {
- margin: 40rpx 32rpx;
- }
- .submit-btn {
- width: 100%;
- height: 88rpx;
- line-height: 88rpx;
- background: linear-gradient(135deg, #FF6B9D, #FF8CB3);
- color: #fff;
- border-radius: 44rpx;
- font-size: 30rpx;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .submit-disabled {
- opacity: 0.5;
- }
- .submit-icon {
- margin-right: 8rpx;
- }
- .loading-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.3);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 999;
- }
- .loading-box {
- background: #fff;
- padding: 40rpx 60rpx;
- border-radius: 20rpx;
- }
- .loading-text {
- font-size: 28rpx;
- color: #666;
- }
- </style>
|