增加喜喜 概念框架
This commit is contained in:
235
pages-subpackage/xixi/components/xixi-dating-advice.vue
Normal file
235
pages-subpackage/xixi/components/xixi-dating-advice.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<scroll-view class="panel-scroll" scroll-y>
|
||||
<view class="head-card">
|
||||
<text class="head-title">今日约会建议</text>
|
||||
<text class="head-date">{{ todayLabel }}</text>
|
||||
<text class="head-note">以下内容根据你在「我是谁」「约会准备」中的填写自动生成,仅供当日参考。</text>
|
||||
</view>
|
||||
|
||||
<view v-if="emptyHint" class="empty-wrap">
|
||||
<text class="empty-title">还没有足够的信息</text>
|
||||
<text class="empty-desc">请先在「我是谁」和「约会准备」里填写内容,保存后再来查看约会建议。</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="list">
|
||||
<view v-for="(line, idx) in lines" :key="idx" class="advice-item">
|
||||
<text class="advice-index">{{ idx + 1 }}</text>
|
||||
<text class="advice-text">{{ line }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const KEY_WHO = 'xixi_who_am_i';
|
||||
const KEY_MEET = 'xixi_first_meeting';
|
||||
|
||||
function formatToday() {
|
||||
const d = new Date();
|
||||
const wk = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 星期${wk[d.getDay()]}`;
|
||||
}
|
||||
|
||||
function trimSnippet(text, max) {
|
||||
if (text == null || text === '') return '';
|
||||
const s = String(text).trim().replace(/\s+/g, ' ');
|
||||
if (!s) return '';
|
||||
return s.length > max ? s.slice(0, max) + '…' : s;
|
||||
}
|
||||
|
||||
function buildLines(who, meet) {
|
||||
const w = who && typeof who === 'object' ? who : {};
|
||||
const m = meet && typeof meet === 'object' ? meet : {};
|
||||
const hasWho = !!(w.selfIntro && String(w.selfIntro).trim()) || !!(w.partnerExpect && String(w.partnerExpect).trim());
|
||||
const hasMeet =
|
||||
!!(m.relationshipStatus && String(m.relationshipStatus).trim()) || !!(m.meetingGoal && String(m.meetingGoal).trim());
|
||||
if (!hasWho && !hasMeet) {
|
||||
return { empty: true, lines: [] };
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push('下面几条结合你在「我是谁」「约会准备」中的填写整理,方便你出门前快速过一遍思路。');
|
||||
|
||||
if (m.relationshipStatus) {
|
||||
const sn = trimSnippet(m.relationshipStatus, 56);
|
||||
lines.push(
|
||||
`关系阶段你描述为「${sn}」:聊天节奏与对方投入程度尽量匹配这一阶段,不必急于定义关系,也避免过度冷淡让对方不安。`,
|
||||
);
|
||||
}
|
||||
|
||||
if (m.meetingGoal) {
|
||||
const sn = trimSnippet(m.meetingGoal, 72);
|
||||
lines.push(
|
||||
`本次见面你希望「${sn}」:可以准备 1~2 个轻松的小话题或问题,自然带入即可,不必像完成任务一样逐项核对。`,
|
||||
);
|
||||
}
|
||||
|
||||
if (w.selfIntro) {
|
||||
lines.push(
|
||||
'「我是谁」里你写到的特点:今天选 1~2 点最想被对方感受到的,用小事或简短例子带过即可,留一点空间让对方慢慢了解你。',
|
||||
);
|
||||
}
|
||||
|
||||
if (w.partnerExpect) {
|
||||
lines.push(
|
||||
'关于对另一半的期待:更适合在互动中观察对方的言行是否合拍,用好奇和分享代替盘问,减少「考察感」。',
|
||||
);
|
||||
}
|
||||
|
||||
lines.push('见面时多倾听、适当复述对方话里的关键词,让对方感到被听懂;适当留白比一直找话题更重要。');
|
||||
lines.push('若短暂冷场,可以从环境、共同经历或轻松八卦切入,不必为「必须有趣」而焦虑。');
|
||||
lines.push('结束前可以自然聊聊今天的感受、是否愉快,再顺势看下次是否方便再约,不给对方过大压力。');
|
||||
|
||||
return { empty: false, lines };
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'XixiDatingAdvice',
|
||||
props: {
|
||||
refreshTick: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
todayLabel: formatToday(),
|
||||
emptyHint: true,
|
||||
lines: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
refreshTick() {
|
||||
this.regenerate();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.regenerate();
|
||||
},
|
||||
methods: {
|
||||
regenerate() {
|
||||
this.todayLabel = formatToday();
|
||||
let who = {};
|
||||
let meet = {};
|
||||
try {
|
||||
const rawW = uni.getStorageSync(KEY_WHO);
|
||||
if (rawW && typeof rawW === 'object') who = rawW;
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
const rawM = uni.getStorageSync(KEY_MEET);
|
||||
if (rawM && typeof rawM === 'object') meet = rawM;
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
const { empty, lines } = buildLines(who, meet);
|
||||
this.emptyHint = empty;
|
||||
this.lines = lines;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.panel-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx 40rpx;
|
||||
}
|
||||
|
||||
.head-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.06);
|
||||
}
|
||||
|
||||
.head-title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.head-date {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #e91e8c;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.head-note {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.empty-wrap {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 48rpx 32rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.04);
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.advice-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(233, 30, 140, 0.06);
|
||||
}
|
||||
|
||||
.advice-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.advice-index {
|
||||
flex-shrink: 0;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
line-height: 44rpx;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #ff6b9d, #e91e8c);
|
||||
border-radius: 12rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.advice-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #444;
|
||||
line-height: 1.65;
|
||||
}
|
||||
</style>
|
||||
111
pages-subpackage/xixi/components/xixi-diagnosis.vue
Normal file
111
pages-subpackage/xixi/components/xixi-diagnosis.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<scroll-view class="panel-scroll" scroll-y>
|
||||
<view v-if="!items.length" class="empty-wrap">
|
||||
<text class="empty-title">暂无诊断结果</text>
|
||||
<text class="empty-desc">请在「幸福相遇」中完成录音,并点击「分析录音」</text>
|
||||
</view>
|
||||
<view v-for="(row, idx) in items" :key="row.id" class="diag-card">
|
||||
<view class="diag-head">
|
||||
<text class="diag-label">诊断 {{ items.length - idx }}</text>
|
||||
<text class="diag-time">{{ formatDate(row.createdAt) }}</text>
|
||||
</view>
|
||||
<text class="diag-body">{{ row.analysisText }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const KEY = 'xixi_recordings';
|
||||
|
||||
export default {
|
||||
name: 'XixiDiagnosis',
|
||||
props: {
|
||||
refreshTick: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
refreshTick() {
|
||||
this.load();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
try {
|
||||
const list = uni.getStorageSync(KEY);
|
||||
const arr = Array.isArray(list) ? list : [];
|
||||
this.items = arr.filter((r) => r && r.analysisText);
|
||||
} catch (e) {
|
||||
this.items = [];
|
||||
}
|
||||
},
|
||||
formatDate(ts) {
|
||||
const d = new Date(ts);
|
||||
const p = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.panel-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx 32rpx;
|
||||
}
|
||||
.empty-wrap {
|
||||
padding: 80rpx 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.empty-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.diag-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.08);
|
||||
border-left: 8rpx solid #ff6b9d;
|
||||
}
|
||||
.diag-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.diag-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #e91e8c;
|
||||
}
|
||||
.diag-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.diag-body {
|
||||
font-size: 28rpx;
|
||||
color: #444;
|
||||
line-height: 1.65;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
108
pages-subpackage/xixi/components/xixi-first-meeting.vue
Normal file
108
pages-subpackage/xixi/components/xixi-first-meeting.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<scroll-view class="panel-scroll" scroll-y>
|
||||
<view class="card">
|
||||
<text class="label">现在两个人的关系</text>
|
||||
<textarea
|
||||
class="area area-sm"
|
||||
v-model="local.relationshipStatus"
|
||||
placeholder="例如:刚认识 / 暧昧期 / 已确定关系 / 异地恋…"
|
||||
maxlength="800"
|
||||
:show-confirm-bar="false"
|
||||
/>
|
||||
</view>
|
||||
<view class="card">
|
||||
<text class="label">本次见面要达到的结果</text>
|
||||
<textarea
|
||||
class="area"
|
||||
v-model="local.meetingGoal"
|
||||
placeholder="例如:更了解对方家庭观、约定下次约会、化解上次误会…"
|
||||
maxlength="2000"
|
||||
:show-confirm-bar="false"
|
||||
/>
|
||||
</view>
|
||||
<button class="btn-save" type="primary" @click="save">保存</button>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const KEY = 'xixi_first_meeting';
|
||||
|
||||
export default {
|
||||
name: 'XixiFirstMeeting',
|
||||
data() {
|
||||
return {
|
||||
local: {
|
||||
relationshipStatus: '',
|
||||
meetingGoal: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
try {
|
||||
const raw = uni.getStorageSync(KEY);
|
||||
if (raw && typeof raw === 'object') {
|
||||
this.local.relationshipStatus = raw.relationshipStatus || '';
|
||||
this.local.meetingGoal = raw.meetingGoal || '';
|
||||
}
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
save() {
|
||||
try {
|
||||
uni.setStorageSync(KEY, { ...this.local });
|
||||
uni.showToast({ title: '已保存', icon: 'success' });
|
||||
this.$emit('saved');
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.panel-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx 32rpx;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.06);
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.area {
|
||||
width: 100%;
|
||||
min-height: 200rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx;
|
||||
background: #fff8fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.area-sm {
|
||||
min-height: 120rpx;
|
||||
}
|
||||
.btn-save {
|
||||
margin-top: 12rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #ff6b9d, #e91e8c);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
278
pages-subpackage/xixi/components/xixi-record.vue
Normal file
278
pages-subpackage/xixi/components/xixi-record.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<scroll-view class="panel-scroll" scroll-y>
|
||||
<view class="toolbar">
|
||||
<button
|
||||
v-if="!recording"
|
||||
class="btn-round"
|
||||
type="primary"
|
||||
:disabled="!recorderSupported"
|
||||
@click="startRecord"
|
||||
>
|
||||
开始录音
|
||||
</button>
|
||||
<button v-else class="btn-round btn-stop" type="warn" @click="stopRecord">结束录音</button>
|
||||
<text v-if="!recorderSupported" class="hint">当前环境不支持录音,请使用微信小程序或 App</text>
|
||||
<text v-else-if="recording" class="hint recording">正在录音…</text>
|
||||
</view>
|
||||
|
||||
<view class="section-title">录音列表</view>
|
||||
<view v-if="!recordings.length" class="empty">暂无录音,点击「开始录音」记录相处时刻</view>
|
||||
<view v-for="item in recordings" :key="item.id" class="rec-card">
|
||||
<view class="rec-head">
|
||||
<text class="rec-time">{{ formatDate(item.createdAt) }}</text>
|
||||
<text class="rec-dur">{{ formatDuration(item.duration) }}</text>
|
||||
</view>
|
||||
<view class="rec-actions">
|
||||
<button class="btn-mini" size="mini" @click="play(item)" :disabled="!item.tempFilePath">试听</button>
|
||||
<button
|
||||
class="btn-mini primary"
|
||||
size="mini"
|
||||
:disabled="item.analyzing"
|
||||
@click="analyze(item)"
|
||||
>
|
||||
{{ item.analyzing ? '分析中…' : item.analysisText ? '重新分析' : '分析录音' }}
|
||||
</button>
|
||||
</view>
|
||||
<text v-if="item.analysisText" class="rec-preview">{{ truncate(item.analysisText, 80) }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const KEY = 'xixi_recordings';
|
||||
|
||||
export default {
|
||||
name: 'XixiRecord',
|
||||
data() {
|
||||
return {
|
||||
recordings: [],
|
||||
recording: false,
|
||||
recorderSupported: false,
|
||||
recorderManager: null,
|
||||
innerAudio: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.recorderSupported = typeof uni.getRecorderManager === 'function';
|
||||
this.loadList();
|
||||
if (this.recorderSupported) {
|
||||
this.recorderManager = uni.getRecorderManager();
|
||||
this.recorderManager.onStop((res) => {
|
||||
this.recording = false;
|
||||
const duration = res.duration || 0;
|
||||
const tempFilePath = res.tempFilePath || '';
|
||||
if (!tempFilePath) {
|
||||
uni.showToast({ title: '未获取到录音文件', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const item = {
|
||||
id: `r_${Date.now()}`,
|
||||
tempFilePath,
|
||||
duration,
|
||||
createdAt: Date.now(),
|
||||
analysisText: '',
|
||||
analyzing: false,
|
||||
};
|
||||
this.recordings.unshift(item);
|
||||
this.persist();
|
||||
uni.showToast({ title: '已保存录音', icon: 'success' });
|
||||
this.$emit('recordings-changed');
|
||||
});
|
||||
this.recorderManager.onError(() => {
|
||||
this.recording = false;
|
||||
uni.showToast({ title: '录音出错', icon: 'none' });
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopAudio();
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
try {
|
||||
const list = uni.getStorageSync(KEY);
|
||||
this.recordings = Array.isArray(list) ? list : [];
|
||||
} catch (e) {
|
||||
this.recordings = [];
|
||||
}
|
||||
},
|
||||
persist() {
|
||||
try {
|
||||
uni.setStorageSync(KEY, this.recordings);
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
startRecord() {
|
||||
if (!this.recorderSupported || !this.recorderManager) return;
|
||||
this.recording = true;
|
||||
try {
|
||||
this.recorderManager.start({
|
||||
duration: 600000,
|
||||
sampleRate: 44100,
|
||||
numberOfChannels: 1,
|
||||
encodeBitRate: 96000,
|
||||
format: 'mp3',
|
||||
});
|
||||
} catch (e) {
|
||||
this.recording = false;
|
||||
uni.showToast({ title: '无法开始录音', icon: 'none' });
|
||||
}
|
||||
},
|
||||
stopRecord() {
|
||||
if (this.recorderManager && this.recording) {
|
||||
this.recorderManager.stop();
|
||||
}
|
||||
},
|
||||
play(item) {
|
||||
if (!item.tempFilePath) return;
|
||||
this.stopAudio();
|
||||
const ctx = uni.createInnerAudioContext();
|
||||
this.innerAudio = ctx;
|
||||
ctx.src = item.tempFilePath;
|
||||
ctx.onEnded(() => this.stopAudio());
|
||||
ctx.onError(() => {
|
||||
uni.showToast({ title: '播放失败', icon: 'none' });
|
||||
this.stopAudio();
|
||||
});
|
||||
ctx.play();
|
||||
},
|
||||
stopAudio() {
|
||||
if (this.innerAudio) {
|
||||
try {
|
||||
this.innerAudio.stop();
|
||||
this.innerAudio.destroy();
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
this.innerAudio = null;
|
||||
}
|
||||
},
|
||||
analyze(item) {
|
||||
const idx = this.recordings.findIndex((r) => r.id === item.id);
|
||||
if (idx < 0) return;
|
||||
const cur = this.recordings[idx];
|
||||
this.recordings.splice(idx, 1, { ...cur, analyzing: true });
|
||||
const who = uni.getStorageSync('xixi_who_am_i') || {};
|
||||
const meet = uni.getStorageSync('xixi_first_meeting') || {};
|
||||
const id = item.id;
|
||||
setTimeout(() => {
|
||||
const i = this.recordings.findIndex((r) => r.id === id);
|
||||
if (i < 0) return;
|
||||
const row = this.recordings[i];
|
||||
const sec = Math.max(1, Math.round((row.duration || 0) / 1000));
|
||||
const text = this.buildMockAnalysis(sec, who, meet);
|
||||
this.recordings.splice(i, 1, { ...row, analysisText: text, analyzing: false });
|
||||
this.persist();
|
||||
uni.showToast({ title: '分析完成', icon: 'success' });
|
||||
this.$emit('recordings-changed');
|
||||
}, 900);
|
||||
},
|
||||
buildMockAnalysis(seconds, who, meet) {
|
||||
const intro = who.selfIntro ? '你已填写「我是谁」,建议在真实对话中自然带出其中 1~2 点,避免像背稿。' : '尚未填写「我是谁」,补全后诊断可结合你的自我描述更贴切。';
|
||||
const goal = meet.meetingGoal ? `结合你写的见面目标「${meet.meetingGoal.slice(0, 40)}${meet.meetingGoal.length > 40 ? '…' : ''}」,可回顾本次对话是否朝该方向推进。` : '可补充「本次见面要达到的结果」,便于对照复盘。';
|
||||
return (
|
||||
`【喜喜 · AI 相处诊断】(示例)\n\n` +
|
||||
`· 录音时长约 ${seconds} 秒。\n` +
|
||||
`· ${intro}\n` +
|
||||
`· ${goal}\n\n` +
|
||||
`沟通氛围:语气平稳、留白适中更易建立信任;若语速偏快,可适当停顿给对方回应空间。\n` +
|
||||
`下一步:在「诊断结果」页查看完整记录;接入语音转写与大模型后,将替换为基于真实内容的分析。`
|
||||
);
|
||||
},
|
||||
formatDate(ts) {
|
||||
const d = new Date(ts);
|
||||
const p = (n) => (n < 10 ? '0' + n : '' + n);
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
||||
},
|
||||
formatDuration(ms) {
|
||||
const s = Math.round((ms || 0) / 1000);
|
||||
const m = Math.floor(s / 60);
|
||||
const r = s % 60;
|
||||
return m > 0 ? `${m}分${r}秒` : `${r}秒`;
|
||||
},
|
||||
truncate(s, n) {
|
||||
if (!s) return '';
|
||||
return s.length <= n ? s : s.slice(0, n) + '…';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.panel-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx 32rpx;
|
||||
}
|
||||
.toolbar {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.btn-round {
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #ff6b9d, #e91e8c);
|
||||
border: none;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.btn-stop {
|
||||
background: linear-gradient(135deg, #ff8a65, #f4511e);
|
||||
}
|
||||
.hint {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
.hint.recording {
|
||||
color: #e91e8c;
|
||||
font-weight: 600;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.empty {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
padding: 40rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
.rec-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.06);
|
||||
}
|
||||
.rec-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.rec-time {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
.rec-dur {
|
||||
font-size: 24rpx;
|
||||
color: #e91e8c;
|
||||
}
|
||||
.rec-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.btn-mini.primary {
|
||||
background: #fff0f5;
|
||||
color: #e91e8c;
|
||||
border: 1rpx solid #ffb6c1;
|
||||
}
|
||||
.rec-preview {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
105
pages-subpackage/xixi/components/xixi-who-am-i.vue
Normal file
105
pages-subpackage/xixi/components/xixi-who-am-i.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<scroll-view class="panel-scroll" scroll-y>
|
||||
<view class="card">
|
||||
<text class="label">我是谁(恋爱相关的自我描述)</text>
|
||||
<textarea
|
||||
class="area"
|
||||
v-model="local.selfIntro"
|
||||
placeholder="例如:我的性格、价值观、在关系里看重什么…"
|
||||
maxlength="2000"
|
||||
:show-confirm-bar="false"
|
||||
/>
|
||||
</view>
|
||||
<view class="card">
|
||||
<text class="label">对另一半的要求</text>
|
||||
<textarea
|
||||
class="area"
|
||||
v-model="local.partnerExpect"
|
||||
placeholder="例如:希望对方具备的特质、相处方式、底线与期待…"
|
||||
maxlength="2000"
|
||||
:show-confirm-bar="false"
|
||||
/>
|
||||
</view>
|
||||
<button class="btn-save" type="primary" @click="save">保存</button>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const KEY = 'xixi_who_am_i';
|
||||
|
||||
export default {
|
||||
name: 'XixiWhoAmI',
|
||||
data() {
|
||||
return {
|
||||
local: {
|
||||
selfIntro: '',
|
||||
partnerExpect: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
try {
|
||||
const raw = uni.getStorageSync(KEY);
|
||||
if (raw && typeof raw === 'object') {
|
||||
this.local.selfIntro = raw.selfIntro || '';
|
||||
this.local.partnerExpect = raw.partnerExpect || '';
|
||||
}
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
save() {
|
||||
try {
|
||||
uni.setStorageSync(KEY, { ...this.local });
|
||||
uni.showToast({ title: '已保存', icon: 'success' });
|
||||
this.$emit('saved');
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '保存失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.panel-scroll {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx 32rpx;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.06);
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.area {
|
||||
width: 100%;
|
||||
min-height: 220rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx;
|
||||
background: #fff8fa;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.btn-save {
|
||||
margin-top: 12rpx;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(135deg, #ff6b9d, #e91e8c);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user