| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754 |
- <template>
- <div class="performance-edit-page">
- <div class="page-header">
- <div class="header-left" @click="goBack">
- <el-icon class="back-icon">
- <ArrowLeft />
- </el-icon>
- <span class="back-text">返回</span>
- </div>
- <h1 class="page-title">
- {{ viewMode ? "详情" : id ? "编辑" : "新建" }}
- </h1>
- <el-button v-if="viewMode" type="primary" @click="goEdit"> 编辑 </el-button>
- <div v-else class="header-right" @click="goBack">
- <el-icon class="close-icon">
- <Close />
- </el-icon>
- </div>
- </div>
- <div class="page-content">
- <el-form
- ref="formRef"
- :model="form"
- :rules="formRules"
- label-width="120px"
- class="performance-form two-column-form"
- @submit.prevent
- >
- <el-row :gutter="24">
- <el-col :span="12">
- <el-form-item label="演出名称" prop="name" required>
- <el-input
- v-model="form.name"
- placeholder="请输入"
- maxlength="20"
- show-word-limit
- clearable
- :disabled="viewMode"
- class="form-input"
- />
- </el-form-item>
- <el-form-item label="演出海报" required>
- <div class="upload-wrap">
- <UploadImgs
- v-model:file-list="posterFileList"
- :api="uploadImgStore"
- :limit="1"
- :file-size="5"
- :disabled="viewMode"
- class="poster-upload"
- @update:file-list="onPosterChange"
- />
- <span class="upload-tip">({{ posterFileList.length }}/1)</span>
- </div>
- </el-form-item>
- <el-form-item label="演出风格" required>
- <el-checkbox-group v-model="form.style" :disabled="viewMode">
- <el-checkbox v-for="opt in styleOptions" :key="opt" :label="opt">
- {{ opt }}
- </el-checkbox>
- </el-checkbox-group>
- </el-form-item>
- <el-form-item label="演出类型">
- <el-radio-group v-model="form.performanceType" :disabled="viewMode">
- <el-radio value="特邀演出"> 特邀演出 </el-radio>
- <el-radio value="常规演出"> 常规演出 </el-radio>
- </el-radio-group>
- <div class="form-tip">特邀演出为单次</div>
- </el-form-item>
- <el-form-item label="演出频次">
- <el-radio-group v-model="form.frequency" :disabled="viewMode">
- <el-radio value="单次"> 单次 </el-radio>
- <el-radio value="每天定时进行" :disabled="form.performanceType === '特邀演出'"> 每天定时进行 </el-radio>
- <el-radio value="每周定时进行" :disabled="form.performanceType === '特邀演出'"> 每周定时进行 </el-radio>
- </el-radio-group>
- </el-form-item>
- <template v-if="form.frequency === '单次'">
- <el-form-item label="演出时间" required>
- <div class="date-time-row">
- <el-date-picker
- v-model="singleStartDate"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- <el-time-picker
- v-model="singleStartTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- <span class="sep">至</span>
- <el-date-picker
- v-model="singleEndDate"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- <el-time-picker
- v-model="singleEndTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- </div>
- </el-form-item>
- </template>
- <template v-if="form.frequency === '每天定时进行'">
- <el-form-item label="演出日期" required>
- <div class="date-time-row">
- <el-date-picker
- v-model="dailyDateStart"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- <span class="sep">至</span>
- <el-date-picker
- v-model="dailyDateEnd"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- </div>
- </el-form-item>
- <el-form-item label="演出时间" required>
- <div class="date-time-row">
- <el-time-picker
- v-model="dailyTimeStart"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- <span class="sep">至</span>
- <el-time-picker
- v-model="dailyTimeEnd"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- </div>
- </el-form-item>
- </template>
- <template v-if="form.frequency === '每周定时进行'">
- <el-form-item label="演出日期">
- <el-checkbox-group v-model="form.weeklyPerformanceWeekdays" :disabled="viewMode">
- <el-checkbox v-for="day in weekdays" :key="day" :label="day">
- {{ day }}
- </el-checkbox>
- </el-checkbox-group>
- </el-form-item>
- <el-form-item label="演出日期" required>
- <div class="date-time-row">
- <el-date-picker
- v-model="weeklyDateStart"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- <span class="sep">至</span>
- <el-date-picker
- v-model="weeklyDateEnd"
- type="date"
- placeholder="年/月/日"
- value-format="YYYY-MM-DD"
- style="width: 140px"
- :disabled="viewMode"
- />
- </div>
- </el-form-item>
- <el-form-item label="演出时间" required>
- <div class="date-time-row">
- <el-time-picker
- v-model="weeklyTimeStart"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- <span class="sep">至</span>
- <el-time-picker
- v-model="weeklyTimeEnd"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="--:--"
- style="width: 100px"
- :disabled="viewMode"
- />
- </div>
- </el-form-item>
- </template>
- <el-form-item label="图文详情图片">
- <div class="upload-wrap">
- <UploadImgs
- v-model:file-list="detailImageFileList"
- :api="uploadImgStore"
- :limit="9"
- :file-size="5"
- :disabled="viewMode"
- class="detail-upload"
- @update:file-list="onDetailImagesChange"
- />
- <span class="upload-tip">({{ form.detailPicUrl.length }}/9)</span>
- </div>
- </el-form-item>
- <el-form-item label="图文详情描述">
- <el-input
- v-model="form.detailDescription"
- type="textarea"
- placeholder="请输入"
- :rows="3"
- maxlength="300"
- show-word-limit
- :disabled="viewMode"
- class="form-textarea"
- />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="表演嘉宾">
- <el-link v-if="!viewMode" type="primary" @click="openGuestSelect"> 请添加 </el-link>
- <div v-if="form.guests?.length" class="guest-list">
- <div v-for="g in form.guests" :key="String(g.id)" class="guest-item">
- <el-avatar :size="32" :src="g.avatar">
- <el-icon><User /></el-icon>
- </el-avatar>
- <span class="guest-name">{{ g.name || g.nickname || "--" }} {{ g.position ? ` ${g.position}` : "" }}</span>
- <el-icon v-if="!viewMode" class="guest-remove" @click="removeGuest(g)">
- <Close />
- </el-icon>
- </div>
- </div>
- </el-form-item>
- <el-form-item label="演出须知">
- <el-input
- v-model="form.notes"
- type="textarea"
- placeholder="请输入"
- :rows="6"
- maxlength="300"
- show-word-limit
- :disabled="viewMode"
- class="form-textarea"
- />
- </el-form-item>
- <!-- 详情模式:只读状态信息 -->
- <template v-if="viewMode">
- <el-form-item label="提交时间">
- <span class="view-only-value">{{ detailExtra.submitTime || "--" }}</span>
- </el-form-item>
- <el-form-item label="审核状态">
- <span class="view-only-value audit-status" :class="'audit-status--' + detailExtra.auditStatus">{{
- detailExtra.auditStatusLabel || "--"
- }}</span>
- </el-form-item>
- <el-form-item label="审核时间">
- <span class="view-only-value">{{ detailExtra.auditTime || "--" }}</span>
- </el-form-item>
- <el-form-item v-if="detailExtra.auditStatus === 2" label="拒绝原因">
- <div class="view-only-value reject-reason-text">
- {{ detailExtra.auditReason || "--" }}
- </div>
- </el-form-item>
- <el-form-item label="在线状态">
- <span class="view-only-value">{{ detailExtra.onlineStatusLabel || "--" }}</span>
- </el-form-item>
- </template>
- </el-col>
- </el-row>
- </el-form>
- </div>
- <div v-if="!viewMode" class="page-footer">
- <div class="footer-inner">
- <el-button @click="goBack"> 取消 </el-button>
- <el-button type="primary" :loading="submitting" @click="submit"> 提交 </el-button>
- </div>
- </div>
- <GuestSelectDialog
- v-model="guestSelectVisible"
- :selected-ids="selectedGuestIds"
- :selected-guests="form.guests"
- @confirm="onGuestsConfirm"
- />
- </div>
- </template>
- <script setup lang="ts" name="performanceEdit">
- import { ref, computed, watch, onMounted } from "vue";
- import { useRoute, useRouter } from "vue-router";
- import { ElMessage } from "element-plus";
- import { User, Close, ArrowLeft } from "@element-plus/icons-vue";
- import type { FormInstance, FormRules, UploadUserFile } from "element-plus";
- import UploadImgs from "@/components/Upload/Imgs.vue";
- import { uploadImgStore } from "@/api/modules/upload";
- import {
- getPerformanceDetail,
- saveOrUpdatePerformance,
- detailToFormModel,
- getDefaultFormModel,
- buildPerformanceBackendPayload,
- AUDIT_STATUS_LABEL
- } from "@/api/modules/performance";
- import GuestSelectDialog from "./components/GuestSelectDialog.vue";
- import type { GuestItem } from "./components/GuestSelectDialog.vue";
- const styleOptions = [
- "民谣",
- "爵士",
- "摇滚",
- "电音",
- "流行",
- "蓝调",
- "AXAPPELLA (全人声伴奏)",
- "SOUL",
- "朋克",
- "R&B",
- "EDM",
- "原创",
- "古典",
- "金属",
- "说唱"
- ];
- const weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
- const route = useRoute();
- const router = useRouter();
- const formRef = ref<FormInstance>();
- const id = ref<string>("");
- const viewMode = computed(() => route.query.mode === "detail");
- const submitting = ref(false);
- const posterFileList = ref<UploadUserFile[]>([]);
- const detailImageFileList = ref<UploadUserFile[]>([]);
- const guestSelectVisible = ref(false);
- const form = ref<Record<string, any>>(getDefaultFormModel());
- const detailExtra = ref<{
- submitTime: string;
- auditStatus: number;
- auditStatusLabel: string;
- auditTime: string;
- auditReason: string;
- onlineStatusLabel: string;
- }>({
- submitTime: "",
- auditStatus: 0,
- auditStatusLabel: "",
- auditTime: "",
- auditReason: "",
- onlineStatusLabel: ""
- });
- watch(
- () => form.value.performanceType,
- type => {
- if (type === "特邀演出") form.value.frequency = "单次";
- }
- );
- const singleStartDate = computed({
- get: () => form.value.performanceTime?.startDate ?? "",
- set: v => {
- if (!form.value.performanceTime) form.value.performanceTime = {};
- form.value.performanceTime.startDate = v;
- }
- });
- const singleStartTime = computed({
- get: () => form.value.performanceTime?.startTime ?? "",
- set: v => {
- if (!form.value.performanceTime) form.value.performanceTime = {};
- form.value.performanceTime.startTime = v;
- }
- });
- const singleEndDate = computed({
- get: () => form.value.performanceTime?.endDate ?? "",
- set: v => {
- if (!form.value.performanceTime) form.value.performanceTime = {};
- form.value.performanceTime.endDate = v;
- }
- });
- const singleEndTime = computed({
- get: () => form.value.performanceTime?.endTime ?? "",
- set: v => {
- if (!form.value.performanceTime) form.value.performanceTime = {};
- form.value.performanceTime.endTime = v;
- }
- });
- const dailyDateStart = computed({
- get: () => form.value.dailyPerformanceDateRange?.start ?? "",
- set: v => {
- if (!form.value.dailyPerformanceDateRange) form.value.dailyPerformanceDateRange = {};
- form.value.dailyPerformanceDateRange.start = v;
- }
- });
- const dailyDateEnd = computed({
- get: () => form.value.dailyPerformanceDateRange?.end ?? "",
- set: v => {
- if (!form.value.dailyPerformanceDateRange) form.value.dailyPerformanceDateRange = {};
- form.value.dailyPerformanceDateRange.end = v;
- }
- });
- const dailyTimeStart = computed({
- get: () => form.value.dailyPerformanceTimeRange?.start ?? "",
- set: v => {
- if (!form.value.dailyPerformanceTimeRange) form.value.dailyPerformanceTimeRange = {};
- form.value.dailyPerformanceTimeRange.start = v;
- }
- });
- const dailyTimeEnd = computed({
- get: () => form.value.dailyPerformanceTimeRange?.end ?? "",
- set: v => {
- if (!form.value.dailyPerformanceTimeRange) form.value.dailyPerformanceTimeRange = {};
- form.value.dailyPerformanceTimeRange.end = v;
- }
- });
- const weeklyDateStart = computed({
- get: () => form.value.weeklyPerformanceDateRange?.start ?? "",
- set: v => {
- if (!form.value.weeklyPerformanceDateRange) form.value.weeklyPerformanceDateRange = {};
- form.value.weeklyPerformanceDateRange.start = v;
- }
- });
- const weeklyDateEnd = computed({
- get: () => form.value.weeklyPerformanceDateRange?.end ?? "",
- set: v => {
- if (!form.value.weeklyPerformanceDateRange) form.value.weeklyPerformanceDateRange = {};
- form.value.weeklyPerformanceDateRange.end = v;
- }
- });
- const weeklyTimeStart = computed({
- get: () => form.value.weeklyPerformanceTimeRange?.start ?? "",
- set: v => {
- if (!form.value.weeklyPerformanceTimeRange) form.value.weeklyPerformanceTimeRange = {};
- form.value.weeklyPerformanceTimeRange.start = v;
- }
- });
- const weeklyTimeEnd = computed({
- get: () => form.value.weeklyPerformanceTimeRange?.end ?? "",
- set: v => {
- if (!form.value.weeklyPerformanceTimeRange) form.value.weeklyPerformanceTimeRange = {};
- form.value.weeklyPerformanceTimeRange.end = v;
- }
- });
- const selectedGuestIds = computed(() => form.value.guests?.map((g: any) => g.id) ?? []);
- const formRules: FormRules = {
- name: [{ required: true, message: "请输入演出名称", trigger: "blur" }]
- };
- onMounted(() => {
- id.value = (route.query.id as string) || "";
- if (id.value) loadDetail();
- else Object.assign(form.value, getDefaultFormModel());
- syncPosterAndDetailFiles();
- });
- async function loadDetail() {
- if (!id.value) return;
- try {
- const res: any = await getPerformanceDetail(id.value);
- const raw = res?.data ?? res;
- if (raw) {
- Object.assign(form.value, detailToFormModel(raw));
- detailExtra.value = {
- submitTime: raw.createdTime ?? raw.submitTime ?? raw.createTime ?? "",
- auditStatus: raw.reviewStatus ?? raw.auditStatus ?? 0,
- auditStatusLabel: AUDIT_STATUS_LABEL[raw.reviewStatus ?? raw.auditStatus] ?? "",
- auditTime: raw.auditTime ?? raw.reviewTime ?? "",
- auditReason: raw.rejectReason ?? raw.auditReason ?? "",
- onlineStatusLabel: (raw.onlineStatus ?? raw.shelfStatus) === 1 ? "上线" : "下线"
- };
- }
- syncPosterAndDetailFiles();
- } catch (e) {}
- }
- function syncPosterAndDetailFiles() {
- const poster = form.value.posterUrls?.[0] ?? form.value.imgUrl;
- posterFileList.value = poster ? ([{ name: "poster", url: poster }] as UploadUserFile[]) : [];
- const urls = form.value.detailPicUrl ?? [];
- detailImageFileList.value = urls.map((u: string, i: number) => ({ name: `detail-${i}`, url: u })) as UploadUserFile[];
- }
- function onPosterChange(list: UploadUserFile[]) {
- const url = list.length ? ((list[0].url as string) ?? "") : "";
- form.value.posterUrls = url ? [url] : [];
- form.value.imgUrl = url;
- }
- function onDetailImagesChange(list: UploadUserFile[]) {
- form.value.detailPicUrl = list.map(f => (typeof f.url === "string" ? f.url : "")).filter(Boolean);
- }
- function openGuestSelect() {
- guestSelectVisible.value = true;
- }
- function onGuestsConfirm(list: GuestItem[]) {
- form.value.guests = list;
- }
- function removeGuest(g: any) {
- form.value.guests = (form.value.guests || []).filter((x: any) => x.id !== g.id);
- }
- function goBack() {
- router.back();
- }
- function goEdit() {
- router.replace({ path: "/performance/edit", query: { id: id.value } });
- }
- function validateForm(): boolean {
- if (!form.value.name?.trim()) {
- ElMessage.warning("请输入演出名称");
- return false;
- }
- const poster = form.value.posterUrls?.[0] ?? form.value.imgUrl;
- if (!poster) {
- ElMessage.warning("请上传演出海报");
- return false;
- }
- if (!form.value.style?.length) {
- ElMessage.warning("请选择演出风格");
- return false;
- }
- if (form.value.frequency === "单次") {
- const pt = form.value.performanceTime || {};
- if (!pt.startDate || !pt.startTime || !pt.endDate || !pt.endTime) {
- ElMessage.warning("请填写演出时间");
- return false;
- }
- }
- if (form.value.frequency === "每天定时进行") {
- const dr = form.value.dailyPerformanceDateRange || {};
- const tr = form.value.dailyPerformanceTimeRange || {};
- if (!dr.start || !dr.end || !tr.start || !tr.end) {
- ElMessage.warning("请填写演出日期与时间");
- return false;
- }
- }
- if (form.value.frequency === "每周定时进行") {
- const wr = form.value.weeklyPerformanceDateRange || {};
- const tr = form.value.weeklyPerformanceTimeRange || {};
- if (!wr.start || !wr.end || !tr.start || !tr.end) {
- ElMessage.warning("请填写演出日期与时间");
- return false;
- }
- }
- return true;
- }
- async function submit() {
- if (!validateForm() || submitting.value) return;
- submitting.value = true;
- try {
- const payload = buildPerformanceBackendPayload(form.value);
- if (id.value) payload.id = id.value;
- const res: any = await saveOrUpdatePerformance(payload);
- if (res?.code === 200) {
- ElMessage.success(id.value ? "编辑成功" : "新建成功");
- router.back();
- } else {
- ElMessage.error(res?.msg || "提交失败");
- }
- } catch (e) {
- } finally {
- submitting.value = false;
- }
- }
- </script>
- <style scoped lang="scss">
- .performance-edit-page {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- background: #f5f7fa;
- }
- .page-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 56px;
- padding: 0 24px;
- background: #ffffff;
- border-bottom: 1px solid #ebeef5;
- .header-left {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 14px;
- color: #606266;
- cursor: pointer;
- .back-icon {
- font-size: 18px;
- }
- }
- .page-title {
- position: absolute;
- left: 50%;
- margin: 0;
- font-size: 18px;
- font-weight: 600;
- color: #303133;
- transform: translateX(-50%);
- }
- .header-right {
- color: #606266;
- cursor: pointer;
- .close-icon {
- font-size: 20px;
- }
- }
- }
- .page-content {
- flex: 1;
- padding: 24px;
- overflow: auto;
- }
- .performance-form {
- padding: 24px;
- background: #ffffff;
- border-radius: 8px;
- :deep(.el-form-item__label) {
- font-size: 14px;
- color: #606266;
- }
- .form-input {
- max-width: 360px;
- }
- .form-textarea {
- width: 100%;
- max-width: 100%;
- }
- }
- .upload-wrap {
- display: flex;
- gap: 8px;
- align-items: center;
- .poster-upload,
- .detail-upload {
- :deep(.el-upload-list--picture-card) {
- --el-upload-list-picture-card-size: 100px;
- }
- :deep(.el-upload--picture-card) {
- width: 100px;
- height: 100px;
- }
- }
- .upload-tip {
- font-size: 12px;
- color: #909399;
- }
- }
- .form-tip {
- margin-top: 4px;
- font-size: 12px;
- color: #909399;
- }
- .date-time-row {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- align-items: center;
- .sep {
- color: #909399;
- }
- }
- .guest-list {
- margin-top: 8px;
- .guest-item {
- display: flex;
- gap: 8px;
- align-items: center;
- padding: 6px 0;
- .guest-name {
- flex: 1;
- font-size: 14px;
- }
- .guest-remove {
- color: #909399;
- cursor: pointer;
- &:hover {
- color: #f56c6c;
- }
- }
- }
- }
- .view-only-value {
- font-size: 14px;
- color: #606266;
- }
- .reject-reason-text {
- min-height: 60px;
- padding: 8px 0;
- word-break: break-word;
- white-space: pre-wrap;
- }
- .audit-status {
- font-weight: 500;
- &.audit-status--0 {
- color: #e6a23c;
- }
- &.audit-status--1 {
- color: #67c23a;
- }
- &.audit-status--2 {
- color: #f56c6c;
- }
- }
- .page-footer {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 64px;
- background: #ffffff;
- border-top: 1px solid #ebeef5;
- }
- .footer-inner {
- display: flex;
- gap: 12px;
- align-items: center;
- justify-content: center;
- width: 100%;
- max-width: 1100px;
- padding: 0 24px;
- }
- </style>
|