feat: 实现资产、策略和交易模块的数据映射与API集成
refactor: 重构策略编辑页面,支持创建和更新操作 fix: 修复投资组合详情页的数据显示问题 perf: 优化API请求处理,添加PUT和DELETE方法 docs: 完善API接口文档注释 style: 统一代码格式和命名规范
This commit is contained in:
parent
58cf092753
commit
bf4fa243ec
@ -95,33 +95,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, onMounted, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
// 策略库数据 (模拟)
|
const { proxy } = getCurrentInstance();
|
||||||
const strategies = [
|
const api = proxy.$api;
|
||||||
{ name: 'HFEA 风险平价', desc: '利用 3x 杠杆股债平衡,适合长期持有。', color: '#10B981' },
|
|
||||||
{ name: '双均线趋势跟踪', desc: '金叉买入死叉卖出,捕捉大波段。', color: '#3B82F6' },
|
|
||||||
{ name: '网格交易 (Grid)', desc: '震荡行情下的高抛低吸策略。', color: '#F59E0B' },
|
|
||||||
{ name: '手动自管', desc: '不使用自动化策略,仅做资产记账。', color: '#6B7280' }
|
|
||||||
];
|
|
||||||
|
|
||||||
|
const strategies = ref([]);
|
||||||
const strategyIndex = ref(-1);
|
const strategyIndex = ref(-1);
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
name: '',
|
name: '',
|
||||||
stocks: [
|
stocks: [
|
||||||
{ name: '', price: '', amount: '', date: '' } // 默认有一行
|
{ name: '', price: '', amount: '', date: '' }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性:当前选中的策略对象
|
|
||||||
const selectedStrategy = computed(() => {
|
const selectedStrategy = computed(() => {
|
||||||
if (strategyIndex.value === -1) return null;
|
if (strategyIndex.value === -1) return null;
|
||||||
return strategies[strategyIndex.value];
|
return strategies.value[strategyIndex.value];
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性:总投入金额
|
|
||||||
const totalInvestment = computed(() => {
|
const totalInvestment = computed(() => {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
form.value.stocks.forEach(stock => {
|
form.value.stocks.forEach(stock => {
|
||||||
@ -129,11 +122,28 @@ const totalInvestment = computed(() => {
|
|||||||
const a = parseFloat(stock.amount) || 0;
|
const a = parseFloat(stock.amount) || 0;
|
||||||
total += p * a;
|
total += p * a;
|
||||||
});
|
});
|
||||||
// 格式化千分位
|
|
||||||
return total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
return total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 方法 ---
|
const fetchStrategies = async () => {
|
||||||
|
try {
|
||||||
|
const response = await api.strategies.getStrategies();
|
||||||
|
if (response.code === 200) {
|
||||||
|
strategies.value = response.data.map(item => ({
|
||||||
|
id: item.Id,
|
||||||
|
name: item.Title,
|
||||||
|
desc: item.Description,
|
||||||
|
color: '#10B981'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取策略列表失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchStrategies();
|
||||||
|
});
|
||||||
|
|
||||||
const onStrategyChange = (e) => {
|
const onStrategyChange = (e) => {
|
||||||
strategyIndex.value = e.detail.value;
|
strategyIndex.value = e.detail.value;
|
||||||
@ -143,33 +153,48 @@ const onDateChange = (e, index) => {
|
|||||||
form.value.stocks[index].date = e.detail.value;
|
form.value.stocks[index].date = e.detail.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加一行股票
|
|
||||||
const addStockRow = () => {
|
const addStockRow = () => {
|
||||||
form.value.stocks.push({ name: '', price: '', amount: '', date: '' });
|
form.value.stocks.push({ name: '', price: '', amount: '', date: '' });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除一行
|
|
||||||
const removeStockRow = (index) => {
|
const removeStockRow = (index) => {
|
||||||
form.value.stocks.splice(index, 1);
|
form.value.stocks.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提交
|
const submitForm = async () => {
|
||||||
const submitForm = () => {
|
|
||||||
// 简单校验
|
|
||||||
if (!form.value.name) return uni.showToast({ title: '请输入组合名称', icon: 'none' });
|
if (!form.value.name) return uni.showToast({ title: '请输入组合名称', icon: 'none' });
|
||||||
if (strategyIndex.value === -1) return uni.showToast({ title: '请选择策略', icon: 'none' });
|
if (strategyIndex.value === -1) return uni.showToast({ title: '请选择策略', icon: 'none' });
|
||||||
|
|
||||||
console.log('提交的数据:', {
|
const selected = strategies.value[strategyIndex.value];
|
||||||
...form.value,
|
|
||||||
strategy: strategies[strategyIndex.value].name
|
const requestData = {
|
||||||
});
|
name: form.value.name,
|
||||||
|
strategyId: selected.id,
|
||||||
|
currency: 'USD',
|
||||||
|
stocks: form.value.stocks.map(stock => ({
|
||||||
|
name: stock.name,
|
||||||
|
code: stock.name,
|
||||||
|
price: parseFloat(stock.price) || 0,
|
||||||
|
amount: parseFloat(stock.amount) || 0,
|
||||||
|
date: stock.date,
|
||||||
|
currency: 'USD'
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
uni.showLoading({ title: '创建中...' });
|
uni.showLoading({ title: '创建中...' });
|
||||||
setTimeout(() => {
|
|
||||||
|
try {
|
||||||
|
const response = await api.assets.createPortfolio(requestData);
|
||||||
|
if (response.code === 200) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '创建成功', icon: 'success' });
|
||||||
|
setTimeout(() => uni.navigateBack(), 1500);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建投资组合失败:', error);
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({ title: '创建成功' });
|
uni.showToast({ title: '创建失败,请重试', icon: 'none' });
|
||||||
setTimeout(() => uni.navigateBack(), 1500);
|
}
|
||||||
}, 1000);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -17,17 +17,17 @@
|
|||||||
|
|
||||||
<view class="card-main">
|
<view class="card-main">
|
||||||
<text class="currency">¥</text>
|
<text class="currency">¥</text>
|
||||||
<text class="big-number">156,240.00</text>
|
<text class="big-number">{{ (portfolioData.portfolioValue || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-bottom">
|
<view class="card-bottom">
|
||||||
<view class="stat-item">
|
<view class="stat-item">
|
||||||
<text class="stat-label">历史变化</text>
|
<text class="stat-label">历史变化</text>
|
||||||
<text class="stat-val text-red">+42.82%</text>
|
<text class="stat-val" :class="(portfolioData.historicalChange || 0) >= 0 ? 'text-red' : 'text-green'">{{ (portfolioData.historicalChange || 0) >= 0 ? '+' : '' }}{{ (portfolioData.historicalChange || 0).toFixed(2) }}%</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-item align-right">
|
<view class="stat-item align-right">
|
||||||
<text class="stat-label">日内波动</text>
|
<text class="stat-label">日内波动</text>
|
||||||
<text class="stat-val text-red">+¥1,240.50</text>
|
<text class="stat-val" :class="(portfolioData.dailyVolatility || 0) >= 0 ? 'text-red' : 'text-green'">{{ (portfolioData.dailyVolatility || 0) >= 0 ? '+' : '' }}¥{{ (portfolioData.dailyVolatility || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -42,16 +42,15 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="strategy-info-card">
|
<view class="strategy-info-card" v-if="portfolioData.logicModel">
|
||||||
<view class="st-left">
|
<view class="st-left">
|
||||||
<view class="st-icon-box bg-green-100">
|
<view class="st-icon-box bg-green-100">
|
||||||
<text class="st-icon-text text-green">H</text>
|
<text class="st-icon-text text-green">{{ portfolioData.logicModel?.charAt(0) || 'S' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-col gap-1">
|
<view class="flex-col gap-1">
|
||||||
<text class="st-name">HFEA 风险平价逻辑</text>
|
<text class="st-name">{{ portfolioData.logicModel || '未设置策略' }}</text>
|
||||||
<view class="flex-row gap-2">
|
<view class="flex-row gap-2">
|
||||||
<text class="st-tag">目标权重</text>
|
<text class="st-tag" v-if="portfolioData.logicModelDescription">{{ portfolioData.logicModelDescription }}</text>
|
||||||
<text class="st-tag">季度调仓</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -59,7 +58,7 @@
|
|||||||
<view class="st-right">
|
<view class="st-right">
|
||||||
<view class="flex-row items-center gap-1">
|
<view class="flex-row items-center gap-1">
|
||||||
<view class="status-dot pulsing"></view>
|
<view class="status-dot pulsing"></view>
|
||||||
<text class="st-status-text">监控中</text>
|
<text class="st-status-text">{{ portfolioData.logicModelStatus || '监控中' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -67,25 +66,25 @@
|
|||||||
|
|
||||||
<view class="section-container">
|
<view class="section-container">
|
||||||
<view class="section-header">
|
<view class="section-header">
|
||||||
<text class="section-title">当前记录项 (3)</text>
|
<text class="section-title">当前记录项 ({{ portfolioData.totalItems || positions.length }})</text>
|
||||||
<text class="section-sub">占比 100%</text>
|
<text class="section-sub">占比 {{ portfolioData.totalRatio || 100 }}%</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="position-list">
|
<view class="position-list">
|
||||||
<view class="position-card" v-for="(item, index) in positions" :key="index">
|
<view class="position-card" v-for="(item, index) in positions" :key="item.id || index">
|
||||||
<view class="pos-top">
|
<view class="pos-top">
|
||||||
<view class="flex-row items-center gap-2">
|
<view class="flex-row items-center gap-2">
|
||||||
<view class="stock-icon" :class="item.iconClass">
|
<view class="stock-icon" :class="index % 2 === 0 ? 'bg-blue-100 text-blue' : 'bg-orange-100 text-orange'">
|
||||||
<text class="icon-char">{{ item.name.charAt(0) }}</text>
|
<text class="icon-char">{{ item.stockName?.charAt(0) || item.stockCode?.charAt(0) || 'S' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-col">
|
<view class="flex-col">
|
||||||
<text class="stock-name">{{ item.name }}</text>
|
<text class="stock-name">{{ item.stockName || item.stockCode }}</text>
|
||||||
<text class="stock-code">{{ item.code }} · {{ item.shares }}份</text>
|
<text class="stock-code">{{ item.stockCode }} · {{ item.amount }}份</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-col align-right">
|
<view class="flex-col align-right">
|
||||||
<text class="market-val">¥{{ item.marketValue }}</text>
|
<text class="market-val">¥{{ (item.totalValue || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
<text class="weight-tag">比例 {{ item.weight }}%</text>
|
<text class="weight-tag">比例 {{ (item.ratio || 0).toFixed(1) }}%</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -94,14 +93,14 @@
|
|||||||
<view class="pos-bottom">
|
<view class="pos-bottom">
|
||||||
<view class="pnl-item">
|
<view class="pnl-item">
|
||||||
<text class="pnl-label">变动额</text>
|
<text class="pnl-label">变动额</text>
|
||||||
<text class="pnl-val" :class="item.pnl > 0 ? 'text-red' : 'text-green'">
|
<text class="pnl-val" :class="(item.changeAmount || 0) >= 0 ? 'text-red' : 'text-green'">
|
||||||
{{ item.pnl > 0 ? '+' : '' }}{{ item.pnl }}
|
{{ (item.changeAmount || 0) >= 0 ? '+' : '' }}¥{{ (item.changeAmount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="pnl-item align-right">
|
<view class="pnl-item align-right">
|
||||||
<text class="pnl-label">偏离比例</text>
|
<text class="pnl-label">偏离比例</text>
|
||||||
<text class="pnl-val" :class="item.pnlPercent > 0 ? 'text-red' : 'text-green'">
|
<text class="pnl-val" :class="(item.deviationRatio || 0) >= 0 ? 'text-red' : 'text-green'">
|
||||||
{{ item.pnlPercent > 0 ? '+' : '' }}{{ item.pnlPercent }}%
|
{{ (item.deviationRatio || 0) >= 0 ? '+' : '' }}{{ (item.deviationRatio || 0).toFixed(2) }}%
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -117,18 +116,18 @@
|
|||||||
<view class="timeline-box">
|
<view class="timeline-box">
|
||||||
<view class="timeline-item" v-for="(log, k) in logs" :key="k">
|
<view class="timeline-item" v-for="(log, k) in logs" :key="k">
|
||||||
<view class="tl-left">
|
<view class="tl-left">
|
||||||
<text class="tl-date">{{ log.date }}</text>
|
<text class="tl-date">{{ log.Date }}</text>
|
||||||
<text class="tl-time">{{ log.time }}</text>
|
<text class="tl-time">{{ log.Time }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="tl-line">
|
<view class="tl-line">
|
||||||
<view class="tl-dot" :class="log.type === 'buy' ? 'bg-red' : 'bg-green'"></view>
|
<view class="tl-dot" :class="log.Type === 'buy' ? 'bg-red' : 'bg-green'"></view>
|
||||||
<view class="tl-dash" v-if="k !== logs.length - 1"></view>
|
<view class="tl-dash" v-if="k !== logs.length - 1"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="tl-right">
|
<view class="tl-right">
|
||||||
<text class="tl-title">{{ log.title }}</text>
|
<text class="tl-title">{{ log.Title }}</text>
|
||||||
<text class="tl-desc">{{ log.type === 'buy' ? '录入增加' : '结出减少' }} {{ log.amount }}</text>
|
<text class="tl-desc">{{ log.Type === 'buy' ? '录入增加' : '结出减少' }} {{ log.Amount }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -145,29 +144,233 @@
|
|||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 交易表单弹窗 -->
|
||||||
|
<view v-if="showTransactionForm" class="transaction-modal">
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="modal-header">
|
||||||
|
<text class="modal-title">{{ transactionType === 'buy' ? '录入增加' : '结出减少' }}</text>
|
||||||
|
<view class="close-btn" @click="showTransactionForm = false">
|
||||||
|
<uni-icons type="close" size="20" color="#6B7280"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-content">
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">股票代码</text>
|
||||||
|
<input
|
||||||
|
v-model="transactionForm.stockCode"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入股票代码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">数量</text>
|
||||||
|
<input
|
||||||
|
v-model="transactionForm.amount"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
placeholder="请输入数量"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">价格</text>
|
||||||
|
<input
|
||||||
|
v-model="transactionForm.price"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
placeholder="请输入价格"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">货币</text>
|
||||||
|
<view class="form-select">
|
||||||
|
<text>{{ transactionForm.currency }}</text>
|
||||||
|
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">备注</text>
|
||||||
|
<input
|
||||||
|
v-model="transactionForm.remark"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="modal-footer">
|
||||||
|
<button class="cancel-btn" @click="showTransactionForm = false">取消</button>
|
||||||
|
<button class="confirm-btn" @click="submitTransaction">确认</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, onMounted, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
const positions = ref([
|
const { proxy } = getCurrentInstance();
|
||||||
{ name: 'UPRO', code: 'UPRO.US', shares: 142, marketValue: '85,932.00', weight: 55, pnl: 12400.00, pnlPercent: 16.8, iconClass: 'bg-blue-100 text-blue' },
|
const api = proxy.$api;
|
||||||
{ name: 'TMF', code: 'TMF.US', shares: 800, marketValue: '70,308.00', weight: 45, pnl: -3200.50, pnlPercent: -4.3, iconClass: 'bg-orange-100 text-orange' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
const logs = ref([
|
const portfolioId = ref('');
|
||||||
{ date: '02-14', time: '14:30', type: 'buy', title: '定期定投 UPRO', amount: '$500.00' },
|
const portfolioData = ref({
|
||||||
{ date: '01-01', time: '09:15', type: 'sell', title: '季度再平衡 TMF', amount: '200股' },
|
id: '',
|
||||||
{ date: '12-15', time: '10:00', type: 'buy', title: '建仓买入', amount: '¥100,000' },
|
name: '',
|
||||||
{ date: '12-10', time: '11:20', type: 'buy', title: '建仓买入', amount: '¥50,000' } // 加一条数据撑开高度,测试滚动
|
currency: 'CNY',
|
||||||
]);
|
status: '',
|
||||||
|
portfolioValue: 0,
|
||||||
|
totalReturn: 0,
|
||||||
|
todayProfit: 0,
|
||||||
|
todayProfitCurrency: 'CNY',
|
||||||
|
historicalChange: 0,
|
||||||
|
dailyVolatility: 0,
|
||||||
|
logicModel: '',
|
||||||
|
logicModelStatus: '',
|
||||||
|
logicModelDescription: '',
|
||||||
|
totalItems: 0,
|
||||||
|
totalRatio: 100,
|
||||||
|
strategy: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const positions = ref([]);
|
||||||
|
const logs = ref([]);
|
||||||
|
|
||||||
|
// 交易表单
|
||||||
|
const showTransactionForm = ref(false);
|
||||||
|
const transactionType = ref('buy'); // buy 或 sell
|
||||||
|
const transactionForm = ref({
|
||||||
|
stockCode: '',
|
||||||
|
amount: '',
|
||||||
|
price: '',
|
||||||
|
currency: 'CNY',
|
||||||
|
remark: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchPortfolioData = async () => {
|
||||||
|
try {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const currentPage = pages[pages.length - 1];
|
||||||
|
const id = currentPage.options?.id;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
console.error('缺少投资组合ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
portfolioId.value = id;
|
||||||
|
const response = await api.assets.getPortfolio(id);
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
portfolioData.value = response.data;
|
||||||
|
positions.value = response.data.positions || [];
|
||||||
|
console.log('投资组合数据获取成功:', response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取投资组合数据失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchTransactions = async () => {
|
||||||
|
try {
|
||||||
|
if (!portfolioId.value) return;
|
||||||
|
|
||||||
|
const response = await api.assets.getTransactions({
|
||||||
|
portfolioId: portfolioId.value,
|
||||||
|
limit: 10,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
logs.value = response.data.items || [];
|
||||||
|
console.log('交易记录获取成功:', response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取交易记录失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchPortfolioData();
|
||||||
|
await fetchTransactions();
|
||||||
|
});
|
||||||
|
|
||||||
const goStrategyConfig = () => {
|
const goStrategyConfig = () => {
|
||||||
uni.navigateTo({ url: '/pages/strategy/edit?id=1' });
|
if (portfolioData.value.strategy?.Id) {
|
||||||
|
uni.navigateTo({ url: `/pages/strategies/edit/edit?id=${portfolioData.value.strategy.Id}` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBuy = () => {
|
||||||
|
transactionType.value = 'buy';
|
||||||
|
resetTransactionForm();
|
||||||
|
showTransactionForm.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSell = () => {
|
||||||
|
transactionType.value = 'sell';
|
||||||
|
resetTransactionForm();
|
||||||
|
showTransactionForm.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTransactionForm = () => {
|
||||||
|
transactionForm.value = {
|
||||||
|
stockCode: '',
|
||||||
|
amount: '',
|
||||||
|
price: '',
|
||||||
|
currency: 'CNY',
|
||||||
|
remark: ''
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitTransaction = async () => {
|
||||||
|
// 表单验证
|
||||||
|
if (!transactionForm.value.stockCode) {
|
||||||
|
return uni.showToast({ title: '请输入股票代码', icon: 'none' });
|
||||||
|
}
|
||||||
|
if (!transactionForm.value.amount || parseFloat(transactionForm.value.amount) <= 0) {
|
||||||
|
return uni.showToast({ title: '请输入有效的数量', icon: 'none' });
|
||||||
|
}
|
||||||
|
if (!transactionForm.value.price || parseFloat(transactionForm.value.price) <= 0) {
|
||||||
|
return uni.showToast({ title: '请输入有效的价格', icon: 'none' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionData = {
|
||||||
|
portfolioId: portfolioId.value,
|
||||||
|
type: transactionType.value,
|
||||||
|
stockCode: transactionForm.value.stockCode,
|
||||||
|
amount: parseFloat(transactionForm.value.amount),
|
||||||
|
price: parseFloat(transactionForm.value.price),
|
||||||
|
currency: transactionForm.value.currency,
|
||||||
|
remark: transactionForm.value.remark
|
||||||
|
};
|
||||||
|
|
||||||
|
uni.showLoading({ title: '提交中...' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await api.assets.createTransaction(transactionData);
|
||||||
|
if (response.code === 200) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '交易提交成功', icon: 'success' });
|
||||||
|
showTransactionForm.value = false;
|
||||||
|
|
||||||
|
// 重新获取交易记录
|
||||||
|
await fetchTransactions();
|
||||||
|
// 重新获取投资组合数据
|
||||||
|
await fetchPortfolioData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建交易失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '提交失败,请重试', icon: 'none' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const handleBuy = () => uni.showToast({ title: '买入', icon: 'none' });
|
|
||||||
const handleSell = () => uni.showToast({ title: '卖出', icon: 'none' });
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -340,4 +543,122 @@ const handleSell = () => uni.showToast({ title: '卖出', icon: 'none' });
|
|||||||
.btn-buy:active { background-color: #047857; }
|
.btn-buy:active { background-color: #047857; }
|
||||||
.btn-sell { background-color: #FFFFFF; color: #064E3B; border: 2rpx solid #064E3B; }
|
.btn-sell { background-color: #FFFFFF; color: #064E3B; border: 2rpx solid #064E3B; }
|
||||||
.btn-sell:active { background-color: #ECFDF5; }
|
.btn-sell:active { background-color: #ECFDF5; }
|
||||||
|
|
||||||
|
/* 交易表单弹窗 */
|
||||||
|
.transaction-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32rpx;
|
||||||
|
border-bottom: 1rpx solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-content {
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
border: 1rpx solid #E5E7EB;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #111827;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
border: 1rpx solid #E5E7EB;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 32rpx;
|
||||||
|
border-top: 1rpx solid #E5E7EB;
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #F3F4F6;
|
||||||
|
color: #6B7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #064E3B;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -110,7 +110,15 @@ const fetchAssetData = async () => {
|
|||||||
console.log('开始获取资产数据...');
|
console.log('开始获取资产数据...');
|
||||||
const response = await api.assets.getAssetData();
|
const response = await api.assets.getAssetData();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
assetData.value = response.data;
|
// 映射资产数据字段
|
||||||
|
const data = response.data;
|
||||||
|
assetData.value = {
|
||||||
|
totalValue: data.TotalValue,
|
||||||
|
currency: data.Currency,
|
||||||
|
todayProfit: data.TodayProfit,
|
||||||
|
todayProfitCurrency: data.TodayProfitCurrency,
|
||||||
|
totalReturnRate: data.TotalReturnRate
|
||||||
|
};
|
||||||
console.log('资产数据获取成功');
|
console.log('资产数据获取成功');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -124,8 +132,22 @@ const fetchHoldingsData = async () => {
|
|||||||
console.log('开始获取持仓数据...');
|
console.log('开始获取持仓数据...');
|
||||||
const response = await api.assets.getHoldings();
|
const response = await api.assets.getHoldings();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
// 处理响应数据结构,获取 items 数组
|
// 处理响应数据结构,获取 items 数组并映射字段
|
||||||
holdings.value = response.data.items || [];
|
const items = response.data.items || [];
|
||||||
|
holdings.value = items.map(item => ({
|
||||||
|
id: item.Id,
|
||||||
|
name: item.Name,
|
||||||
|
tags: item.Tags,
|
||||||
|
status: item.Status,
|
||||||
|
statusType: item.StatusType,
|
||||||
|
iconChar: item.IconChar,
|
||||||
|
iconBgClass: item.IconBgClass,
|
||||||
|
iconTextClass: item.IconTextClass,
|
||||||
|
value: item.Value,
|
||||||
|
currency: item.Currency,
|
||||||
|
returnRate: item.ReturnRate,
|
||||||
|
returnType: item.ReturnType
|
||||||
|
}));
|
||||||
console.log('持仓数据获取成功,items数量:', holdings.value.length);
|
console.log('持仓数据获取成功,items数量:', holdings.value.length);
|
||||||
console.log('持仓数据:', holdings.value);
|
console.log('持仓数据:', holdings.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-container">
|
<view class="page-container">
|
||||||
|
|
||||||
|
<view class="nav-bar">
|
||||||
|
<view class="back-btn" @click="uni.navigateBack()">
|
||||||
|
<uni-icons type="left" size="20" color="#374151"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text class="nav-title">{{ isEditMode ? '编辑策略' : '创建策略' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="section-title">选择逻辑模型</view>
|
<view class="section-title">选择逻辑模型</view>
|
||||||
<scroll-view scroll-x class="strategy-scroll" :show-scrollbar="false">
|
<scroll-view scroll-x class="strategy-scroll" :show-scrollbar="false">
|
||||||
<view class="strategy-row">
|
<view class="strategy-row">
|
||||||
@ -38,44 +45,44 @@
|
|||||||
<view class="form-card">
|
<view class="form-card">
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">模型别名</text>
|
<text class="label">策略名称</text>
|
||||||
<input class="input-field" v-model="formData.alias" placeholder="例如: 纳指长期定投" />
|
<input class="input-field" v-model="formData.name" placeholder="例如: 双均线趋势策略" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<template v-if="currentType === 'weight'">
|
<view class="form-item">
|
||||||
<view class="form-item">
|
<text class="label">策略描述</text>
|
||||||
<text class="label">再平衡周期</text>
|
<input class="input-field" v-model="formData.description" placeholder="描述策略的用途和特点" />
|
||||||
<picker :range="['每日', '每周', '每月', '每季度', '每年']" @change="onPeriodChange">
|
</view>
|
||||||
<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="form-item">
|
||||||
|
<text class="label">风险等级</text>
|
||||||
|
<picker :range="['low', 'medium', 'high']" @change="onRiskLevelChange">
|
||||||
|
<view class="picker-display">
|
||||||
|
<text>{{ formData.riskLevel || 'medium' }}</text>
|
||||||
|
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">标签</text>
|
||||||
|
<input class="input-field" v-model="formData.tags" placeholder="用逗号分隔,如:趋势,均线" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<template v-if="currentType === 'ma_trend'">
|
||||||
<view class="flex-row gap-3">
|
<view class="flex-row gap-3">
|
||||||
<view class="form-item flex-1">
|
<view class="form-item flex-1">
|
||||||
<text class="label">快线周期 (Short)</text>
|
<text class="label">短期周期</text>
|
||||||
<input class="input-field" type="number" v-model="formData.fastPeriod" placeholder="10" />
|
<input class="input-field" type="number" v-model="formData.shortPeriod" placeholder="20" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item flex-1">
|
<view class="form-item flex-1">
|
||||||
<text class="label">慢线周期 (Long)</text>
|
<text class="label">长期周期</text>
|
||||||
<input class="input-field" type="number" v-model="formData.slowPeriod" placeholder="30" />
|
<input class="input-field" type="number" v-model="formData.longPeriod" placeholder="60" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">均线类型</text>
|
<text class="label">均线类型</text>
|
||||||
<picker :range="['SMA (简单移动平均)', 'EMA (指数移动平均)']" @change="onMaTypeChange">
|
<picker :range="['SMA', 'EMA']" @change="onMaTypeChange">
|
||||||
<view class="picker-display">
|
<view class="picker-display">
|
||||||
<text>{{ formData.maType || 'SMA' }}</text>
|
<text>{{ formData.maType || 'SMA' }}</text>
|
||||||
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||||||
@ -83,22 +90,71 @@
|
|||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-tag bg-blue-50">
|
<view class="info-tag bg-blue-50">
|
||||||
<text class="text-blue-700 text-xs">规则:快线上穿慢线买入,下穿卖出。</text>
|
<text class="text-blue-700 text-xs">规则:短期均线上穿长期均线买入,下穿卖出。</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="currentType === 'chandelier'">
|
<template v-if="currentType === 'risk_parity'">
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">回看周期</text>
|
||||||
|
<input class="input-field" type="number" v-model="formData.lookbackPeriod" placeholder="60" />
|
||||||
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">再平衡阈值</text>
|
||||||
|
<input class="input-field" type="digit" v-model="formData.rebalanceThreshold" placeholder="0.05" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">资产配置</text>
|
||||||
|
<view class="assets-list">
|
||||||
|
<view class="asset-item" v-for="(asset, index) in formData.assets" :key="index">
|
||||||
|
<view class="asset-header">
|
||||||
|
<text class="asset-title">资产 #{{ index + 1 }}</text>
|
||||||
|
<uni-icons type="trash" size="18" color="#EF4444" @click="removeAsset(index)" v-if="formData.assets.length > 1"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="asset-inputs">
|
||||||
|
<view class="asset-input">
|
||||||
|
<text class="asset-label">代码</text>
|
||||||
|
<input class="input-field" v-model="asset.symbol" placeholder="如 AAPL" />
|
||||||
|
</view>
|
||||||
|
<view class="asset-input">
|
||||||
|
<text class="asset-label">目标权重</text>
|
||||||
|
<input class="input-field" type="digit" v-model="asset.targetWeight" placeholder="0.6" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="add-asset-btn" @click="addAsset">
|
||||||
|
<uni-icons type="plus" size="16" color="#064E3B"></uni-icons>
|
||||||
|
<text class="add-asset-text">添加资产</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="info-tag bg-green-50">
|
||||||
|
<text class="text-green-700 text-xs">当资产权重偏离目标超过此阈值时触发再平衡。</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="currentType === 'chandelier_exit'">
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">ATR 周期</text>
|
<text class="label">ATR 周期</text>
|
||||||
<input class="input-field" type="number" v-model="formData.atrPeriod" placeholder="22" />
|
<input class="input-field" type="number" v-model="formData.period" placeholder="22" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">ATR 倍数 (Multiplier)</text>
|
<text class="label">ATR 倍数</text>
|
||||||
<input class="input-field" type="digit" v-model="formData.atrMultiplier" placeholder="3.0" />
|
<input class="input-field" type="digit" v-model="formData.multiplier" placeholder="3.0" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="label">趋势过滤均线 (可选)</text>
|
<text class="label">使用收盘价</text>
|
||||||
<input class="input-field" type="number" v-model="formData.trendMa" placeholder="200 (日线)" />
|
<picker :range="['是', '否']" @change="onUseCloseChange">
|
||||||
|
<view class="picker-display">
|
||||||
|
<text>{{ formData.useClose ? '是' : '否' }}</text>
|
||||||
|
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
<view class="info-tag bg-orange-50">
|
||||||
|
<text class="text-orange-700 text-xs">动态止损策略,随着价格上涨止损点上移。</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -106,33 +162,25 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="footer-bar">
|
<view class="footer-bar">
|
||||||
<button class="submit-btn" @click="submit">保存策略配置</button>
|
<button class="submit-btn" @click="submit">{{ isEditMode ? '更新策略配置' : '保存策略配置' }}</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, getCurrentInstance } from 'vue';
|
import { ref, computed, getCurrentInstance, onMounted } from 'vue';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
const api = proxy.$api;
|
const api = proxy.$api;
|
||||||
|
|
||||||
const currentType = ref('weight'); // 当前选中的策略类型
|
const isEditMode = ref(false);
|
||||||
|
const strategyId = ref('');
|
||||||
|
const currentType = ref('ma_trend'); // 当前选中的策略类型
|
||||||
|
|
||||||
// 策略定义数据
|
|
||||||
const strategyTypes = [
|
const strategyTypes = [
|
||||||
{
|
{
|
||||||
key: 'weight',
|
key: 'ma_trend',
|
||||||
name: '目标权重策略',
|
|
||||||
tag: '资产配置',
|
|
||||||
icon: 'pie-chart-filled',
|
|
||||||
bgClass: 'bg-green-100',
|
|
||||||
iconColor: '#059669',
|
|
||||||
description: '为投资组合中的每个资产设定固定的目标权重比例,通过定期再平衡将资产配置恢复到目标权重。适合长期投资和多元化资产配置(如60/40组合)。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ma',
|
|
||||||
name: '双均线策略',
|
name: '双均线策略',
|
||||||
tag: '趋势跟踪',
|
tag: '趋势跟踪',
|
||||||
icon: 'navigate-filled',
|
icon: 'navigate-filled',
|
||||||
@ -141,7 +189,16 @@ const strategyTypes = [
|
|||||||
description: '经典技术分析策略,通过短期和长期移动平均线的金叉死叉信号捕捉价格趋势。金叉买入,死叉卖出,过滤短期噪音。'
|
description: '经典技术分析策略,通过短期和长期移动平均线的金叉死叉信号捕捉价格趋势。金叉买入,死叉卖出,过滤短期噪音。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'chandelier',
|
key: 'risk_parity',
|
||||||
|
name: '风险平价策略',
|
||||||
|
tag: '资产配置',
|
||||||
|
icon: 'pie-chart-filled',
|
||||||
|
bgClass: 'bg-green-100',
|
||||||
|
iconColor: '#059669',
|
||||||
|
description: '为投资组合中的每个资产设定固定的目标权重比例,通过定期再平衡将资产配置恢复到目标权重。适合长期投资和多元化资产配置(如60/40组合)。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'chandelier_exit',
|
||||||
name: '吊灯止损策略',
|
name: '吊灯止损策略',
|
||||||
tag: '风险控制',
|
tag: '风险控制',
|
||||||
icon: 'fire-filled',
|
icon: 'fire-filled',
|
||||||
@ -151,17 +208,22 @@ const strategyTypes = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
alias: '',
|
name: '',
|
||||||
period: '每季度',
|
description: '',
|
||||||
threshold: '',
|
riskLevel: 'medium',
|
||||||
fastPeriod: '',
|
tags: [],
|
||||||
slowPeriod: '',
|
|
||||||
maType: 'SMA',
|
maType: 'SMA',
|
||||||
atrPeriod: '',
|
shortPeriod: '',
|
||||||
atrMultiplier: '',
|
longPeriod: '',
|
||||||
trendMa: ''
|
lookbackPeriod: '',
|
||||||
|
rebalanceThreshold: '',
|
||||||
|
period: '',
|
||||||
|
multiplier: '',
|
||||||
|
useClose: false,
|
||||||
|
assets: [
|
||||||
|
{ symbol: '', targetWeight: '' }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算当前选中的策略信息
|
// 计算当前选中的策略信息
|
||||||
@ -184,69 +246,204 @@ const onMaTypeChange = (e) => {
|
|||||||
formData.value.maType = types[e.detail.value];
|
formData.value.maType = types[e.detail.value];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onUseCloseChange = (e) => {
|
||||||
|
formData.value.useClose = e.detail.value === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRiskLevelChange = (e) => {
|
||||||
|
const levels = ['low', 'medium', 'high'];
|
||||||
|
formData.value.riskLevel = levels[e.detail.value];
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAsset = () => {
|
||||||
|
formData.value.assets.push({ symbol: '', targetWeight: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeAsset = (index) => {
|
||||||
|
formData.value.assets.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateRiskParityAssets = () => {
|
||||||
|
const assets = formData.value.assets;
|
||||||
|
|
||||||
|
if (assets.length < 2) {
|
||||||
|
uni.showToast({ title: '风险平价策略至少需要2个资产', icon: 'none' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalWeight = 0;
|
||||||
|
for (const asset of assets) {
|
||||||
|
if (!asset.symbol) {
|
||||||
|
uni.showToast({ title: '请填写所有资产代码', icon: 'none' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!asset.targetWeight) {
|
||||||
|
uni.showToast({ title: '请填写所有资产目标权重', icon: 'none' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
totalWeight += parseFloat(asset.targetWeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(totalWeight - 1) > 0.01) {
|
||||||
|
uni.showToast({ title: `资产权重总和必须为100%,当前为${(totalWeight * 100).toFixed(0)}%`, icon: 'none' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!formData.value.alias) {
|
if (!formData.value.name) {
|
||||||
uni.showToast({ title: '请输入模型别名', icon: 'none' });
|
uni.showToast({ title: '请输入策略名称', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理标签,将逗号分隔的字符串转换为数组
|
||||||
|
let tags = [];
|
||||||
|
if (formData.value.tags) {
|
||||||
|
tags = formData.value.tags.split(',').map(tag => tag.trim()).filter(tag => tag);
|
||||||
|
}
|
||||||
|
if (tags.length === 0) {
|
||||||
|
tags = [currentStrategyInfo.value.tag];
|
||||||
|
}
|
||||||
|
|
||||||
const strategyData = {
|
const strategyData = {
|
||||||
|
name: formData.value.name,
|
||||||
type: currentType.value,
|
type: currentType.value,
|
||||||
alias: formData.value.alias,
|
description: formData.value.description || currentStrategyInfo.value.description,
|
||||||
config: {}
|
riskLevel: formData.value.riskLevel,
|
||||||
|
tags: tags,
|
||||||
|
parameters: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (currentType.value) {
|
switch (currentType.value) {
|
||||||
case 'weight':
|
case 'ma_trend':
|
||||||
if (!formData.value.threshold) {
|
if (!formData.value.shortPeriod || !formData.value.longPeriod) {
|
||||||
uni.showToast({ title: '请输入偏离阈值', icon: 'none' });
|
uni.showToast({ title: '请输入短期和长期周期', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strategyData.config = {
|
strategyData.parameters = {
|
||||||
period: formData.value.period,
|
maType: formData.value.maType,
|
||||||
threshold: parseFloat(formData.value.threshold)
|
shortPeriod: parseInt(formData.value.shortPeriod),
|
||||||
|
longPeriod: parseInt(formData.value.longPeriod)
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'ma':
|
case 'risk_parity':
|
||||||
if (!formData.value.fastPeriod || !formData.value.slowPeriod) {
|
if (!formData.value.lookbackPeriod || !formData.value.rebalanceThreshold) {
|
||||||
uni.showToast({ title: '请输入快线和慢线周期', icon: 'none' });
|
uni.showToast({ title: '请输入回看周期和再平衡阈值', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strategyData.config = {
|
if (!validateRiskParityAssets()) {
|
||||||
fastPeriod: parseInt(formData.value.fastPeriod),
|
return;
|
||||||
slowPeriod: parseInt(formData.value.slowPeriod),
|
}
|
||||||
maType: formData.value.maType
|
strategyData.parameters = {
|
||||||
|
lookbackPeriod: parseInt(formData.value.lookbackPeriod),
|
||||||
|
rebalanceThreshold: parseFloat(formData.value.rebalanceThreshold),
|
||||||
|
assets: formData.value.assets.map(asset => ({
|
||||||
|
symbol: asset.symbol,
|
||||||
|
targetWeight: parseFloat(asset.targetWeight)
|
||||||
|
}))
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'chandelier':
|
case 'chandelier_exit':
|
||||||
if (!formData.value.atrPeriod || !formData.value.atrMultiplier) {
|
if (!formData.value.period || !formData.value.multiplier) {
|
||||||
uni.showToast({ title: '请输入ATR周期和倍数', icon: 'none' });
|
uni.showToast({ title: '请输入ATR周期和倍数', icon: 'none' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strategyData.config = {
|
strategyData.parameters = {
|
||||||
atrPeriod: parseInt(formData.value.atrPeriod),
|
period: parseInt(formData.value.period),
|
||||||
atrMultiplier: parseFloat(formData.value.atrMultiplier),
|
multiplier: parseFloat(formData.value.multiplier),
|
||||||
trendMa: formData.value.trendMa ? parseInt(formData.value.trendMa) : null
|
useClose: formData.value.useClose
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('保存策略:', strategyData);
|
console.log('保存策略:', strategyData);
|
||||||
uni.showLoading({ title: '保存中' });
|
uni.showLoading({ title: isEditMode.value ? '更新中' : '保存中' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.strategies.createStrategy(strategyData);
|
let res;
|
||||||
console.log('策略创建成功:', res);
|
if (isEditMode.value) {
|
||||||
|
res = await api.strategies.updateStrategy(strategyId.value, strategyData);
|
||||||
|
console.log('策略更新成功:', res);
|
||||||
|
} else {
|
||||||
|
res = await api.strategies.createStrategy(strategyData);
|
||||||
|
console.log('策略创建成功:', res);
|
||||||
|
}
|
||||||
|
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({ title: '策略已保存', icon: 'success' });
|
uni.showToast({ title: isEditMode.value ? '策略已更新' : '策略已保存', icon: 'success' });
|
||||||
setTimeout(() => uni.navigateBack(), 1500);
|
setTimeout(() => uni.navigateBack(), 1500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('策略创建失败:', error);
|
console.error(isEditMode.value ? '策略更新失败:' : '策略创建失败:', error);
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({ title: '保存失败,请重试', icon: 'none' });
|
uni.showToast({ title: isEditMode.value ? '更新失败,请重试' : '保存失败,请重试', icon: 'none' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 加载策略详情
|
||||||
|
const loadStrategyDetail = async (id) => {
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '加载中' });
|
||||||
|
const response = await api.strategies.getStrategy(id);
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
const data = response.data;
|
||||||
|
isEditMode.value = true;
|
||||||
|
strategyId.value = data.Id;
|
||||||
|
currentType.value = data.Type;
|
||||||
|
|
||||||
|
// 填充表单数据
|
||||||
|
formData.value.name = data.Title || '';
|
||||||
|
formData.value.description = data.Description || '';
|
||||||
|
formData.value.riskLevel = data.RiskLevel || 'medium';
|
||||||
|
formData.value.tags = data.Tags ? data.Tags.join(', ') : '';
|
||||||
|
|
||||||
|
// 根据策略类型填充参数
|
||||||
|
const params = data.Parameters || {};
|
||||||
|
switch (data.Type) {
|
||||||
|
case 'ma_trend':
|
||||||
|
formData.value.maType = params.maType || 'SMA';
|
||||||
|
formData.value.shortPeriod = params.shortPeriod?.toString() || '';
|
||||||
|
formData.value.longPeriod = params.longPeriod?.toString() || '';
|
||||||
|
break;
|
||||||
|
case 'risk_parity':
|
||||||
|
formData.value.lookbackPeriod = params.lookbackPeriod?.toString() || '';
|
||||||
|
formData.value.rebalanceThreshold = params.rebalanceThreshold?.toString() || '';
|
||||||
|
if (params.assets && params.assets.length > 0) {
|
||||||
|
formData.value.assets = params.assets.map(asset => ({
|
||||||
|
symbol: asset.symbol || '',
|
||||||
|
targetWeight: asset.targetWeight?.toString() || ''
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'chandelier_exit':
|
||||||
|
formData.value.period = params.period?.toString() || '';
|
||||||
|
formData.value.multiplier = params.multiplier?.toString() || '';
|
||||||
|
formData.value.useClose = params.useClose || false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('策略详情加载成功:', data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('加载策略详情失败:', error);
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时检查是否是编辑模式
|
||||||
|
onMounted(() => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const currentPage = pages[pages.length - 1];
|
||||||
|
const id = currentPage.options?.id;
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
loadStrategyDetail(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -256,6 +453,35 @@ const submit = async () => {
|
|||||||
padding-bottom: 200rpx; /* 增加底部内边距,防止内容被底部按钮遮挡 */
|
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;
|
||||||
|
border-bottom: 1rpx solid #E5E7EB;
|
||||||
|
}
|
||||||
|
.back-btn {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.nav-title {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
margin-right: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
/* 导航栏 (简化版) */
|
/* 导航栏 (简化版) */
|
||||||
.nav-bar {
|
.nav-bar {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@ -423,9 +649,67 @@ const submit = async () => {
|
|||||||
|
|
||||||
.info-tag { padding: 16rpx; border-radius: 12rpx; margin-top: -10rpx; }
|
.info-tag { padding: 16rpx; border-radius: 12rpx; margin-top: -10rpx; }
|
||||||
.bg-blue-50 { background-color: #EFF6FF; }
|
.bg-blue-50 { background-color: #EFF6FF; }
|
||||||
|
.bg-green-50 { background-color: #ECFDF5; }
|
||||||
|
.bg-orange-50 { background-color: #FFF7ED; }
|
||||||
.text-blue-700 { color: #1D4ED8; }
|
.text-blue-700 { color: #1D4ED8; }
|
||||||
|
.text-green-700 { color: #047857; }
|
||||||
|
.text-orange-700 { color: #C2410C; }
|
||||||
.text-xs { font-size: 22rpx; }
|
.text-xs { font-size: 22rpx; }
|
||||||
|
|
||||||
|
/* 资产配置列表 */
|
||||||
|
.assets-list {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
.asset-item {
|
||||||
|
background-color: #F9FAFB;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
.asset-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
.asset-title {
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
.asset-inputs {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.asset-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.asset-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6B7280;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.add-asset-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
background-color: #ECFDF5;
|
||||||
|
border: 2rpx dashed #10B981;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
.add-asset-btn:active {
|
||||||
|
background-color: #D1FAFA;
|
||||||
|
}
|
||||||
|
.add-asset-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #064E3B;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部悬浮按钮栏 */
|
/* 底部悬浮按钮栏 */
|
||||||
.footer-bar {
|
.footer-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -61,38 +61,46 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, getCurrentInstance } from 'vue';
|
||||||
import { api } from '../../utils/api';
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const api = proxy.$api;
|
||||||
|
|
||||||
// 策略数据
|
|
||||||
const strategies = ref([]);
|
const strategies = ref([]);
|
||||||
|
|
||||||
// 从后端API获取策略数据的函数
|
|
||||||
const fetchStrategies = async () => {
|
const fetchStrategies = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await api.strategies.getStrategies();
|
const response = await api.strategies.getStrategies();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
strategies.value = response.data;
|
strategies.value = response.data.map(item => ({
|
||||||
|
id: item.Id,
|
||||||
|
title: item.Title,
|
||||||
|
tag: item.Tags?.[0] || '策略',
|
||||||
|
desc: item.Description,
|
||||||
|
tags: item.Tags || [],
|
||||||
|
bgClass: item.Type === 'ma_trend' ? 'bg-emerald-900' : item.Type === 'risk_parity' ? 'bg-blue-600' : 'bg-orange-500',
|
||||||
|
tagClass: item.Type === 'ma_trend' ? 'text-emerald-700' : item.Type === 'risk_parity' ? 'text-blue-700' : 'text-orange-700',
|
||||||
|
iconChar: item.Title?.charAt(0) || 'S',
|
||||||
|
btnClass: 'btn-primary',
|
||||||
|
btnText: '配置'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取策略数据失败:', error);
|
console.error('获取策略数据失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面加载时获取数据
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchStrategies();
|
await fetchStrategies();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 跳转到添加/编辑页
|
|
||||||
const goToAdd = () => {
|
const goToAdd = () => {
|
||||||
uni.navigateTo({ url: '/pages/strategies/edit/edit' });
|
uni.navigateTo({ url: '/pages/strategies/edit/edit' });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理卡片按钮点击
|
|
||||||
const handleAction = (item) => {
|
const handleAction = (item) => {
|
||||||
console.log('点击策略:', item.title);
|
console.log('点击策略:', item.title);
|
||||||
uni.navigateTo({ url: '/pages/strategies/edit/edit?type=' + item.id });
|
uni.navigateTo({ url: `/pages/strategies/edit/edit?id=${item.id}` });
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
119
utils/api.js
119
utils/api.js
@ -246,46 +246,157 @@ export const post = (url, data = {}, headers = {}) => {
|
|||||||
return request(url, 'POST', data, headers);
|
return request(url, 'POST', data, headers);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT请求
|
||||||
|
* @param {string} url - 请求URL
|
||||||
|
* @param {object} data - 请求数据
|
||||||
|
* @param {object} headers - 请求头
|
||||||
|
* @returns {Promise} - 返回Promise对象
|
||||||
|
*/
|
||||||
|
export const put = (url, data = {}, headers = {}) => {
|
||||||
|
return request(url, 'PUT', data, headers);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE请求
|
||||||
|
* @param {string} url - 请求URL
|
||||||
|
* @param {object} headers - 请求头
|
||||||
|
* @returns {Promise} - 返回Promise对象
|
||||||
|
*/
|
||||||
|
export const del = (url, headers = {}) => {
|
||||||
|
return request(url, 'DELETE', {}, headers);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API接口封装
|
* API接口封装
|
||||||
*/
|
*/
|
||||||
console.log('📦 api.js 开始导出api对象')
|
console.log('📦 api.js 开始导出api对象')
|
||||||
export const api = {
|
export const api = {
|
||||||
|
/**
|
||||||
|
* 资产相关接口
|
||||||
|
*/
|
||||||
assets: {
|
assets: {
|
||||||
|
/**
|
||||||
|
* 获取资产数据
|
||||||
|
* @returns {Promise} 返回资产数据
|
||||||
|
*/
|
||||||
getAssetData: () => {
|
getAssetData: () => {
|
||||||
console.log('📤 发起 getAssetData 请求');
|
console.log('📤 发起 getAssetData 请求');
|
||||||
return get('/api/v1/portfolio/assets');
|
return get('/api/v1/portfolio/assets');
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 获取持仓信息
|
||||||
|
* @returns {Promise} 返回持仓信息
|
||||||
|
*/
|
||||||
getHoldings: () => {
|
getHoldings: () => {
|
||||||
console.log('📤 发起 getHoldings 请求');
|
console.log('📤 发起 getHoldings 请求');
|
||||||
return get('/api/v1/portfolio');
|
return get('/api/v1/portfolio');
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取投资组合详情
|
||||||
|
* @param {string|number} id - 投资组合ID
|
||||||
|
* @returns {Promise} 返回投资组合详情
|
||||||
|
*/
|
||||||
|
getPortfolio: (id) => {
|
||||||
|
console.log('📤 发起 getPortfolio 请求:', id);
|
||||||
|
return get(`/api/v1/portfolio/${id}`);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取交易记录
|
||||||
|
* @param {object} params - 查询参数
|
||||||
|
* @returns {Promise} 返回交易记录列表
|
||||||
|
*/
|
||||||
|
getTransactions: (params) => {
|
||||||
|
console.log('📤 发起 getTransactions 请求:', params);
|
||||||
|
return get('/api/v1/portfolio/transactions', params);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建交易记录
|
||||||
|
* @param {object} data - 交易数据
|
||||||
|
* @returns {Promise} 返回创建结果
|
||||||
|
*/
|
||||||
|
createTransaction: (data) => {
|
||||||
|
console.log('📤 发起 createTransaction 请求:', data);
|
||||||
|
return post('/api/v1/portfolio/transactions', data);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建投资组合
|
||||||
|
* @param {object} data - 投资组合数据
|
||||||
|
* @returns {Promise} 返回创建结果
|
||||||
|
*/
|
||||||
|
createPortfolio: (data) => {
|
||||||
|
console.log('📤 发起 createPortfolio 请求:', data);
|
||||||
|
return post('/api/v1/portfolio', data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 策略相关接口
|
||||||
|
*/
|
||||||
strategies: {
|
strategies: {
|
||||||
|
/**
|
||||||
|
* 获取策略列表
|
||||||
|
* @returns {Promise} 返回策略列表
|
||||||
|
*/
|
||||||
getStrategies: () => {
|
getStrategies: () => {
|
||||||
console.log('📤 发起 getStrategies 请求');
|
console.log('📤 发起 getStrategies 请求');
|
||||||
return get('/api/v1/strategies');
|
return get('/api/v1/strategy/strategies');
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 获取策略详情
|
||||||
|
* @param {string|number} id - 策略ID
|
||||||
|
* @returns {Promise} 返回策略详情
|
||||||
|
*/
|
||||||
|
getStrategy: (id) => {
|
||||||
|
console.log('📤 发起 getStrategy 请求:', id);
|
||||||
|
return get(`/api/v1/strategy/${id}`);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建策略
|
||||||
|
* @param {object} data - 策略数据
|
||||||
|
* @returns {Promise} 返回创建结果
|
||||||
|
*/
|
||||||
createStrategy: (data) => {
|
createStrategy: (data) => {
|
||||||
console.log('📤 发起 createStrategy 请求:', data);
|
console.log('📤 发起 createStrategy 请求:', data);
|
||||||
return post('/api/v1/strategies', data);
|
return post('/api/v1/strategy', data);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 更新策略
|
||||||
|
* @param {string|number} id - 策略ID
|
||||||
|
* @param {object} data - 更新数据
|
||||||
|
* @returns {Promise} 返回更新结果
|
||||||
|
*/
|
||||||
updateStrategy: (id, data) => {
|
updateStrategy: (id, data) => {
|
||||||
console.log('📤 发起 updateStrategy 请求:', id, data);
|
console.log('📤 发起 updateStrategy 请求:', id, data);
|
||||||
return post(`/api/v1/strategies/${id}`, data);
|
return put(`/api/v1/strategy/${id}`, data);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 删除策略
|
||||||
|
* @param {string|number} id - 策略ID
|
||||||
|
* @returns {Promise} 返回删除结果
|
||||||
|
*/
|
||||||
deleteStrategy: (id) => {
|
deleteStrategy: (id) => {
|
||||||
console.log('📤 发起 deleteStrategy 请求:', id);
|
console.log('📤 发起 deleteStrategy 请求:', id);
|
||||||
return post(`/api/v1/strategies/${id}/delete`, {});
|
return del(`/api/v1/strategy/${id}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户相关接口
|
||||||
|
*/
|
||||||
user: {
|
user: {
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
* @returns {Promise} 返回用户信息
|
||||||
|
*/
|
||||||
getUserInfo: () => {
|
getUserInfo: () => {
|
||||||
console.log('📤 发起 getUserInfo 请求');
|
console.log('📤 发起 getUserInfo 请求');
|
||||||
return get('/api/user/info');
|
return get('/api/user/info');
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 获取用户统计数据
|
||||||
|
* @returns {Promise} 返回用户统计数据
|
||||||
|
*/
|
||||||
getUserStats: () => {
|
getUserStats: () => {
|
||||||
console.log('📤 发起 getUserStats 请求');
|
console.log('📤 发起 getUserStats 请求');
|
||||||
return get('/api/user/stats');
|
return get('/api/user/stats');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user