Files
CosScene/clients/pages/shooting/mine.vue
T
2026-05-09 16:40:29 +08:00

341 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref } from "vue";
import { onPullDownRefresh, onReachBottom } from "@dcloudio/uni-app";
import { getMyShootings, getMyApplications } from "@/api/shooting";
const tab = ref("published");
const publishedList = ref([]);
const appliedList = ref([]);
const publishedPage = ref(1);
const appliedPage = ref(1);
const publishedTotal = ref(0);
const appliedTotal = ref(0);
const publishedFinished = ref(false);
const appliedFinished = ref(false);
const loading = ref(false);
const roleLabels = {
photographer: "找摄影",
cosplayer: "找Coser",
both: "不限",
};
const statusLabels = {
open: "招募中",
matched: "已匹配",
closed: "已关闭",
};
const statusColors = {
open: "#22c55e",
matched: "#f59e0b",
closed: "#9ca3af",
};
const auditLabels = {
pending: "待审核",
approved: "已通过",
rejected: "已驳回",
};
const auditColors = {
pending: "#f59e0b",
approved: "#22c55e",
rejected: "#ef4444",
};
const appStatusLabels = {
pending: "待处理",
accepted: "已接受",
rejected: "已拒绝",
};
const appStatusColors = {
pending: "#f59e0b",
accepted: "#22c55e",
rejected: "#ef4444",
};
async function fetchPublished(reset = false) {
if (loading.value) return;
if (reset) {
publishedPage.value = 1;
publishedFinished.value = false;
publishedList.value = [];
}
loading.value = true;
try {
const res = await getMyShootings({
page: publishedPage.value,
page_size: 20,
});
const items = res.items || [];
if (reset) {
publishedList.value = items;
} else {
publishedList.value.push(...items);
}
publishedTotal.value = res.total || 0;
if (publishedList.value.length >= publishedTotal.value)
publishedFinished.value = true;
publishedPage.value++;
} catch (e) {
console.error(e);
} finally {
loading.value = false;
}
}
async function fetchApplied(reset = false) {
if (loading.value) return;
if (reset) {
appliedPage.value = 1;
appliedFinished.value = false;
appliedList.value = [];
}
loading.value = true;
try {
const res = await getMyApplications({
page: appliedPage.value,
page_size: 20,
});
const items = res.items || [];
if (reset) {
appliedList.value = items;
} else {
appliedList.value.push(...items);
}
appliedTotal.value = res.total || 0;
if (appliedList.value.length >= appliedTotal.value)
appliedFinished.value = true;
appliedPage.value++;
} catch (e) {
console.error(e);
} finally {
loading.value = false;
}
}
function switchTab(t) {
tab.value = t;
if (t === "published" && publishedList.value.length === 0) fetchPublished(true);
if (t === "applied" && appliedList.value.length === 0) fetchApplied(true);
}
function goDetail(id) {
uni.navigateTo({ url: `/pages/shooting/detail?id=${id}` });
}
function formatDate(d) {
if (!d) return "";
const dt = new Date(d);
return `${dt.getMonth() + 1}${dt.getDate()}`;
}
onPullDownRefresh(async () => {
if (tab.value === "published") await fetchPublished(true);
else await fetchApplied(true);
uni.stopPullDownRefresh();
});
onReachBottom(() => {
if (tab.value === "published" && !publishedFinished.value) fetchPublished();
if (tab.value === "applied" && !appliedFinished.value) fetchApplied();
});
fetchPublished(true);
</script>
<template>
<view class="mine-shooting-page">
<view class="tabs">
<view
class="tab-item"
:class="{ active: tab === 'published' }"
@tap="switchTab('published')"
>
我发布的
</view>
<view
class="tab-item"
:class="{ active: tab === 'applied' }"
@tap="switchTab('applied')"
>
我报名的
</view>
</view>
<!-- Published list -->
<view v-if="tab === 'published'" class="card-list">
<view
v-for="item in publishedList"
:key="item.id"
class="shoot-card"
@tap="goDetail(item.id)"
>
<view class="card-title-row">
<text class="card-title">{{ item.title }}</text>
<view class="dual-tags">
<view
class="mini-tag"
:style="{ background: auditColors[item.audit_status] || '#9ca3af' }"
>{{ auditLabels[item.audit_status] || item.audit_status }}</view>
<view
class="mini-tag"
:style="{ background: statusColors[item.status] || '#9ca3af' }"
>{{ statusLabels[item.status] || item.status }}</view>
</view>
</view>
<view class="card-sub">
<text>{{ item.city }}</text>
<text v-if="item.style"> · {{ item.style }}</text>
<text> · {{ roleLabels[item.role_needed] || item.role_needed }}</text>
</view>
<view class="card-bottom">
<text class="card-date">{{ formatDate(item.created_at) }}</text>
<text class="apply-count">{{ item.application_count }}人报名</text>
</view>
</view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="publishedFinished && publishedList.length" class="loading-tip">没有更多了</view>
<view v-else-if="!loading && !publishedList.length" class="empty-tip">
<uni-icons type="info" size="40" color="#d1d5db" />
<text>还没有发布约拍</text>
</view>
</view>
<!-- Applied list -->
<view v-if="tab === 'applied'" class="card-list">
<view
v-for="app in appliedList"
:key="app.id"
class="shoot-card"
@tap="goDetail(app.request_id)"
>
<view class="card-title-row">
<text class="card-title">约拍 #{{ app.request_id }}</text>
<view
class="mini-tag"
:style="{ background: appStatusColors[app.status] || '#9ca3af' }"
>{{ appStatusLabels[app.status] || app.status }}</view>
</view>
<view class="card-sub" v-if="app.message">
<text>留言{{ app.message }}</text>
</view>
<view class="card-bottom">
<text class="card-date">{{ formatDate(app.created_at) }}</text>
</view>
</view>
<view v-if="loading" class="loading-tip">加载中...</view>
<view v-else-if="appliedFinished && appliedList.length" class="loading-tip">没有更多了</view>
<view v-else-if="!loading && !appliedList.length" class="empty-tip">
<uni-icons type="info" size="40" color="#d1d5db" />
<text>还没有报名约拍</text>
</view>
</view>
</view>
</template>
<style scoped>
.mine-shooting-page {
min-height: 100vh;
background: #f5f6fa;
}
.tabs {
display: flex;
background: #fff;
border-bottom: 1rpx solid #e5e7eb;
}
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #6b7280;
position: relative;
}
.tab-item.active {
color: #6366f1;
font-weight: 600;
}
.tab-item.active::after {
content: "";
position: absolute;
left: 30%;
right: 30%;
bottom: 0;
height: 4rpx;
background: #6366f1;
border-radius: 4rpx;
}
.card-list {
padding: 0 20rpx;
}
.shoot-card {
background: #fff;
border-radius: 20rpx;
padding: 24rpx 28rpx;
margin-top: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: 28rpx;
font-weight: 600;
color: #1e1e2e;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dual-tags {
display: flex;
gap: 8rpx;
flex-shrink: 0;
margin-left: 12rpx;
}
.mini-tag {
font-size: 20rpx;
color: #fff;
padding: 2rpx 12rpx;
border-radius: 16rpx;
}
.card-sub {
font-size: 24rpx;
color: #6b7280;
margin-top: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12rpx;
}
.card-date {
font-size: 22rpx;
color: #9ca3af;
}
.apply-count {
font-size: 22rpx;
color: #6366f1;
}
.loading-tip {
text-align: center;
font-size: 26rpx;
color: #9ca3af;
padding: 30rpx 0;
}
.empty-tip {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
padding: 120rpx 0;
font-size: 28rpx;
color: #9ca3af;
}
</style>