fix: S2 缺陷修复 — emoji/类型/双重请求/动画/prefers-reduced-motion

This commit is contained in:
2026-06-08 16:53:27 +08:00
parent 618cd20ebf
commit 4e43a9049a
11 changed files with 47 additions and 26 deletions

View File

@@ -1,5 +1,20 @@
import { request } from '@/utils/request' import { request } from '@/utils/request'
/** 反馈条目 */
export interface Feedback {
id: number
user_id: number
nickname?: string
avatar_url?: string
type: string
content: string
contact: string
status: 'pending' | 'processed' | 'ignored'
admin_reply?: string
created_at: string
updated_at?: string
}
/** 提交反馈 */ /** 提交反馈 */
export function submitFeedback(data: { export function submitFeedback(data: {
type: string type: string
@@ -11,7 +26,7 @@ export function submitFeedback(data: {
/** 获取反馈列表(管理员) */ /** 获取反馈列表(管理员) */
export function getFeedbackList(params?: { page?: number; pageSize?: number; status?: string }) { export function getFeedbackList(params?: { page?: number; pageSize?: number; status?: string }) {
return request({ url: '/feedback', data: params }) return request<{ list: Feedback[]; total: number; page: number; pageSize: number }>({ url: '/feedback', data: params })
} }
/** 更新反馈状态(管理员) */ /** 更新反馈状态(管理员) */

View File

@@ -136,7 +136,7 @@ onMounted(async () => {
} }
const pages = getCurrentPages() const pages = getCurrentPages()
const page = pages[pages.length - 1] as any const page = pages[pages.length - 1] as { options?: Record<string, string> }
if (page?.options?.type && ['expense', 'income'].includes(page.options.type)) { if (page?.options?.type && ['expense', 'income'].includes(page.options.type)) {
type.value = page.options.type type.value = page.options.type
} }

View File

@@ -86,6 +86,7 @@ import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
import { waitForReady } from '@/utils/app-ready' import { waitForReady } from '@/utils/app-ready'
import { statusBarHeight } from '@/utils/system' import { statusBarHeight } from '@/utils/system'
import { getFeedbackList, updateFeedbackStatus } from '@/api/feedback' import { getFeedbackList, updateFeedbackStatus } from '@/api/feedback'
import type { Feedback } from '@/api/feedback'
import { API_BASE } from '@/config' import { API_BASE } from '@/config'
import Icon from '@/components/Icon/Icon.vue' import Icon from '@/components/Icon/Icon.vue'
@@ -102,7 +103,7 @@ const statusMap: Record<string, string> = {
ignored: '已忽略' ignored: '已忽略'
} }
const list = ref<any[]>([]) const list = ref<Feedback[]>([])
const loading = ref(false) const loading = ref(false)
const filterStatus = ref('') const filterStatus = ref('')
const page = ref(1) const page = ref(1)
@@ -138,7 +139,7 @@ async function loadList(reset = false) {
} }
loading.value = true loading.value = true
try { try {
const params: any = { page: page.value, pageSize } const params: { page: number; pageSize: number; status?: string } = { page: page.value, pageSize }
if (filterStatus.value) params.status = filterStatus.value if (filterStatus.value) params.status = filterStatus.value
const data = await getFeedbackList(params) const data = await getFeedbackList(params)
if (reset) { if (reset) {
@@ -160,7 +161,7 @@ function loadMore() {
loadList(false) loadList(false)
} }
async function updateStatus(item: any, status: string) { async function updateStatus(item: Feedback, status: string) {
try { try {
await updateFeedbackStatus(item.id, status) await updateFeedbackStatus(item.id, status)
item.status = status item.status = status

View File

@@ -180,14 +180,12 @@ function loadMore() {
function selectDate(date: string) { function selectDate(date: string) {
selectedDate.value = date selectedDate.value = date
page.value = 1 // loadLogs() 由 watch([filterLevel, selectedDate]) 统一触发,避免双重请求
loadLogs()
} }
function onDateChange(e: any) { function onDateChange(e: any) {
selectedDate.value = e.detail.value selectedDate.value = e.detail.value
page.value = 1 // loadLogs() 由 watch 统一触发
loadLogs()
} }
function getBarWidth(count: number): number { function getBarWidth(count: number): number {

View File

@@ -96,7 +96,7 @@ async function loadUsers(reset = false) {
} }
loading.value = true loading.value = true
try { try {
const params: any = { page: page.value, pageSize: 20 } const params: { page: number; pageSize: number; keyword?: string } = { page: page.value, pageSize: 20 }
if (keyword.value.trim()) params.keyword = keyword.value.trim() if (keyword.value.trim()) params.keyword = keyword.value.trim()
const data = await getUsers(params) const data = await getUsers(params)
if (reset) { if (reset) {

View File

@@ -79,8 +79,9 @@
<text class="state-text">加载失败下拉刷新重试</text> <text class="state-text">加载失败下拉刷新重试</text>
</view> </view>
<view class="empty" v-if="txList.length === 0 && !loading && !loadError"> <view class="state-box" v-if="txList.length === 0 && !loading && !loadError">
<text class="empty-text">暂无账单记录</text> <Icon name="edit" :size="48" color="#BFB3B3" />
<text class="state-text">暂无账单记录</text>
</view> </view>
</view> </view>
</template> </template>
@@ -98,6 +99,7 @@ import Skeleton from '@/components/Skeleton/Skeleton.vue'
import FilterPanel from './filter-panel.vue' import FilterPanel from './filter-panel.vue'
import { statusBarHeight } from '@/utils/system' import { statusBarHeight } from '@/utils/system'
import { formatAmount, formatDate } from '@/utils/format' import { formatAmount, formatDate } from '@/utils/format'
import type { Transaction, TransactionParams } from '@/api/transaction'
import type { FilterParams } from '@/api/filter' import type { FilterParams } from '@/api/filter'
const userStore = useUserStore() const userStore = useUserStore()
@@ -106,7 +108,7 @@ const loading = ref(false)
const filterType = ref('all') const filterType = ref('all')
const page = ref(1) const page = ref(1)
const pageSize = 20 const pageSize = 20
const txList = ref<any[]>([]) const txList = ref<Transaction[]>([])
const total = ref(0) const total = ref(0)
const loadError = ref(false) const loadError = ref(false)
let loadSeq = 0 // 请求序号,防止并发覆盖 let loadSeq = 0 // 请求序号,防止并发覆盖
@@ -162,7 +164,7 @@ async function loadTx(reset = false, silent = false) {
if (!silent) txList.value = [] if (!silent) txList.value = []
} }
const seq = ++loadSeq const seq = ++loadSeq
const params: any = { page: page.value, pageSize, group_id: groupStore.currentGroupId } const params: TransactionParams & { group_id?: number | null; category_ids?: string } = { page: page.value, pageSize, group_id: groupStore.currentGroupId }
if (filterType.value !== 'all') params.type = filterType.value if (filterType.value !== 'all') params.type = filterType.value
// 合并高级筛选参数 // 合并高级筛选参数
const af = advancedFilters.value const af = advancedFilters.value
@@ -369,13 +371,6 @@ onPullDownRefresh(() => {
.loading-text { font-size: $font-md; color: $text-muted; } .loading-text { font-size: $font-md; color: $text-muted; }
.empty {
text-align: center;
padding: 120rpx 0;
}
.empty-text { font-size: $font-lg; color: $text-muted; }
.state-box { .state-box {
@include state-box; @include state-box;
justify-content: center; justify-content: center;

View File

@@ -81,6 +81,7 @@ import { useGroupStore } from '@/stores/group'
import { getOverview } from '@/api/stats' import { getOverview } from '@/api/stats'
import { waitForReady } from '@/utils/app-ready' import { waitForReady } from '@/utils/app-ready'
import { formatAmount, getCurrentMonth } from '@/utils/format' import { formatAmount, getCurrentMonth } from '@/utils/format'
import type { Budget } from '@/api/budget'
import { statusBarHeight } from '@/utils/system' import { statusBarHeight } from '@/utils/system'
import BudgetBar from '@/components/BudgetBar/BudgetBar.vue' import BudgetBar from '@/components/BudgetBar/BudgetBar.vue'
import Numpad from '@/components/Numpad/Numpad.vue' import Numpad from '@/components/Numpad/Numpad.vue'
@@ -90,7 +91,7 @@ const budgetStore = useBudgetStore()
const groupStore = useGroupStore() const groupStore = useGroupStore()
const currentMonth = getCurrentMonth() const currentMonth = getCurrentMonth()
const budget = ref<any>(null) const budget = ref<Budget | null>(null)
const budgetAmount = ref(0) const budgetAmount = ref(0)
const monthExpense = ref(0) const monthExpense = ref(0)
const loading = ref(true) const loading = ref(true)

View File

@@ -146,6 +146,7 @@ import { useGroupStore } from '@/stores/group'
import { useNotificationStore } from '@/stores/notification' import { useNotificationStore } from '@/stores/notification'
import { waitForReady } from '@/utils/app-ready' import { waitForReady } from '@/utils/app-ready'
import { getOverview } from '@/api/stats' import { getOverview } from '@/api/stats'
import type { Transaction } from '@/api/transaction'
import { getNotificationImageUrl } from '@/api/notification' import { getNotificationImageUrl } from '@/api/notification'
import { formatAmount, formatAmountRaw } from '@/utils/format' import { formatAmount, formatAmountRaw } from '@/utils/format'
import { statusBarHeight, capsuleRight } from '@/utils/system' import { statusBarHeight, capsuleRight } from '@/utils/system'
@@ -165,7 +166,7 @@ const DEFAULT_BUDGET = 500000 // 5000元
const overview = computed(() => statsStore.overview) const overview = computed(() => statsStore.overview)
const budget = computed(() => budgetStore.budget) const budget = computed(() => budgetStore.budget)
const recentTx = ref<any[]>([]) const recentTx = ref<Transaction[]>([])
const loading = ref(true) const loading = ref(true)
const loadError = ref(false) const loadError = ref(false)
const todayExpense = ref(0) const todayExpense = ref(0)

View File

@@ -39,7 +39,7 @@
</view> </view>
<view class="notif-content"> <view class="notif-content">
<view class="notif-title-row"> <view class="notif-title-row">
<text v-if="item.is_pinned" class="pin-badge">📌</text> <text v-if="item.is_pinned" class="pin-badge">置顶</text>
<text class="notif-title">{{ item.title }}</text> <text class="notif-title">{{ item.title }}</text>
</view> </view>
<text class="notif-body" v-if="item.content">{{ item.content }}</text> <text class="notif-body" v-if="item.content">{{ item.content }}</text>
@@ -355,7 +355,12 @@ onReachBottom(() => {
} }
.pin-badge { .pin-badge {
font-size: $font-md; font-size: $font-xs;
color: $primary;
background: #FFE8E0;
padding: 2rpx 10rpx;
border-radius: $radius-xs;
font-weight: 500;
} }
.notif-title { .notif-title {

View File

@@ -144,6 +144,7 @@ import { useGroupStore } from '@/stores/group'
import { useConfigStore } from '@/stores/config' import { useConfigStore } from '@/stores/config'
import { waitForReady } from '@/utils/app-ready' import { waitForReady } from '@/utils/app-ready'
import { getTransactions } from '@/api/transaction' import { getTransactions } from '@/api/transaction'
import type { Transaction } from '@/api/transaction'
import { formatAmount } from '@/utils/format' import { formatAmount } from '@/utils/format'
import { statusBarHeight } from '@/utils/system' import { statusBarHeight } from '@/utils/system'
import Icon from '@/components/Icon/Icon.vue' import Icon from '@/components/Icon/Icon.vue'
@@ -326,7 +327,7 @@ async function exportData() {
uni.showLoading({ title: '导出中 0%' }) uni.showLoading({ title: '导出中 0%' })
try { try {
// 分页获取全部数据(服务端 pageSize 上限 100 // 分页获取全部数据(服务端 pageSize 上限 100
const allList: any[] = [] const allList: Transaction[] = []
let page = 1 let page = 1
let total = 0 let total = 0
do { do {

View File

@@ -548,6 +548,10 @@ function getBarWidth(amount: number) {
border-radius: 6rpx; border-radius: 6rpx;
background: linear-gradient(90deg, $primary, #FFB89A); background: linear-gradient(90deg, $primary, #FFB89A);
transition: width 0.6s ease-out; transition: width 0.6s ease-out;
@media (prefers-reduced-motion: reduce) {
transition: none;
}
} }
.trend-amount { .trend-amount {