fix: S2 缺陷修复 — emoji/类型/双重请求/动画/prefers-reduced-motion
This commit is contained in:
@@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新反馈状态(管理员) */
|
/** 更新反馈状态(管理员) */
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user