AssetManager.UniApp/pages/strategies/edit/edit.vue
fanfpy bc48e8f7a3 feat: 初始化项目并添加基础功能
- 添加uni-ui组件库依赖
- 实现微信静默登录功能
- 创建资产、策略、我的等核心页面
- 添加策略组合配置功能
- 实现持仓详情展示
- 完善用户信息展示
- 添加全局样式和工具类
- 配置小程序项目设置
2026-02-18 20:51:42 +08:00

397 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>