|
|
@@ -12,7 +12,17 @@
|
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
|
<el-table-column prop="title" label="标题" min-width="180" show-overflow-tooltip />
|
|
|
<el-table-column prop="explain" label="说明" min-width="220" show-overflow-tooltip />
|
|
|
-
|
|
|
+ <el-table-column label="类型" width="110" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ taskTypeLabel(row.type) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="标签" width="130" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag v-if="taskLabelName(row)" size="small" type="info">{{ taskLabelName(row) }}</el-tag>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
<el-table-column prop="exp" label="经验值" width="100" align="center" />
|
|
|
<el-table-column prop="total" label="总量" width="100" align="center" />
|
|
|
<el-table-column label="等级" min-width="120" show-overflow-tooltip align="center">
|
|
|
@@ -83,15 +93,49 @@
|
|
|
|
|
|
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="640px" destroy-on-close>
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
|
|
|
+ <el-form-item label="类型" prop="type">
|
|
|
+ <el-radio-group v-model="form.type">
|
|
|
+ <el-radio label="0">普通剪辑</el-radio>
|
|
|
+ <el-radio label="1">剪同款</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="标题" prop="title">
|
|
|
<el-input v-model="form.title" clearable maxlength="80" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="说明" prop="explain">
|
|
|
<el-input v-model="form.explain" type="textarea" :rows="4" />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="标签" prop="label_id">
|
|
|
+ <el-select
|
|
|
+ v-model="form.label_id"
|
|
|
+ placeholder="请选择视频标签"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ :loading="taskLabelLoading"
|
|
|
+ style="width: 100%;"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in taskLabelOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="经验值" prop="exp">
|
|
|
<el-input v-model="form.exp" placeholder="完成任务可获得的经验值" clearable />
|
|
|
</el-form-item>
|
|
|
+ <el-form-item v-if="String(form.type) === '1'" label="封面视频" prop="coverVideoFiles">
|
|
|
+ <scUploadFile
|
|
|
+ v-model="form.coverVideoFiles"
|
|
|
+ :apiObj="$API.common.upload"
|
|
|
+ :multiple="false"
|
|
|
+ :limit="1"
|
|
|
+ accept="video/*"
|
|
|
+ tip="请上传封面视频,限制1个文件"
|
|
|
+ btnTxt="上传视频"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="总量" prop="taskTotal">
|
|
|
<el-input v-model="form.taskTotal" placeholder="任务可领取或完成的总名额" clearable />
|
|
|
</el-form-item>
|
|
|
@@ -115,7 +159,7 @@
|
|
|
<el-form-item label="素材" prop="materialFiles">
|
|
|
<scUploadFile
|
|
|
v-model="form.materialFiles"
|
|
|
- :apiObj="$API.common.uploadFile"
|
|
|
+ :apiObj="$API.common.upload"
|
|
|
:multiple="true"
|
|
|
:limit="20"
|
|
|
tip="请上传素材文件,支持多文件,上传后自动写入素材链接"
|
|
|
@@ -173,6 +217,9 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+/** 与标签管理「视频标签」一致,对应接口 GET /cutter/label/list/3 */
|
|
|
+const TASK_LABEL_LIST_TYPE = 3;
|
|
|
+
|
|
|
function formatMaterialLabels(material) {
|
|
|
if (!Array.isArray(material) || !material.length) return [];
|
|
|
return material
|
|
|
@@ -222,6 +269,38 @@ function normalizeTaskGradeForForm(grade) {
|
|
|
return String(grade);
|
|
|
}
|
|
|
|
|
|
+function normalizeSingleUploadFile(fileValue) {
|
|
|
+ if (!fileValue) return [];
|
|
|
+ if (typeof fileValue === "string") {
|
|
|
+ const url = fileValue.trim();
|
|
|
+ if (!url) return [];
|
|
|
+ const name = url.split(/[/\\]/).pop() || url;
|
|
|
+ return [{ name, url }];
|
|
|
+ }
|
|
|
+ if (typeof fileValue === "object") {
|
|
|
+ const url = String(fileValue.src || fileValue.url || "").trim();
|
|
|
+ if (!url) return [];
|
|
|
+ const name = String(fileValue.fileName || fileValue.name || "").trim() || url.split(/[/\\]/).pop() || url;
|
|
|
+ return [{ name, url }];
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+}
|
|
|
+
|
|
|
+function taskTypeLabel(type) {
|
|
|
+ const val = String(type ?? "0");
|
|
|
+ return val === "1" ? "剪同款" : "普通剪辑";
|
|
|
+}
|
|
|
+
|
|
|
+/** 列表接口 label 可能为 { id, label_name } */
|
|
|
+function taskLabelName(row) {
|
|
|
+ const label = row?.label;
|
|
|
+ if (label != null && typeof label === "object") {
|
|
|
+ const name = label.label_name != null ? String(label.label_name).trim() : "";
|
|
|
+ return name || "";
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+
|
|
|
export default {
|
|
|
name: "taskList",
|
|
|
data() {
|
|
|
@@ -234,6 +313,8 @@ export default {
|
|
|
kefuOptions: [],
|
|
|
levelLoading: false,
|
|
|
levelOptions: [],
|
|
|
+ taskLabelLoading: false,
|
|
|
+ taskLabelOptions: [],
|
|
|
query: {
|
|
|
page: 1,
|
|
|
limit: 15
|
|
|
@@ -244,6 +325,7 @@ export default {
|
|
|
rules: {
|
|
|
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
|
|
|
explain: [{ required: true, message: "请输入说明", trigger: "blur" }],
|
|
|
+ type: [{ required: true, message: "请选择类型", trigger: "change" }],
|
|
|
materialFiles: [
|
|
|
{
|
|
|
validator: (rule, value, callback) => {
|
|
|
@@ -253,10 +335,24 @@ export default {
|
|
|
trigger: "change"
|
|
|
}
|
|
|
],
|
|
|
+ coverVideoFiles: [
|
|
|
+ {
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ if (String(this.form.type) !== "1") {
|
|
|
+ callback();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (Array.isArray(value) && value.length && value[0]?.url) callback();
|
|
|
+ else callback(new Error("请上传封面视频"));
|
|
|
+ },
|
|
|
+ trigger: "change"
|
|
|
+ }
|
|
|
+ ],
|
|
|
price: [{ required: true, message: "请输入价格", trigger: "blur" }],
|
|
|
exp: [{ required: true, message: "请输入经验值", trigger: "blur" }],
|
|
|
taskTotal: [{ required: true, message: "请输入总量", trigger: "blur" }],
|
|
|
grade: [{ required: true, message: "请选择等级", trigger: "change" }],
|
|
|
+ label_id: [{ required: true, message: "请选择标签", trigger: "change" }],
|
|
|
kefu_id: [{ required: true, message: "请选择客服", trigger: "change" }],
|
|
|
endDate: [{ required: true, message: "请选择截止时间", trigger: "change" }]
|
|
|
}
|
|
|
@@ -265,6 +361,7 @@ export default {
|
|
|
created() {
|
|
|
this.getKefuOptions();
|
|
|
this.getLevelOptions();
|
|
|
+ this.getTaskLabelOptions();
|
|
|
this.getList();
|
|
|
},
|
|
|
methods: {
|
|
|
@@ -273,12 +370,16 @@ export default {
|
|
|
id: "",
|
|
|
title: "",
|
|
|
explain: "",
|
|
|
+ type: "0",
|
|
|
+ coverVideoFiles: [],
|
|
|
+ cover_video: "",
|
|
|
materialFiles: [],
|
|
|
price: "",
|
|
|
exp: "",
|
|
|
taskTotal: "",
|
|
|
grade: "",
|
|
|
kefu_id: "",
|
|
|
+ label_id: "",
|
|
|
endDate: "",
|
|
|
end_time: "",
|
|
|
status: 1
|
|
|
@@ -291,6 +392,8 @@ export default {
|
|
|
return this.$TOOL.getTime(Math.floor(num / 1000), "YYYY-MM-DD HH:mm:ss");
|
|
|
},
|
|
|
taskGradeNameLabel,
|
|
|
+ taskTypeLabel,
|
|
|
+ taskLabelName,
|
|
|
async getList() {
|
|
|
this.loading = true;
|
|
|
try {
|
|
|
@@ -353,6 +456,25 @@ export default {
|
|
|
this.levelLoading = false;
|
|
|
}
|
|
|
},
|
|
|
+ async getTaskLabelOptions() {
|
|
|
+ this.taskLabelLoading = true;
|
|
|
+ try {
|
|
|
+ const res = await this.$API.cutter.labelList.get(TASK_LABEL_LIST_TYPE);
|
|
|
+ if (res.code !== 1) {
|
|
|
+ this.$message.error(res.msg || "获取标签列表失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const raw = res.data;
|
|
|
+ const rawList = Array.isArray(raw) ? raw : raw?.list || raw?.data || [];
|
|
|
+ this.taskLabelOptions = rawList.map((item) => ({
|
|
|
+ id: item.id,
|
|
|
+ value: String(item.id),
|
|
|
+ label: item.label_name ?? item.name ?? String(item.id)
|
|
|
+ }));
|
|
|
+ } finally {
|
|
|
+ this.taskLabelLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
handlePageChange(page) {
|
|
|
this.query.page = page;
|
|
|
this.getList();
|
|
|
@@ -385,12 +507,19 @@ export default {
|
|
|
id: info.id || row.id,
|
|
|
title: info.title || "",
|
|
|
explain: info.explain || "",
|
|
|
+ type: String(info.type ?? "0"),
|
|
|
+ coverVideoFiles: normalizeSingleUploadFile(info.cover_video),
|
|
|
+ cover_video: String(info.cover_video || ""),
|
|
|
materialFiles,
|
|
|
price: info.price || info.pirce || "",
|
|
|
exp: info.exp != null && info.exp !== "" ? String(info.exp) : "",
|
|
|
taskTotal: info.total != null && info.total !== "" ? String(info.total) : "",
|
|
|
grade: normalizeTaskGradeForForm(info.grade),
|
|
|
kefu_id: String(info.kefu_id || ""),
|
|
|
+ label_id:
|
|
|
+ info.label_id != null && info.label_id !== ""
|
|
|
+ ? String(info.label_id)
|
|
|
+ : "",
|
|
|
endDate: endTime ? String(endTime) : "",
|
|
|
end_time: String(info.end_time || ""),
|
|
|
status: Number(info.status ?? 1)
|
|
|
@@ -405,15 +534,19 @@ export default {
|
|
|
fileName: f.name || "",
|
|
|
src: f.url || ""
|
|
|
})).filter((m) => m.src);
|
|
|
+ const coverVideo = (this.form.coverVideoFiles || [])[0]?.url || "";
|
|
|
const endTimeSec = this.form.endDate ? Math.floor(Number(this.form.endDate) / 1000) : "";
|
|
|
const payload = {
|
|
|
title: this.form.title,
|
|
|
explain: this.form.explain,
|
|
|
+ type: String(this.form.type),
|
|
|
+ cover_video: coverVideo,
|
|
|
material,
|
|
|
price: this.form.price,
|
|
|
exp: this.form.exp,
|
|
|
total: this.form.taskTotal,
|
|
|
grade: this.form.grade,
|
|
|
+ label_id: this.form.label_id,
|
|
|
end_time: String(endTimeSec),
|
|
|
kefu_id: this.form.kefu_id,
|
|
|
status: String(this.form.status)
|