| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- <template>
- <el-main>
- <el-card shadow="never">
- <template #header>
- <div class="header-row">
- <span>任务管理</span>
- <el-button type="primary" @click="openCreate">新增任务</el-button>
- </div>
- </template>
- <el-table v-loading="loading" :data="list">
- <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="素材" min-width="180">
- <template #default="{ row }">
- <div v-if="row.materialDisplay.length">
- <el-tag
- v-for="(item, index) in row.materialDisplay"
- :key="`${row.id}-${index}`"
- size="small"
- class="material-tag"
- >
- {{ item }}
- </el-tag>
- </div>
- <span v-else>-</span>
- </template>
- </el-table-column>
- <el-table-column prop="kefu_id" label="客服ID" width="100" />
- <el-table-column prop="price" label="价格" width="110" />
- <el-table-column label="状态" width="120">
- <template #default="{ row }">
- <el-switch
- :model-value="Number(row.status)"
- :active-value="1"
- :inactive-value="0"
- active-text="开启"
- inactive-text="关闭"
- inline-prompt
- active-color="#13ce66"
- inactive-color="#ff4949"
- @change="(val) => changeStatus(row, val)"
- />
- </template>
- </el-table-column>
- <el-table-column prop="end_time" label="截稿日期" min-width="160">
- <template #default="{ row }">
- {{ formatTime(row.end_time) }}
- </template>
- </el-table-column>
- <el-table-column label="操作" width="140" fixed="right">
- <template #default="{ row }">
- <el-button size="small" @click="openEdit(row)">编辑</el-button>
- <el-button size="small" type="danger" @click="removeTask(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="pager">
- <el-pagination
- background
- layout="total, prev, pager, next, sizes"
- :total="total"
- :current-page="query.page"
- :page-size="query.limit"
- :page-sizes="[5, 10, 20, 50]"
- @current-change="handlePageChange"
- @size-change="handleSizeChange"
- />
- </div>
- </el-card>
- <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="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="materialFiles">
- <scUploadFile
- v-model="form.materialFiles"
- :apiObj="$API.common.uploadFile"
- :multiple="true"
- :limit="20"
- tip="请上传素材文件,支持多文件,上传后自动写入素材链接"
- btnTxt="上传素材"
- />
- </el-form-item>
- <el-form-item label="价格" prop="price">
- <el-input v-model="form.price" />
- </el-form-item>
- <el-form-item label="客服ID" prop="kefu_id">
- <el-select
- v-model="form.kefu_id"
- placeholder="请选择客服"
- clearable
- filterable
- :loading="kefuLoading"
- style="width: 100%;"
- >
- <el-option
- v-for="item in kefuOptions"
- :key="item.id"
- :label="item.label"
- :value="item.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="截止时间" prop="endDate">
- <el-date-picker
- v-model="form.endDate"
- type="datetime"
- value-format="x"
- placeholder="选择截止时间"
- style="width: 100%;"
- />
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-switch
- v-model="form.status"
- :active-value="1"
- :inactive-value="0"
- active-text="开启"
- inactive-text="关闭"
- inline-prompt
- active-color="#13ce66"
- inactive-color="#ff4949"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
- </template>
- </el-dialog>
- </el-main>
- </template>
- <script>
- function formatMaterialLabels(material) {
- if (!Array.isArray(material) || !material.length) return [];
- return material
- .map((m) => {
- if (m == null) return "";
- if (typeof m === "string") return m.trim();
- return String(m.fileName || m.name || m.src || m.url || "").trim();
- })
- .filter(Boolean);
- }
- function normalizeMaterialFiles(material) {
- if (!Array.isArray(material) || !material.length) return [];
- return material
- .map((m) => {
- if (typeof m === "string") {
- const url = m.trim();
- if (!url) return null;
- const parts = url.split(/[/\\]/);
- return { name: parts[parts.length - 1] || url, url };
- }
- const src = String(m.src || m.url || "").trim();
- if (!src) return null;
- const fileName = String(m.fileName || m.name || "").trim();
- const name = fileName || src.split(/[/\\]/).pop() || src;
- return { name, url: src };
- })
- .filter(Boolean);
- }
- export default {
- name: "taskList",
- data() {
- return {
- loading: false,
- saving: false,
- list: [],
- total: 0,
- kefuLoading: false,
- kefuOptions: [],
- query: {
- page: 1,
- limit: 15
- },
- dialogVisible: false,
- dialogTitle: "新增任务",
- form: this.getDefaultForm(),
- rules: {
- title: [{ required: true, message: "请输入标题", trigger: "blur" }],
- explain: [{ required: true, message: "请输入说明", trigger: "blur" }],
- materialFiles: [
- {
- validator: (rule, value, callback) => {
- if (Array.isArray(value) && value.length) callback();
- else callback(new Error("请上传素材文件"));
- },
- trigger: "change"
- }
- ],
- price: [{ required: true, message: "请输入价格", trigger: "blur" }],
- kefu_id: [{ required: true, message: "请选择客服", trigger: "change" }],
- endDate: [{ required: true, message: "请选择截止时间", trigger: "change" }]
- }
- };
- },
- created() {
- this.getKefuOptions();
- this.getList();
- },
- methods: {
- getDefaultForm() {
- return {
- id: "",
- title: "",
- explain: "",
- materialFiles: [],
- price: "",
- kefu_id: "",
- endDate: "",
- end_time: "",
- status: 1
- };
- },
- formatTime(ts) {
- if (!ts) return "-";
- const num = Number(ts);
- if (!num) return ts;
- return this.$TOOL.getTime(Math.floor(num / 1000), "YYYY-MM-DD HH:mm:ss");
- },
- async getList() {
- this.loading = true;
- try {
- const res = await this.$API.task.list.get(this.query);
- if (res.code !== 1) {
- this.$message.error(res.msg || "获取任务列表失败");
- return;
- }
- const data = res.data || {};
- const rawList = data.data || data.list || [];
- this.list = rawList.map((item) => ({
- ...item,
- materialDisplay: formatMaterialLabels(item.material),
- // 后端字段为 pirce,这里统一映射给表格展示
- price: item.price || item.pirce || ""
- }));
- this.total = Number(data.count || data.total || this.list.length || 0);
- } finally {
- this.loading = false;
- }
- },
- async getKefuOptions() {
- this.kefuLoading = true;
- try {
- const res = await this.$API.kefu.list.get({ page: 1, limit: 200 });
- if (res.code !== 1) {
- this.$message.error(res.msg || "获取客服列表失败");
- return;
- }
- const data = res.data || {};
- const rawList = data.data || data.list || [];
- this.kefuOptions = rawList.map((item) => ({
- id: item.id,
- value: String(item.id),
- label: item.name ? `${item.name}(id:${item.id})` : String(item.id)
- }));
- } finally {
- this.kefuLoading = false;
- }
- },
- handlePageChange(page) {
- this.query.page = page;
- this.getList();
- },
- handleSizeChange(limit) {
- this.query.limit = limit;
- this.query.page = 1;
- this.getList();
- },
- openCreate() {
- this.dialogTitle = "新增任务";
- this.form = this.getDefaultForm();
- this.dialogVisible = true;
- },
- async openEdit(row) {
- this.dialogTitle = "编辑任务";
- this.form = this.getDefaultForm();
- this.dialogVisible = true;
- const res = await this.$API.task.info.get(row.id);
- if (res.code !== 1) {
- this.$message.error(res.msg || "获取任务详情失败");
- return;
- }
- const info = res.data || {};
- const materialFiles = normalizeMaterialFiles(
- Array.isArray(info.material) ? info.material : []
- );
- const endTime = info.end_time ? Number(info.end_time) * 1000 : "";
- this.form = {
- id: info.id || row.id,
- title: info.title || "",
- explain: info.explain || "",
- materialFiles,
- price: info.price || info.pirce || "",
- kefu_id: String(info.kefu_id || ""),
- endDate: endTime ? String(endTime) : "",
- end_time: String(info.end_time || ""),
- status: Number(info.status ?? 1)
- };
- },
- async submitForm() {
- this.$refs.formRef.validate(async (valid) => {
- if (!valid) return false;
- this.saving = true;
- try {
- const material = (this.form.materialFiles || []).map((f) => ({
- fileName: f.name || "",
- src: f.url || ""
- })).filter((m) => m.src);
- const endTimeSec = this.form.endDate ? Math.floor(Number(this.form.endDate) / 1000) : "";
- const payload = {
- title: this.form.title,
- explain: this.form.explain,
- material,
- price: this.form.price,
- end_time: String(endTimeSec),
- kefu_id: this.form.kefu_id,
- status: String(this.form.status)
- };
- if (this.form.id) payload.id = this.form.id;
- const res = await this.$API.task.save.post(payload);
- if (res.code !== 1) {
- this.$message.error(res.msg || "保存失败");
- return false;
- }
- this.$message.success(res.msg || "保存成功");
- this.dialogVisible = false;
- this.getList();
- } finally {
- this.saving = false;
- }
- });
- },
- async removeTask(row) {
- const confirm = await this.$confirm("确认删除该任务吗?", "提示", {
- type: "warning",
- confirmButtonText: "删除",
- confirmButtonClass: "el-button--danger"
- }).catch(() => {});
- if (confirm !== "confirm") return;
- const res = await this.$API.task.del.delete(row.id);
- if (res.code !== 1) {
- this.$message.error(res.msg || "删除失败");
- return;
- }
- this.$message.success(res.msg || "删除成功");
- this.getList();
- },
- async changeStatus(row, status) {
- const res = await this.$API.task.setStatus.get(row.id, status);
- if (res.code !== 1) {
- this.$message.error(res.msg || "状态更新失败");
- this.getList();
- return;
- }
- this.$message.success(res.msg || "状态更新成功");
- row.status = status;
- }
- }
- };
- </script>
- <style scoped>
- .header-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .pager {
- margin-top: 16px;
- display: flex;
- justify-content: flex-end;
- }
- .material-tag {
- margin-right: 6px;
- margin-bottom: 4px;
- }
- </style>
|