397 lines
11 KiB
Vue
397 lines
11 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
|
||
<view class="section-title">选择逻辑模型</view>
|
||
<scroll-view scroll-x class="strategy-scroll" :show-scrollbar="false">
|
||
<view class="strategy-row">
|
||
<view
|
||
v-for="(item, index) in strategyTypes"
|
||
:key="index"
|
||
class="strategy-card"
|
||
:class="{ 'active': currentType === item.key }"
|
||
@click="selectType(item.key)"
|
||
>
|
||
<view class="icon-circle" :class="currentType === item.key ? 'bg-white text-green' : item.bgClass">
|
||
<uni-icons :type="item.icon" size="24" :color="currentType === item.key ? '#064E3B' : item.iconColor"></uni-icons>
|
||
</view>
|
||
<text class="st-name" :class="{ 'text-white': currentType === item.key }">{{ item.name }}</text>
|
||
<text class="st-tag" :class="{ 'text-green-light': currentType === item.key }">{{ item.tag }}</text>
|
||
|
||
<view class="check-mark" v-if="currentType === item.key">
|
||
<uni-icons type="checkmarkempty" size="16" color="#064E3B"></uni-icons>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="desc-box" v-if="currentStrategyInfo">
|
||
<view class="desc-header">
|
||
<uni-icons type="info-filled" size="18" color="#064E3B"></uni-icons>
|
||
<text class="desc-title">策略原理</text>
|
||
</view>
|
||
<text class="desc-content">{{ currentStrategyInfo.description }}</text>
|
||
</view>
|
||
|
||
<view class="config-section">
|
||
<view class="section-title">参数配置</view>
|
||
|
||
<view class="form-card">
|
||
|
||
<view class="form-item">
|
||
<text class="label">模型别名</text>
|
||
<input class="input-field" v-model="formData.alias" placeholder="例如: 纳指长期定投" />
|
||
</view>
|
||
|
||
<template v-if="currentType === 'weight'">
|
||
<view class="form-item">
|
||
<text class="label">再平衡周期</text>
|
||
<picker :range="['每日', '每周', '每月', '每季度', '每年']" @change="onPeriodChange">
|
||
<view class="picker-display">
|
||
<text>{{ formData.period || '请选择' }}</text>
|
||
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">偏离阈值 (%)</text>
|
||
<view class="input-wrapper">
|
||
<input class="input-field" type="number" v-model="formData.threshold" placeholder="5" />
|
||
<text class="unit">sw</text>
|
||
</view>
|
||
<text class="helper">当资产权重偏离目标超过此数值时触发调仓。</text>
|
||
</view>
|
||
</template>
|
||
|
||
<template v-if="currentType === 'ma'">
|
||
<view class="flex-row gap-3">
|
||
<view class="form-item flex-1">
|
||
<text class="label">快线周期 (Short)</text>
|
||
<input class="input-field" type="number" v-model="formData.fastPeriod" placeholder="10" />
|
||
</view>
|
||
<view class="form-item flex-1">
|
||
<text class="label">慢线周期 (Long)</text>
|
||
<input class="input-field" type="number" v-model="formData.slowPeriod" placeholder="30" />
|
||
</view>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">均线类型</text>
|
||
<picker :range="['SMA (简单移动平均)', 'EMA (指数移动平均)']" @change="onMaTypeChange">
|
||
<view class="picker-display">
|
||
<text>{{ formData.maType || 'SMA' }}</text>
|
||
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="info-tag bg-blue-50">
|
||
<text class="text-blue-700 text-xs">规则:快线上穿慢线买入,下穿卖出。</text>
|
||
</view>
|
||
</template>
|
||
|
||
<template v-if="currentType === 'chandelier'">
|
||
<view class="form-item">
|
||
<text class="label">ATR 周期</text>
|
||
<input class="input-field" type="number" v-model="formData.atrPeriod" placeholder="22" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">ATR 倍数 (Multiplier)</text>
|
||
<input class="input-field" type="digit" v-model="formData.atrMultiplier" placeholder="3.0" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="label">趋势过滤均线 (可选)</text>
|
||
<input class="input-field" type="number" v-model="formData.trendMa" placeholder="200 (日线)" />
|
||
</view>
|
||
</template>
|
||
|
||
</view>
|
||
</view>
|
||
|
||
<view class="footer-bar">
|
||
<button class="submit-btn" @click="submit">保存策略配置</button>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue';
|
||
|
||
const currentType = ref('weight'); // 当前选中的策略类型
|
||
|
||
// 策略定义数据
|
||
const strategyTypes = [
|
||
{
|
||
key: 'weight',
|
||
name: '目标权重策略',
|
||
tag: '资产配置',
|
||
icon: 'pie-chart-filled',
|
||
bgClass: 'bg-green-100',
|
||
iconColor: '#059669',
|
||
description: '为投资组合中的每个资产设定固定的目标权重比例,通过定期再平衡将资产配置恢复到目标权重。适合长期投资和多元化资产配置(如60/40组合)。'
|
||
},
|
||
{
|
||
key: 'ma',
|
||
name: '双均线策略',
|
||
tag: '趋势跟踪',
|
||
icon: 'navigate-filled',
|
||
bgClass: 'bg-blue-100',
|
||
iconColor: '#2563EB',
|
||
description: '经典技术分析策略,通过短期和长期移动平均线的金叉死叉信号捕捉价格趋势。金叉买入,死叉卖出,过滤短期噪音。'
|
||
},
|
||
{
|
||
key: 'chandelier',
|
||
name: '吊灯止损策略',
|
||
tag: '风险控制',
|
||
icon: 'fire-filled',
|
||
bgClass: 'bg-orange-100',
|
||
iconColor: '#EA580C',
|
||
description: '结合移动平均线和ATR(平均真实波幅)生成的动态止损点。随着价格上涨止损点上移,价格回撤触及止损点时离场,有效锁住利润。'
|
||
}
|
||
];
|
||
|
||
// 表单数据
|
||
const formData = ref({
|
||
alias: '',
|
||
period: '每季度',
|
||
threshold: '',
|
||
fastPeriod: '',
|
||
slowPeriod: '',
|
||
maType: 'SMA',
|
||
atrPeriod: '',
|
||
atrMultiplier: '',
|
||
trendMa: ''
|
||
});
|
||
|
||
// 计算当前选中的策略信息
|
||
const currentStrategyInfo = computed(() => {
|
||
return strategyTypes.find(item => item.key === currentType.value);
|
||
});
|
||
|
||
|
||
const selectType = (key) => {
|
||
currentType.value = key;
|
||
};
|
||
|
||
const onPeriodChange = (e) => {
|
||
const periods = ['每日', '每周', '每月', '每季度', '每年'];
|
||
formData.value.period = periods[e.detail.value];
|
||
};
|
||
|
||
const onMaTypeChange = (e) => {
|
||
const types = ['SMA', 'EMA'];
|
||
formData.value.maType = types[e.detail.value];
|
||
};
|
||
|
||
const submit = () => {
|
||
console.log('保存策略:', {
|
||
type: currentType.value,
|
||
config: formData.value
|
||
});
|
||
uni.showLoading({ title: '保存中' });
|
||
setTimeout(() => {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '策略已保存', icon: 'success' });
|
||
setTimeout(() => uni.navigateBack(), 1500);
|
||
}, 800);
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-container {
|
||
min-height: 100vh;
|
||
background-color: #F9FAFB;
|
||
padding-bottom: 200rpx; /* 增加底部内边距,防止内容被底部按钮遮挡 */
|
||
}
|
||
|
||
/* 导航栏 (简化版) */
|
||
.nav-bar {
|
||
background-color: #fff;
|
||
padding: var(--status-bar-height) 32rpx 20rpx 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 88rpx;
|
||
box-sizing: content-box;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
}
|
||
.back-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
/* 标题通用 */
|
||
.section-title {
|
||
padding: 32rpx 32rpx 20rpx 32rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #374151;
|
||
}
|
||
|
||
/* 策略选择器 */
|
||
.strategy-scroll {
|
||
white-space: nowrap;
|
||
width: 100%;
|
||
padding-bottom: 20rpx;
|
||
}
|
||
.strategy-row {
|
||
display: flex;
|
||
padding: 0 32rpx;
|
||
gap: 24rpx;
|
||
}
|
||
.strategy-card {
|
||
width: 280rpx;
|
||
height: 320rpx;
|
||
background-color: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
padding: 32rpx;
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border: 2rpx solid transparent;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||
position: relative;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.strategy-card.active {
|
||
background-color: #064E3B;
|
||
border-color: #064E3B;
|
||
transform: translateY(-4rpx);
|
||
box-shadow: 0 12rpx 24rpx rgba(6, 78, 59, 0.2);
|
||
}
|
||
|
||
.icon-circle {
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
.bg-green-100 { background-color: #ECFDF5; }
|
||
.bg-blue-100 { background-color: #EFF6FF; }
|
||
.bg-orange-100 { background-color: #FFF7ED; }
|
||
|
||
.st-name { font-size: 30rpx; font-weight: 700; color: #1F2937; margin-bottom: 8rpx; white-space: normal; text-align: center; }
|
||
.st-tag { font-size: 22rpx; color: #9CA3AF; }
|
||
.text-white { color: #fff !important; }
|
||
.text-green { color: #064E3B !important; }
|
||
.text-green-light { color: rgba(255,255,255,0.7) !important; }
|
||
|
||
.check-mark {
|
||
position: absolute;
|
||
top: 16rpx;
|
||
right: 16rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background-color: #fff;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 策略描述 */
|
||
.desc-box {
|
||
margin: 10rpx 32rpx 30rpx 32rpx;
|
||
background-color: #ECFDF5;
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid #D1FAE5;
|
||
}
|
||
.desc-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.desc-title { font-size: 26rpx; font-weight: 700; color: #064E3B; }
|
||
.desc-content {
|
||
font-size: 24rpx;
|
||
color: #047857;
|
||
line-height: 1.6;
|
||
text-align: justify;
|
||
}
|
||
|
||
/* 表单区域 */
|
||
.config-section { margin-top: 20rpx; }
|
||
.form-card {
|
||
background-color: #fff;
|
||
margin: 0 32rpx;
|
||
padding: 32rpx;
|
||
border-radius: 32rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.02);
|
||
}
|
||
|
||
.form-item { margin-bottom: 32rpx; }
|
||
.label { font-size: 26rpx; font-weight: 600; color: #374151; margin-bottom: 16rpx; display: block; }
|
||
|
||
.input-field {
|
||
background-color: #F9FAFB;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 20rpx;
|
||
height: 88rpx;
|
||
padding: 0 24rpx;
|
||
font-size: 28rpx;
|
||
color: #1F2937;
|
||
}
|
||
|
||
.input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.unit { position: absolute; right: 24rpx; font-size: 26rpx; color: #9CA3AF; }
|
||
|
||
.picker-display {
|
||
background-color: #F9FAFB;
|
||
border: 2rpx solid #E5E7EB;
|
||
border-radius: 20rpx;
|
||
height: 88rpx;
|
||
padding: 0 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
font-size: 28rpx;
|
||
color: #1F2937;
|
||
}
|
||
|
||
.helper { font-size: 22rpx; color: #9CA3AF; margin-top: 10rpx; display: block; }
|
||
|
||
.flex-row { display: flex; flex-direction: row; }
|
||
.flex-1 { flex: 1; }
|
||
.gap-3 { gap: 24rpx; }
|
||
|
||
.info-tag { padding: 16rpx; border-radius: 12rpx; margin-top: -10rpx; }
|
||
.bg-blue-50 { background-color: #EFF6FF; }
|
||
.text-blue-700 { color: #1D4ED8; }
|
||
.text-xs { font-size: 22rpx; }
|
||
|
||
/* 底部悬浮按钮栏 */
|
||
.footer-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #fff;
|
||
padding: 20rpx 32rpx 50rpx 32rpx; /* 适配底部安全区 */
|
||
box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.05);
|
||
z-index: 99;
|
||
}
|
||
.submit-btn {
|
||
background-color: #064E3B;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
border-radius: 24rpx;
|
||
height: 96rpx;
|
||
line-height: 96rpx;
|
||
font-size: 30rpx;
|
||
width: 100%;
|
||
}
|
||
.submit-btn:active { opacity: 0.9; }
|
||
</style> |