|
@@ -0,0 +1,342 @@
|
|
|
|
|
+import { Image } from "expo-image";
|
|
|
|
|
+import { useLocalSearchParams, useRouter } from "expo-router";
|
|
|
|
|
+import React, { useCallback, useEffect, useState } from "react";
|
|
|
|
|
+import {
|
|
|
|
|
+ ActivityIndicator,
|
|
|
|
|
+ Alert,
|
|
|
|
|
+ FlatList,
|
|
|
|
|
+ ImageBackground,
|
|
|
|
|
+ RefreshControl,
|
|
|
|
|
+ StatusBar,
|
|
|
|
|
+ StyleSheet,
|
|
|
|
|
+ Text,
|
|
|
|
|
+ TouchableOpacity,
|
|
|
|
|
+ View,
|
|
|
|
|
+} from "react-native";
|
|
|
|
|
+import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
|
|
+
|
|
|
|
|
+import { Images } from "@/constants/images";
|
|
|
|
|
+import {
|
|
|
|
|
+ roomAuditPass,
|
|
|
|
|
+ roomAuditRecord,
|
|
|
|
|
+ roomAuditUnpass,
|
|
|
|
|
+} from "@/services/dimension";
|
|
|
|
|
+
|
|
|
|
|
+interface AuditItem {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ avatar: string;
|
|
|
|
|
+ nickname: string;
|
|
|
|
|
+ auditStatus: number; // 0-待审核 1-拒绝 2-通过
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const TABS = [
|
|
|
|
|
+ { title: "待审核", value: 0 },
|
|
|
|
|
+ { title: "审核通过", value: 2 },
|
|
|
|
|
+ { title: "审核不通过", value: 1 },
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+export default function AuditListScreen() {
|
|
|
|
|
+ const { id: roomId } = useLocalSearchParams<{ id: string }>();
|
|
|
|
|
+ const router = useRouter();
|
|
|
|
|
+ const insets = useSafeAreaInsets();
|
|
|
|
|
+
|
|
|
|
|
+ const [activeTab, setActiveTab] = useState(0);
|
|
|
|
|
+ const [list, setList] = useState<AuditItem[]>([]);
|
|
|
|
|
+ const [loading, setLoading] = useState(false);
|
|
|
|
|
+ const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
+ const [page, setPage] = useState(1);
|
|
|
|
|
+ const [hasMore, setHasMore] = useState(true);
|
|
|
|
|
+
|
|
|
|
|
+ const loadData = useCallback(
|
|
|
|
|
+ async (pageNum: number, isRefresh = false) => {
|
|
|
|
|
+ if (loading && !isRefresh) return;
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (pageNum === 1) setLoading(true);
|
|
|
|
|
+ const data = await roomAuditRecord(
|
|
|
|
|
+ pageNum,
|
|
|
|
|
+ 20,
|
|
|
|
|
+ roomId as string,
|
|
|
|
|
+ TABS[activeTab].value,
|
|
|
|
|
+ );
|
|
|
|
|
+ const records = Array.isArray(data)
|
|
|
|
|
+ ? data
|
|
|
|
|
+ : data?.records || [];
|
|
|
|
|
+ if (records.length < 20) setHasMore(false);
|
|
|
|
|
+ if (pageNum === 1 || isRefresh) {
|
|
|
|
|
+ setList(records);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setList((prev) => [...prev, ...records]);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error("加载审核记录失败:", e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ setRefreshing(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ [roomId, activeTab, loading],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ setPage(1);
|
|
|
|
|
+ setList([]);
|
|
|
|
|
+ setHasMore(true);
|
|
|
|
|
+ loadData(1, true);
|
|
|
|
|
+ }, [activeTab]);
|
|
|
|
|
+
|
|
|
|
|
+ const handleRefresh = () => {
|
|
|
|
|
+ setRefreshing(true);
|
|
|
|
|
+ setPage(1);
|
|
|
|
|
+ setHasMore(true);
|
|
|
|
|
+ loadData(1, true);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleLoadMore = () => {
|
|
|
|
|
+ if (!loading && hasMore) {
|
|
|
|
|
+ const np = page + 1;
|
|
|
|
|
+ setPage(np);
|
|
|
|
|
+ loadData(np);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handlePass = async (item: AuditItem, index: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await roomAuditPass(item.id);
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ const newList = [...list];
|
|
|
|
|
+ newList[index] = { ...item, auditStatus: 2 };
|
|
|
|
|
+ setList(newList);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ Alert.alert("提示", "操作失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleReject = async (item: AuditItem, index: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await roomAuditUnpass(item.id);
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ const newList = [...list];
|
|
|
|
|
+ newList[index] = { ...item, auditStatus: 1 };
|
|
|
|
|
+ setList(newList);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ Alert.alert("提示", "操作失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const renderItem = ({
|
|
|
|
|
+ item,
|
|
|
|
|
+ index,
|
|
|
|
|
+ }: {
|
|
|
|
|
+ item: AuditItem;
|
|
|
|
|
+ index: number;
|
|
|
|
|
+ }) => (
|
|
|
|
|
+ <View style={styles.cell}>
|
|
|
|
|
+ <Image
|
|
|
|
|
+ source={{ uri: item.avatar }}
|
|
|
|
|
+ style={styles.avatar}
|
|
|
|
|
+ contentFit="cover"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Text style={styles.nickname} numberOfLines={1}>
|
|
|
|
|
+ {item.nickname}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ <View style={styles.actionArea}>
|
|
|
|
|
+ {item.auditStatus === 0 ? (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ style={[styles.btn, styles.rejectBtn]}
|
|
|
|
|
+ onPress={() => handleReject(item, index)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.btnText}>拒绝</Text>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ style={[styles.btn, styles.passBtn]}
|
|
|
|
|
+ onPress={() => handlePass(item, index)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.btnText}>通过</Text>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ </>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Text style={styles.statusText}>
|
|
|
|
|
+ {item.auditStatus === 2 ? "通过" : "拒绝"}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View style={styles.container}>
|
|
|
|
|
+ <StatusBar barStyle="light-content" />
|
|
|
|
|
+ <ImageBackground
|
|
|
|
|
+ source={{ uri: Images.mine.kaixinMineBg }}
|
|
|
|
|
+ style={styles.background}
|
|
|
|
|
+ resizeMode="cover"
|
|
|
|
|
+ >
|
|
|
|
|
+ {/* 顶部导航 */}
|
|
|
|
|
+ <View style={[styles.header, { paddingTop: insets.top }]}>
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ style={styles.backBtn}
|
|
|
|
|
+ onPress={() => router.back()}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text style={styles.backText}>←</Text>
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ <Text style={styles.title}>审核</Text>
|
|
|
|
|
+ <View style={styles.placeholder} />
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Tab 切换 */}
|
|
|
|
|
+ <View style={styles.tabs}>
|
|
|
|
|
+ {TABS.map((tab, index) => {
|
|
|
|
|
+ const isActive = activeTab === index;
|
|
|
|
|
+ return (
|
|
|
|
|
+ <TouchableOpacity
|
|
|
|
|
+ key={index}
|
|
|
|
|
+ style={[styles.tabItem, isActive && styles.tabItemActive]}
|
|
|
|
|
+ onPress={() => setActiveTab(index)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Text
|
|
|
|
|
+ style={[
|
|
|
|
|
+ styles.tabText,
|
|
|
|
|
+ isActive && styles.tabTextActive,
|
|
|
|
|
+ ]}
|
|
|
|
|
+ >
|
|
|
|
|
+ {tab.title}
|
|
|
|
|
+ </Text>
|
|
|
|
|
+ {isActive && <View style={styles.tabLine} />}
|
|
|
|
|
+ </TouchableOpacity>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </View>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 列表 */}
|
|
|
|
|
+ <FlatList
|
|
|
|
|
+ data={list}
|
|
|
|
|
+ renderItem={renderItem}
|
|
|
|
|
+ keyExtractor={(item, index) => item.id || index.toString()}
|
|
|
|
|
+ contentContainerStyle={styles.listContent}
|
|
|
|
|
+ refreshControl={
|
|
|
|
|
+ <RefreshControl
|
|
|
|
|
+ refreshing={refreshing}
|
|
|
|
|
+ onRefresh={handleRefresh}
|
|
|
|
|
+ tintColor="#fff"
|
|
|
|
|
+ />
|
|
|
|
|
+ }
|
|
|
|
|
+ onEndReached={handleLoadMore}
|
|
|
|
|
+ onEndReachedThreshold={0.3}
|
|
|
|
|
+ ListFooterComponent={
|
|
|
|
|
+ loading && list.length > 0 ? (
|
|
|
|
|
+ <ActivityIndicator
|
|
|
|
|
+ color="#fff"
|
|
|
|
|
+ style={{ marginVertical: 10 }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ) : null
|
|
|
|
|
+ }
|
|
|
|
|
+ ListEmptyComponent={
|
|
|
|
|
+ !loading ? (
|
|
|
|
|
+ <View style={styles.emptyBox}>
|
|
|
|
|
+ <Text style={styles.emptyText}>暂无记录</Text>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ ) : null
|
|
|
|
|
+ }
|
|
|
|
|
+ />
|
|
|
|
|
+ </ImageBackground>
|
|
|
|
|
+ </View>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const styles = StyleSheet.create({
|
|
|
|
|
+ container: { flex: 1, backgroundColor: "#1a1a2e" },
|
|
|
|
|
+ background: { flex: 1 },
|
|
|
|
|
+ header: {
|
|
|
|
|
+ flexDirection: "row",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ justifyContent: "space-between",
|
|
|
|
|
+ paddingHorizontal: 15,
|
|
|
|
|
+ paddingBottom: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ backBtn: {
|
|
|
|
|
+ width: 40,
|
|
|
|
|
+ height: 40,
|
|
|
|
|
+ justifyContent: "center",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ },
|
|
|
|
|
+ backText: { color: "#fff", fontSize: 20 },
|
|
|
|
|
+ title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
|
|
|
|
|
+ placeholder: { width: 40 },
|
|
|
|
|
+
|
|
|
|
|
+ // Tabs
|
|
|
|
|
+ tabs: {
|
|
|
|
|
+ flexDirection: "row",
|
|
|
|
|
+ paddingHorizontal: 10,
|
|
|
|
|
+ borderBottomWidth: 1,
|
|
|
|
|
+ borderBottomColor: "rgba(255,255,255,0.15)",
|
|
|
|
|
+ },
|
|
|
|
|
+ tabItem: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ height: 44,
|
|
|
|
|
+ justifyContent: "center",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ position: "relative",
|
|
|
|
|
+ },
|
|
|
|
|
+ tabItemActive: {},
|
|
|
|
|
+ tabText: { color: "#999", fontSize: 14 },
|
|
|
|
|
+ tabTextActive: { color: "#fff", fontWeight: "bold" },
|
|
|
|
|
+ tabLine: {
|
|
|
|
|
+ position: "absolute",
|
|
|
|
|
+ bottom: 0,
|
|
|
|
|
+ width: "60%",
|
|
|
|
|
+ height: 2,
|
|
|
|
|
+ backgroundColor: "#e79018",
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // List
|
|
|
|
|
+ listContent: { padding: 15, paddingBottom: 100 },
|
|
|
|
|
+ cell: {
|
|
|
|
|
+ flexDirection: "row",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ backgroundColor: "rgba(255,255,255,0.9)",
|
|
|
|
|
+ borderRadius: 8,
|
|
|
|
|
+ paddingHorizontal: 15,
|
|
|
|
|
+ paddingVertical: 12,
|
|
|
|
|
+ marginBottom: 10,
|
|
|
|
|
+ },
|
|
|
|
|
+ avatar: {
|
|
|
|
|
+ width: 40,
|
|
|
|
|
+ height: 40,
|
|
|
|
|
+ borderRadius: 4,
|
|
|
|
|
+ borderWidth: 2,
|
|
|
|
|
+ borderColor: "#000",
|
|
|
|
|
+ marginRight: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ nickname: {
|
|
|
|
|
+ flex: 1,
|
|
|
|
|
+ fontSize: 14,
|
|
|
|
|
+ color: "#333",
|
|
|
|
|
+ fontWeight: "500",
|
|
|
|
|
+ },
|
|
|
|
|
+ actionArea: {
|
|
|
|
|
+ flexDirection: "row",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ },
|
|
|
|
|
+ btn: {
|
|
|
|
|
+ width: 68,
|
|
|
|
|
+ height: 32,
|
|
|
|
|
+ borderRadius: 4,
|
|
|
|
|
+ justifyContent: "center",
|
|
|
|
|
+ alignItems: "center",
|
|
|
|
|
+ marginLeft: 8,
|
|
|
|
|
+ },
|
|
|
|
|
+ rejectBtn: { backgroundColor: "#ff4d4f" },
|
|
|
|
|
+ passBtn: { backgroundColor: "#52c41a" },
|
|
|
|
|
+ btnText: { color: "#fff", fontSize: 13, fontWeight: "bold" },
|
|
|
|
|
+ statusText: {
|
|
|
|
|
+ fontSize: 13,
|
|
|
|
|
+ color: "#666",
|
|
|
|
|
+ fontWeight: "500",
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ emptyBox: { alignItems: "center", paddingTop: 80 },
|
|
|
|
|
+ emptyText: { color: "#999", fontSize: 14 },
|
|
|
|
|
+});
|