This commit is contained in:
niannian zheng 2026-03-13 10:59:01 +08:00
commit 7dd5767f76
7 changed files with 134 additions and 131 deletions

53
App.vue
View File

@ -12,6 +12,59 @@ export default {
} }
</script> </script>
<template>
<view id="app">
<!-- 全局uView配置 -->
<u-config :config="uConfig">
<router-view />
<!-- 全局toast组件 -->
<u-toast ref="uToast" />
<!-- 全局modal组件 -->
<u-modal ref="uModal" />
</u-config>
</view>
</template>
<script setup>
// uView
const uConfig = {
//
primaryColor: '#064e3b',
successColor: '#10B981',
warningColor: '#f59e0b',
errorColor: '#ef4444'
}
//
uni.$u.toast = (title, options = {}) => {
uni.$refs.uToast.show({
title,
...options
})
}
uni.$u.toast.success = (title) => {
uni.$refs.uToast.show({
title,
type: 'success'
})
}
uni.$u.toast.error = (title) => {
uni.$refs.uToast.show({
title,
type: 'error'
})
}
uni.$u.toast.loading = (title) => {
uni.$refs.uToast.show({
title,
type: 'loading'
})
}
uni.$u.showModal = (options) => {
uni.$refs.uModal.show(options)
}
</script>
<style> <style>
/* 每个页面公共css */ /* 每个页面公共css */
page { page {

View File

@ -14,7 +14,7 @@ app.$mount()
// #ifdef VUE3 // #ifdef VUE3
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
import api from './utils/api' import api from './utils/api'
import uviewPlus from 'uview-plus' import uView from 'uview-ui'
console.log('🚀 应用启动导入api模块') console.log('🚀 应用启动导入api模块')
@ -25,9 +25,9 @@ export function createApp() {
app.config.globalProperties.$api = api app.config.globalProperties.$api = api
console.log('✅ api已全局注册为 $api') console.log('✅ api已全局注册为 $api')
// 注册uView Plus // 注册uView UI 2.0
app.use(uviewPlus) app.use(uView)
console.log('✅ uView Plus 已全局注册') console.log('✅ uView UI 2.0 已全局注册')
return { return {
app app

View File

@ -72,7 +72,7 @@
"easycom": { "easycom": {
"autoscan": true, "autoscan": true,
"custom": { "custom": {
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue" "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
} }
} }
} }

View File

@ -47,7 +47,12 @@
</view> </view>
</view> </view>
<view class="strategy-info-card" v-if="portfolioData.logicModel"> <u-card
v-if="portfolioData.logicModel"
:border="false"
:shadow="false"
class="strategy-info-card"
>
<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">{{ portfolioData.logicModel?.charAt(0) || 'S' }}</text> <text class="st-icon-text text-green">{{ portfolioData.logicModel?.charAt(0) || 'S' }}</text>
@ -66,7 +71,7 @@
<text class="st-status-text">{{ portfolioData.logicModelStatus || '监控中' }}</text> <text class="st-status-text">{{ portfolioData.logicModelStatus || '监控中' }}</text>
</view> </view>
</view> </view>
</view> </u-card>
</view> </view>
<view class="section-container"> <view class="section-container">
@ -76,7 +81,13 @@
</view> </view>
<view class="position-list"> <view class="position-list">
<view class="position-card" v-for="(item, index) in positions" :key="item.id || index"> <u-card
v-for="(item, index) in positions"
:key="item.id || index"
:border="false"
:shadow="false"
class="position-card"
>
<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="index % 2 === 0 ? 'bg-blue-100 text-blue' : 'bg-orange-100 text-orange'"> <view class="stock-icon" :class="index % 2 === 0 ? 'bg-blue-100 text-blue' : 'bg-orange-100 text-orange'">
@ -116,7 +127,7 @@
</view> </view>
</view> </view>
</view> </view>
</view> </u-card>
</view> </view>
</view> </view>
@ -175,14 +186,15 @@
</view> </view>
<view class="form-content"> <view class="form-content">
<view class="form-item relative"> <view class="form-item">
<text class="form-label">{{ transactionType === 'sell' ? '选择持仓' : '股票代码' }}</text> <text class="form-label">{{ transactionType === 'sell' ? '选择持仓' : '股票代码' }}</text>
<input <u-search
v-model="transactionForm.stockCode" v-model="transactionForm.stockCode"
class="form-input"
:placeholder="transactionType === 'sell' ? '请选择要卖出的持仓' : '请输入股票代码'" :placeholder="transactionType === 'sell' ? '请选择要卖出的持仓' : '请输入股票代码'"
:disabled="transactionType === 'sell'" :disabled="transactionType === 'sell'"
@input="transactionType === 'buy' ? searchStock(e.detail.value) : () => {}" :show-action="false"
:border="false"
@input="transactionType === 'buy' ? searchStock($event) : () => {}"
@click="transactionType === 'sell' ? (searchResults = (positions.value || []).map(pos => ({ @click="transactionType === 'sell' ? (searchResults = (positions.value || []).map(pos => ({
ticker: pos.stockCode, ticker: pos.stockCode,
stockName: pos.stockName, stockName: pos.stockName,
@ -191,43 +203,42 @@
amount: pos.amount, amount: pos.amount,
exchange: '' exchange: ''
}))) : () => {}" }))) : () => {}"
/> >
<!-- 搜索下拉列表/持仓列表 --> <!-- 搜索下拉列表 -->
<view class="search-dropdown" v-if="searchResults.length > 0"> <view class="search-dropdown" v-if="searchResults.length > 0" slot="suffix">
<view <u-dropdown>
class="dropdown-item" <u-dropdown-item
v-for="(result, idx) in searchResults" v-for="(result, idx) in searchResults"
:key="idx" :key="idx"
:title="`${result.ticker || result.stockCode} ${result.name || result.stockName}`"
@click="selectStock(result)" @click="selectStock(result)"
> >
<view class="item-left">
<text class="item-ticker">{{ result.ticker || result.stockCode }}</text>
<text class="item-name">{{ result.name || result.stockName }}</text>
<text class="item-type" v-if="result.assetType">{{ result.assetType }}</text> <text class="item-type" v-if="result.assetType">{{ result.assetType }}</text>
</view>
<text class="item-exchange">{{ result.exchange || '' }}</text> <text class="item-exchange">{{ result.exchange || '' }}</text>
</u-dropdown-item>
</u-dropdown>
</view> </view>
</view> </u-search>
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">数量{{ transactionType === 'sell' && maxSellAmount > 0 ? `(最多可卖 ${maxSellAmount} 份)` : '' }}</text> <text class="form-label">数量{{ transactionType === 'sell' && maxSellAmount > 0 ? `(最多可卖 ${maxSellAmount} 份)` : '' }}</text>
<input <u-input
v-model="transactionForm.amount" v-model="transactionForm.amount"
class="form-input"
type="number" type="number"
:placeholder="transactionType === 'sell' && maxSellAmount > 0 ? `请输入数量,不超过 ${maxSellAmount}` : '请输入数量'" :placeholder="transactionType === 'sell' && maxSellAmount > 0 ? `请输入数量,不超过 ${maxSellAmount}` : '请输入数量'"
:border="false"
/> />
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">价格</text> <text class="form-label">价格</text>
<input <u-input
v-model="transactionForm.price" v-model="transactionForm.price"
class="form-input"
type="number" type="number"
step="0.01" step="0.01"
placeholder="请输入价格" placeholder="请输入价格"
:border="false"
/> />
</view> </view>
@ -235,20 +246,25 @@
<view class="form-item"> <view class="form-item">
<text class="form-label">交易时间</text> <text class="form-label">交易时间</text>
<picker mode="date" @change="onDateChange" :value="transactionForm.transactionDate"> <u-datetime-picker
v-model="showDatePicker"
mode="date"
:value="transactionForm.transactionDate"
@confirm="onDateChange"
>
<view class="form-select"> <view class="form-select">
<text>{{ transactionForm.transactionDate }}</text> <text>{{ transactionForm.transactionDate }}</text>
<uni-icons type="bottom" size="14" color="#9CA3AF"></uni-icons> <u-icon name="arrow-down" size="14" color="#9CA3AF"></u-icon>
</view> </view>
</picker> </u-datetime-picker>
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">备注</text> <text class="form-label">备注</text>
<input <u-input
v-model="transactionForm.remark" v-model="transactionForm.remark"
class="form-input"
placeholder="请输入备注" placeholder="请输入备注"
:border="false"
/> />
</view> </view>
</view> </view>
@ -322,6 +338,8 @@ const transactionForm = ref({
}); });
// //
const maxSellAmount = ref(0); const maxSellAmount = ref(0);
//
const showDatePicker = ref(false);
// //
const currencyList = ref([ const currencyList = ref([
@ -337,7 +355,9 @@ const onCurrencyChange = (e) => {
}; };
const onDateChange = (e) => { const onDateChange = (e) => {
transactionForm.value.transactionDate = e.detail.value; // u-datetime-pickerYYYY-MM-DD
transactionForm.value.transactionDate = e.value;
showDatePicker.value = false;
}; };
// //
@ -410,7 +430,7 @@ const fetchPortfolioData = async () => {
} }
} catch (error) { } catch (error) {
console.error('获取投资组合数据失败:', error); console.error('获取投资组合数据失败:', error);
uni.showToast({ title: '加载失败,请重试', icon: 'none' }); uni.$u.toast.error('加载失败,请重试');
} }
}; };
@ -430,7 +450,7 @@ const fetchTransactions = async () => {
} }
} catch (error) { } catch (error) {
console.error('获取交易记录失败:', error); console.error('获取交易记录失败:', error);
uni.showToast({ title: '加载交易记录失败', icon: 'none' }); uni.$u.toast.error('加载交易记录失败');
} }
}; };
@ -488,18 +508,18 @@ const resetTransactionForm = () => {
const submitTransaction = async () => { const submitTransaction = async () => {
// //
if (!transactionForm.value.stockCode) { if (!transactionForm.value.stockCode) {
return uni.showToast({ title: transactionType.value === 'sell' ? '请选择要卖出的持仓' : '请输入股票代码', icon: 'none' }); return uni.$u.toast.error(transactionType.value === 'sell' ? '请选择要卖出的持仓' : '请输入股票代码');
} }
const amount = parseFloat(transactionForm.value.amount); const amount = parseFloat(transactionForm.value.amount);
if (!amount || amount <= 0) { if (!amount || amount <= 0) {
return uni.showToast({ title: '请输入有效的数量', icon: 'none' }); return uni.$u.toast.error('请输入有效的数量');
} }
// //
if (transactionType.value === 'sell' && amount > maxSellAmount.value) { if (transactionType.value === 'sell' && amount > maxSellAmount.value) {
return uni.showToast({ title: `卖出数量不能超过持仓数量 ${maxSellAmount.value}`, icon: 'none' }); return uni.$u.toast.error(`卖出数量不能超过持仓数量 ${maxSellAmount.value}`);
} }
if (!transactionForm.value.price || parseFloat(transactionForm.value.price) <= 0) { if (!transactionForm.value.price || parseFloat(transactionForm.value.price) <= 0) {
return uni.showToast({ title: '请输入有效的价格', icon: 'none' }); return uni.$u.toast.error('请输入有效的价格');
} }
const transactionData = { const transactionData = {
@ -513,13 +533,12 @@ const submitTransaction = async () => {
remark: transactionForm.value.remark remark: transactionForm.value.remark
}; };
uni.showLoading({ title: '提交中...' }); uni.$u.toast.loading('提交中...');
try { try {
const response = await api.assets.createTransaction(transactionData); const response = await api.assets.createTransaction(transactionData);
if (response.code === 200) { if (response.code === 200) {
uni.hideLoading(); uni.$u.toast.success('交易提交成功');
uni.showToast({ title: '交易提交成功', icon: 'success' });
showTransactionForm.value = false; showTransactionForm.value = false;
// //
@ -529,19 +548,20 @@ const submitTransaction = async () => {
} }
} catch (error) { } catch (error) {
console.error('创建交易失败:', error); console.error('创建交易失败:', error);
uni.hideLoading(); uni.$u.toast.error('提交失败,请重试');
uni.showToast({ title: '提交失败,请重试', icon: 'none' });
} }
}; };
// //
const deletePortfolio = async () => { const deletePortfolio = async () => {
uni.showModal({ uni.$u.showModal({
title: '确认删除', title: '确认删除',
content: '删除后所有持仓和交易记录都会丢失,确定要删除这个组合吗?', content: '删除后所有持仓和交易记录都会丢失,确定要删除这个组合吗?',
confirmText: '删除',
confirmColor: '#EF4444',
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
uni.showLoading({ title: '删除中' }); uni.$u.toast.loading('删除中');
try { try {
// //
const response = await uni.request({ const response = await uni.request({
@ -553,15 +573,13 @@ const deletePortfolio = async () => {
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
uni.hideLoading(); uni.$u.toast.success('删除成功');
uni.showToast({ title: '删除成功', icon: 'success' });
setTimeout(() => uni.switchTab({ url: '/pages/index/index' }), 1500); setTimeout(() => uni.switchTab({ url: '/pages/index/index' }), 1500);
} else { } else {
throw new Error('删除失败'); throw new Error('删除失败');
} }
} catch (error) { } catch (error) {
uni.hideLoading(); uni.$u.toast.error('删除失败,请重试');
uni.showToast({ title: '删除失败,请重试', icon: 'none' });
} }
} }
} }
@ -639,14 +657,11 @@ const deletePortfolio = async () => {
/* 策略信息卡片 */ /* 策略信息卡片 */
.strategy-info-card { .strategy-info-card {
background-color: #FFFFFF;
border-radius: 24rpx; border-radius: 24rpx;
padding: 24rpx; padding: 24rpx;
border: 1rpx solid #F3F4F6;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
} }
.st-left { display: flex; align-items: center; gap: 20rpx; } .st-left { display: flex; align-items: center; gap: 20rpx; }
.st-icon-box { .st-icon-box {
@ -680,12 +695,9 @@ const deletePortfolio = async () => {
/* 持仓卡片 */ /* 持仓卡片 */
.position-card { .position-card {
background-color: #fff;
border-radius: 24rpx; border-radius: 24rpx;
padding: 32rpx; padding: 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.02);
border: 1rpx solid #F3F4F6;
} }
.stock-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; } .stock-icon { width: 80rpx; height: 80rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; }
.icon-char { font-size: 32rpx; font-weight: 800; } .icon-char { font-size: 32rpx; font-weight: 800; }
@ -837,67 +849,7 @@ const deletePortfolio = async () => {
color: #9CA3AF; color: #9CA3AF;
} }
/* 搜索下拉列表 */
.relative {
position: relative;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #FFFFFF;
border: 1rpx solid #E5E7EB;
border-radius: 12rpx;
margin-top: 4rpx;
max-height: 300rpx;
overflow-y: auto;
z-index: 100;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.dropdown-item {
padding: 16rpx 20rpx;
border-bottom: 1rpx solid #F3F4F6;
display: flex;
justify-content: space-between;
align-items: center;
}
.dropdown-item:last-child {
border-bottom: none;
}
.dropdown-item:active {
background-color: #F3F4F6;
}
.item-left {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
}
.item-ticker {
font-size: 26rpx;
font-weight: 600;
color: #1F2937;
}
.item-type {
font-size: 18rpx;
color: #064E3B;
background-color: #D1FAE5;
padding: 2rpx 8rpx;
border-radius: 4rpx;
}
.item-exchange {
font-size: 22rpx;
color: #9CA3AF;
}
.form-select { .form-select {
width: 100%; width: 100%;

View File

@ -76,4 +76,5 @@ $uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px; $uni-font-size-paragraph:15px;
/* 引入uView样式 */ /* 引入uView样式 */
@import "uview-plus/index.scss"; @import "uview-ui/theme.scss";
@import "uview-ui/index.scss";

View File

@ -17,10 +17,7 @@ let loginLock = null;
let loadingCount = 0; let loadingCount = 0;
const showLoading = () => { const showLoading = () => {
if (loadingCount === 0) { if (loadingCount === 0) {
uni.showLoading({ uni.$u.toast.loading('加载中...');
title: '加载中...',
mask: true
});
} }
loadingCount++; loadingCount++;
}; };
@ -28,7 +25,7 @@ const hideLoading = () => {
loadingCount--; loadingCount--;
if (loadingCount <= 0) { if (loadingCount <= 0) {
loadingCount = 0; loadingCount = 0;
uni.hideLoading(); uni.$u.toast.hide();
} }
}; };
@ -199,7 +196,7 @@ const requestWithRetry = async (url, method = 'GET', data = {}, headers = {}, re
return await requestWithRetry(url, method, data, headers, retryCount + 1); return await requestWithRetry(url, method, data, headers, retryCount + 1);
} else { } else {
console.error('❌ 达到最大重试次数'); console.error('❌ 达到最大重试次数');
uni.showToast({ title: '系统异常,请稍后重试', icon: 'none', duration: 2000 }); uni.$u.toast.error('系统异常,请稍后重试');
throw new Error('登录已过期,重试次数超限'); throw new Error('登录已过期,重试次数超限');
} }
} else { } else {

View File

@ -4,6 +4,6 @@ import uni from '@dcloudio/vite-plugin-uni'
export default defineConfig({ export default defineConfig({
plugins: [uni()], plugins: [uni()],
optimizeDeps: { optimizeDeps: {
include: ['uview-plus'] include: ['uview-ui']
} }
}) })