refactor: API 请求整合与 Bug 修复
前端重构: - 新增 api/ 目录统一管理所有 API 请求 - 所有 stores 和 pages 改为使用 api 模块 - 修复日期格式化函数,支持 Date 对象和 ISO 字符串 - 修复 TransactionItem 使用 CategoryIcon 组件 - 优化 ChartWrapper 区分 H5 和小程序环境 后端修复: - 修复 auth 中间件 PUBLIC_PATHS 缺少 demo-login - 修复备份工具命令注入漏洞,改用 execFile - 修复 stats 路由 type 参数验证 - 优化分类排序为批量 SQL 更新 - 合并迁移和删除为数据库事务原子操作 - 修复交易和统计日期返回格式 - 新增自动备份功能(启动时备份)
This commit is contained in:
@@ -11,17 +11,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, nextTick, getCurrentInstance } from 'vue'
|
||||
import { onMounted, watch, nextTick, getCurrentInstance } from 'vue'
|
||||
// @ts-ignore
|
||||
import uCharts from '@qiun/ucharts/u-charts.min.js'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
canvasId: string
|
||||
type: 'pie' | 'line' | 'column'
|
||||
chartData: any
|
||||
width?: number
|
||||
height?: number
|
||||
}>()
|
||||
}>(), { width: 600, height: 400 })
|
||||
|
||||
const emit = defineEmits(['tap'])
|
||||
|
||||
@@ -30,34 +30,80 @@ let chartInstance: any = null
|
||||
function drawChart() {
|
||||
if (!props.chartData) return
|
||||
|
||||
const query = uni.createSelectorQuery().in(getCurrentInstance()?.proxy)
|
||||
query.select(`#${props.canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (!res[0]) return
|
||||
// #ifdef H5
|
||||
drawChartH5()
|
||||
// #endif
|
||||
|
||||
const canvas = res[0].node
|
||||
const ctx = canvas.getContext('2d')
|
||||
// #ifdef MP-WEIXIN
|
||||
drawChartMP()
|
||||
// #endif
|
||||
}
|
||||
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio
|
||||
canvas.width = res[0].width * dpr
|
||||
canvas.height = res[0].height * dpr
|
||||
ctx.scale(dpr, dpr)
|
||||
function drawChartH5() {
|
||||
try {
|
||||
const canvas = document.querySelector(`#${props.canvasId}`) as HTMLCanvasElement
|
||||
if (!canvas) return
|
||||
|
||||
const config: any = {
|
||||
context: ctx,
|
||||
canvas2d: true,
|
||||
pixelRatio: dpr,
|
||||
width: res[0].width,
|
||||
height: res[0].height,
|
||||
animation: true,
|
||||
background: '#FFFFFF',
|
||||
padding: [15, 15, 15, 15],
|
||||
...props.chartData
|
||||
}
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
chartInstance = new uCharts(config)
|
||||
})
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
canvas.width = rect.width * dpr
|
||||
canvas.height = rect.height * dpr
|
||||
ctx.scale(dpr, dpr)
|
||||
|
||||
const config: any = {
|
||||
context: ctx,
|
||||
canvas2d: true,
|
||||
pixelRatio: dpr,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
animation: true,
|
||||
background: '#FFFFFF',
|
||||
padding: [15, 15, 15, 15],
|
||||
...props.chartData
|
||||
}
|
||||
|
||||
chartInstance = new uCharts(config)
|
||||
} catch (e) {
|
||||
console.error('[Chart] H5 draw error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function drawChartMP() {
|
||||
try {
|
||||
const query = uni.createSelectorQuery().in(getCurrentInstance()?.proxy)
|
||||
query.select(`#${props.canvasId}`)
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (!res || !res[0]) return
|
||||
|
||||
const canvas = res[0].node
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
const dpr = uni.getSystemInfoSync().pixelRatio
|
||||
canvas.width = res[0].width * dpr
|
||||
canvas.height = res[0].height * dpr
|
||||
ctx.scale(dpr, dpr)
|
||||
|
||||
const config: any = {
|
||||
context: ctx,
|
||||
canvas2d: true,
|
||||
pixelRatio: dpr,
|
||||
width: res[0].width,
|
||||
height: res[0].height,
|
||||
animation: true,
|
||||
background: '#FFFFFF',
|
||||
padding: [15, 15, 15, 15],
|
||||
...props.chartData
|
||||
}
|
||||
|
||||
chartInstance = new uCharts(config)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('[Chart] MP draw error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function onTap(e: any) {
|
||||
@@ -67,13 +113,13 @@ function onTap(e: any) {
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setTimeout(drawChart, 100)
|
||||
setTimeout(drawChart, 300)
|
||||
})
|
||||
})
|
||||
|
||||
watch(() => props.chartData, () => {
|
||||
nextTick(() => {
|
||||
setTimeout(drawChart, 100)
|
||||
setTimeout(drawChart, 300)
|
||||
})
|
||||
}, { deep: true })
|
||||
</script>
|
||||
@@ -87,5 +133,6 @@ watch(() => props.chartData, () => {
|
||||
|
||||
.chart-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
@touchmove="!disableSwipe && onTouchMove($event)"
|
||||
@touchend="!disableSwipe && onTouchEnd()"
|
||||
>
|
||||
<view class="icon-wrap" :style="{ background: item.category_color + '20' }">
|
||||
<text class="icon-text" :style="{ color: item.category_color }">{{ item.category_name?.[0] || '?' }}</text>
|
||||
</view>
|
||||
<CategoryIcon
|
||||
:label="item.category_name?.[0] || '?'"
|
||||
:color="item.category_color || '#BFB3B3'"
|
||||
:bg-color="(item.category_color || '#BFB3B3') + '20'"
|
||||
size="sm"
|
||||
/>
|
||||
<view class="info">
|
||||
<text class="name">{{ item.note || item.category_name || '未分类' }}</text>
|
||||
<text class="meta">{{ item.category_name || '未分类' }}{{ hideDate ? '' : ' · ' + formatDate(item.date) }}</text>
|
||||
@@ -27,6 +30,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { formatAmount, formatDate } from '@/utils/format'
|
||||
import Icon from '@/components/Icon/Icon.vue'
|
||||
import CategoryIcon from '@/components/CategoryIcon/CategoryIcon.vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
item: {
|
||||
@@ -84,7 +88,7 @@ function onTouchEnd() {
|
||||
.tx-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 0;
|
||||
padding: 28rpx 24rpx;
|
||||
gap: 24rpx;
|
||||
border-radius: 20rpx;
|
||||
transition: transform 0.2s ease;
|
||||
@@ -117,21 +121,6 @@ function onTouchEnd() {
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
Reference in New Issue
Block a user