|
@@ -0,0 +1,1158 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="watch-reward-page" v-loading="loading">
|
|
|
|
|
+ <template v-if="liveType == 2">
|
|
|
|
|
+ <div class="page-banner">
|
|
|
|
|
+ <div class="banner-icon tone-green-banner"><i class="el-icon-circle-check"></i></div>
|
|
|
|
|
+ <div class="banner-copy">
|
|
|
|
|
+ <div class="banner-title">完课奖励</div>
|
|
|
|
|
+ <div class="banner-desc">观看进度达到完课标准后自动发放积分 / 优惠券</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="reward-switch-card">
|
|
|
|
|
+ <div class="switch-info">
|
|
|
|
|
+ <div class="switch-title">开启完课奖励</div>
|
|
|
|
|
+ <div class="switch-hint">与观看奖励相互独立,关闭后停止完课发放,不影响观看时长奖励</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-switch v-model="form.completionEnabled" active-color="#1890ff"></el-switch>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="form.completionEnabled" class="config-flow">
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ :model="form"
|
|
|
|
|
+ :rules="rules"
|
|
|
|
|
+ ref="completionForm"
|
|
|
|
|
+ label-width="120px"
|
|
|
|
|
+ class="reward-form"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="flow-card">
|
|
|
|
|
+ <div class="flow-head">
|
|
|
|
|
+ <span class="step-no">1</span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="flow-title">完课条件</div>
|
|
|
|
|
+ <div class="flow-sub">设置用户达到完课标准后可领取奖励</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="cond-fields" style="border-top: none; padding-top: 0; margin-top: 0;">
|
|
|
|
|
+ <el-form-item label="完课要求" prop="completionRate">
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="form.completionRate"
|
|
|
|
|
+ :min="1"
|
|
|
|
|
+ :max="100"
|
|
|
|
|
+ :precision="0"
|
|
|
|
|
+ class="field-sm"
|
|
|
|
|
+ ></el-input-number>
|
|
|
|
|
+ <span class="field-tip">%(观看时长 / 视频总时长)</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flow-card">
|
|
|
|
|
+ <div class="flow-head">
|
|
|
|
|
+ <span class="step-no">2</span>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="flow-title">完课奖励</div>
|
|
|
|
|
+ <div class="flow-sub">可多选,完课达标后按所选类型发放</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label-width="0" prop="completionTypes" class="type-form-item">
|
|
|
|
|
+ <div class="reward-type-grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="item in completionTypeCards"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ class="reward-type-card"
|
|
|
|
|
+ :class="{ active: hasCompletionType(item.value) }"
|
|
|
|
|
+ @click="toggleCompletionType(item.value)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="rtype-icon" :class="item.tone"><i :class="item.icon"></i></div>
|
|
|
|
|
+ <div class="rtype-name">{{ item.label }}</div>
|
|
|
|
|
+ <div class="rtype-desc">{{ item.desc }}</div>
|
|
|
|
|
+ <i class="el-icon-check rtype-check" v-if="hasCompletionType(item.value)"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="hasCompletionType('2')" class="detail-panel">
|
|
|
|
|
+ <div class="detail-head">
|
|
|
|
|
+ <i class="el-icon-medal"></i>
|
|
|
|
|
+ <span>完课积分配置</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form-item label-width="0" prop="pointsConfig">
|
|
|
|
|
+ <div class="points-grid">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(point, index) in form.pointsConfig"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="points-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="points-day">第{{ index + 1 }}天</span>
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="form.pointsConfig[index]"
|
|
|
|
|
+ :min="0"
|
|
|
|
|
+ :precision="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ class="points-input"
|
|
|
|
|
+ ></el-input-number>
|
|
|
|
|
+ <span class="points-unit">积分</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="hasCompletionType('3')" class="detail-panel">
|
|
|
|
|
+ <div class="detail-head">
|
|
|
|
|
+ <i class="el-icon-ticket"></i>
|
|
|
|
|
+ <span>完课优惠券</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form-item label="选择优惠券" prop="finishCoupons">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="finishCouponSelectedIds"
|
|
|
|
|
+ multiple
|
|
|
|
|
+ filterable
|
|
|
|
|
+ collapse-tags
|
|
|
|
|
+ placeholder="可多选优惠券"
|
|
|
|
|
+ class="field-lg"
|
|
|
|
|
+ @change="onFinishCouponIdsChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="item in couponSelectOptions"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :label="item.label"
|
|
|
|
|
+ :value="item.value"
|
|
|
|
|
+ ></el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ <span class="field-tip">可多选,每张券可单独设置领取数量</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <div class="coupon-multi-list" v-if="form.finishCoupons && form.finishCoupons.length">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(row, idx) in form.finishCoupons"
|
|
|
|
|
+ :key="'fc-' + row.couponId"
|
|
|
|
|
+ class="coupon-multi-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="cmi-main">
|
|
|
|
|
+ <div class="cmi-title">{{ resolveCouponTitle(row.couponId) }}</div>
|
|
|
|
|
+ <div class="cmi-meta">
|
|
|
|
|
+ <span v-for="kv in buildCouponMeta(row.couponId)" :key="kv">{{ kv }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="cmi-count">
|
|
|
|
|
+ <span class="cmi-count-label">领取数量</span>
|
|
|
|
|
+ <el-input-number
|
|
|
|
|
+ v-model="row.count"
|
|
|
|
|
+ :min="1"
|
|
|
|
|
+ :precision="0"
|
|
|
|
|
+ controls-position="right"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ ></el-input-number>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button type="text" class="danger-text" icon="el-icon-delete" @click="removeFinishCoupon(idx)">移除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form-item label="完课答题" prop="finishQuestionIds" class="question-item">
|
|
|
|
|
+ <div class="question-block">
|
|
|
|
|
+ <div class="question-toolbar">
|
|
|
|
|
+ <el-button type="primary" plain icon="el-icon-plus" size="small" @click="openQuestionDialog">选择题目</el-button>
|
|
|
|
|
+ <el-link type="primary" :underline="false" @click="handleToQuestionBank">前往直播题库 ></el-link>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ v-if="selectedQuestionList.length > 0"
|
|
|
|
|
+ :data="selectedQuestionList"
|
|
|
|
|
+ class="question-table"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ border
|
|
|
|
|
+ max-height="240"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column prop="title" label="题目" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="type" label="类型" width="100">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ {{ scope.row.type === 1 ? '单选题' : '多选题' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="80">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ <el-button type="text" class="danger-text" @click="removeSelectedQuestion(scope.row)">移除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div v-else class="empty-hint">观看完视频后将自动弹出完课题目,用户答对后发放优惠券</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="form-actions">
|
|
|
|
|
+ <el-button type="primary" size="medium" icon="el-icon-check" @click="saveCompletionReward">保存配置</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="disabled-hint">
|
|
|
|
|
+ <i class="el-icon-info"></i>
|
|
|
|
|
+ <span>完课奖励已关闭,开启后可配置完课条件与奖励内容</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="disabled-hint">
|
|
|
|
|
+ <i class="el-icon-info"></i>
|
|
|
|
|
+ <span>仅录播支持完课奖励</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ title="选择完课题目"
|
|
|
|
|
+ :visible.sync="questionDialogVisible"
|
|
|
|
|
+ width="800px"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
|
+ custom-class="question-select-dialog"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="dialog-search">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="questionSearchTitle"
|
|
|
|
|
+ placeholder="请输入题目标题"
|
|
|
|
|
+ class="field-md"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ @keyup.enter.native="handleQuestionSearch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button slot="append" icon="el-icon-search" @click="handleQuestionSearch"></el-button>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ ref="questionTable"
|
|
|
|
|
+ :data="questionOptionList"
|
|
|
|
|
+ v-loading="questionLoading"
|
|
|
|
|
+ row-key="id"
|
|
|
|
|
+ @selection-change="handleQuestionSelectionChange"
|
|
|
|
|
+ @row-click="handleQuestionRowClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column type="selection" width="55" :reserve-selection="true" />
|
|
|
|
|
+ <el-table-column prop="title" label="题目" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="type" label="类型" width="100">
|
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
|
+ {{ scope.row.type === 1 ? '单选题' : '多选题' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="createBy" label="创建人" width="120" />
|
|
|
|
|
+ <el-table-column prop="createTime" label="创建时间" width="160" />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="questionOptionTotal > 0"
|
|
|
|
|
+ :total="questionOptionTotal"
|
|
|
|
|
+ :page.sync="questionQueryParams.pageNum"
|
|
|
|
|
+ :limit.sync="questionQueryParams.pageSize"
|
|
|
|
|
+ @pagination="loadQuestionOptions"
|
|
|
|
|
+ style="margin-top: 16px;"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div slot="footer">
|
|
|
|
|
+ <span class="dialog-selected-count">
|
|
|
|
|
+ 已选择 <em>{{ tempSelectedQuestions.length }}</em> 题
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <el-button @click="questionDialogVisible = false">取 消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="confirmQuestionSelection">确 定</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import {addConfig, getConfig, updateConfig} from "@/api/live/liveQuestionLive";
|
|
|
|
|
+import {listLiveCouponOn} from "@/api/live/liveCoupon";
|
|
|
|
|
+import {listLiveQuestionBank, getLiveQuestionBank} from "@/api/live/liveQuestionBank";
|
|
|
|
|
+
|
|
|
|
|
+const DEFAULT_POINTS_CONFIG = [100, 110, 120, 130, 140, 150, 160, 170, 180, 200];
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ props: {
|
|
|
|
|
+ liveType: {
|
|
|
|
|
+ type: Number,
|
|
|
|
|
+ default: 1
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ loading: true,
|
|
|
|
|
+ liveId: null,
|
|
|
|
|
+ existingConfig: {},
|
|
|
|
|
+ form: {
|
|
|
|
|
+ id: null,
|
|
|
|
|
+ liveId: null,
|
|
|
|
|
+ completionEnabled: false,
|
|
|
|
|
+ completionRate: 90,
|
|
|
|
|
+ pointsConfig: [...DEFAULT_POINTS_CONFIG],
|
|
|
|
|
+ completionTypes: ['2'],
|
|
|
|
|
+ finishCouponId: null,
|
|
|
|
|
+ finishCouponCount: 1,
|
|
|
|
|
+ finishCoupons: [],
|
|
|
|
|
+ finishQuestionIds: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ finishCouponSelectedIds: [],
|
|
|
|
|
+ rules: {
|
|
|
|
|
+ completionRate: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
|
|
+ if (!value && value !== 0) {
|
|
|
|
|
+ callback(new Error('请输入完课要求'));
|
|
|
|
|
+ } else if (value < 1 || value > 100) {
|
|
|
|
|
+ callback(new Error('完课要求范围为1-100'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'blur'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ completionTypes: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
|
|
+ if (!value || !value.length) {
|
|
|
|
|
+ callback(new Error('请至少选择一种完课奖励类型'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'change'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ finishCoupons: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
|
|
+ if (this.hasCompletionType('3')) {
|
|
|
|
|
+ if (!value || !value.length) {
|
|
|
|
|
+ callback(new Error('请至少选择一张完课优惠券'));
|
|
|
|
|
+ } else if (value.some(item => !item.count || item.count < 1)) {
|
|
|
|
|
+ callback(new Error('每张优惠券领取数量至少为1'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'change'
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ finishQuestionIds: [
|
|
|
|
|
+ {
|
|
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
|
|
+ if (this.hasCompletionType('3')) {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ callback(new Error('请选择完课题目'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ callback();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ trigger: 'change'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ completionTypeCards: [
|
|
|
|
|
+ { label: '完课积分', value: '2', icon: 'el-icon-medal', tone: 'tone-orange', desc: '按天发放完课积分' },
|
|
|
|
|
+ { label: '完课优惠券', value: '3', icon: 'el-icon-ticket', tone: 'tone-green', desc: '答对题目后发放优惠券' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ couponSelectOptions: [],
|
|
|
|
|
+ couponListData: [],
|
|
|
|
|
+ questionDialogVisible: false,
|
|
|
|
|
+ questionLoading: false,
|
|
|
|
|
+ questionSearchTitle: '',
|
|
|
|
|
+ questionOptionList: [],
|
|
|
|
|
+ questionOptionTotal: 0,
|
|
|
|
|
+ questionQueryParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ title: null,
|
|
|
|
|
+ status: 1
|
|
|
|
|
+ },
|
|
|
|
|
+ selectedQuestionList: [],
|
|
|
|
|
+ tempSelectedQuestions: []
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ watch: {
|
|
|
|
|
+ '$route.query': {
|
|
|
|
|
+ handler() {
|
|
|
|
|
+ this.liveId = this.$route.params.liveId;
|
|
|
|
|
+ this.form.liveId = this.liveId;
|
|
|
|
|
+ if (this.liveId == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.getLiveConfig();
|
|
|
|
|
+ },
|
|
|
|
|
+ immediate: true
|
|
|
|
|
+ },
|
|
|
|
|
+ 'form.completionEnabled': {
|
|
|
|
|
+ handler(newValue, oldValue) {
|
|
|
|
|
+ if (oldValue === true && newValue === false && this.liveId) {
|
|
|
|
|
+ this.autoSaveEnabled();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ couponTypeLabel(type) {
|
|
|
|
|
+ if (type === 0) return '普通券';
|
|
|
|
|
+ if (type === 1) return '套餐券';
|
|
|
|
|
+ if (type === 2) return '制单券';
|
|
|
|
|
+ return '无门槛券';
|
|
|
|
|
+ },
|
|
|
|
|
+ resolveCouponTitle(couponId) {
|
|
|
|
|
+ const coupon = this.couponListData.find(item => item.couponId === couponId);
|
|
|
|
|
+ return coupon ? coupon.title : `优惠券#${couponId}`;
|
|
|
|
|
+ },
|
|
|
|
|
+ buildCouponMeta(couponId) {
|
|
|
|
|
+ const coupon = this.couponListData.find(item => item.couponId === couponId);
|
|
|
|
|
+ if (!coupon) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ return [
|
|
|
|
|
+ `面值?${coupon.couponPrice}`,
|
|
|
|
|
+ `满?${coupon.useMinPrice}`,
|
|
|
|
|
+ `${coupon.couponTime}天`,
|
|
|
|
|
+ this.couponTypeLabel(coupon.type)
|
|
|
|
|
+ ];
|
|
|
|
|
+ },
|
|
|
|
|
+ syncLegacyCouponFields() {
|
|
|
|
|
+ const finishList = this.form.finishCoupons || [];
|
|
|
|
|
+ if (finishList.length) {
|
|
|
|
|
+ this.form.finishCouponId = finishList[0].couponId;
|
|
|
|
|
+ this.form.finishCouponCount = finishList[0].count || 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.form.finishCouponId = null;
|
|
|
|
|
+ this.form.finishCouponCount = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ normalizeCouponItems(list, fallbackId, fallbackCount) {
|
|
|
|
|
+ let items = [];
|
|
|
|
|
+ if (Array.isArray(list) && list.length) {
|
|
|
|
|
+ items = list
|
|
|
|
|
+ .map(item => ({
|
|
|
|
|
+ couponId: item.couponId != null ? item.couponId : item.id,
|
|
|
|
|
+ count: item.count != null && item.count > 0 ? item.count : (item.couponCount > 0 ? item.couponCount : 1)
|
|
|
|
|
+ }))
|
|
|
|
|
+ .filter(item => item.couponId != null);
|
|
|
|
|
+ } else if (fallbackId != null && fallbackId !== '') {
|
|
|
|
|
+ items = [{
|
|
|
|
|
+ couponId: fallbackId,
|
|
|
|
|
+ count: fallbackCount && fallbackCount > 0 ? fallbackCount : 1
|
|
|
|
|
+ }];
|
|
|
|
|
+ }
|
|
|
|
|
+ const map = new Map();
|
|
|
|
|
+ items.forEach(item => {
|
|
|
|
|
+ map.set(item.couponId, { couponId: item.couponId, count: item.count || 1 });
|
|
|
|
|
+ });
|
|
|
|
|
+ return Array.from(map.values());
|
|
|
|
|
+ },
|
|
|
|
|
+ onFinishCouponIdsChange(ids) {
|
|
|
|
|
+ const prevMap = {};
|
|
|
|
|
+ (this.form.finishCoupons || []).forEach(item => {
|
|
|
|
|
+ prevMap[item.couponId] = item.count || 1;
|
|
|
|
|
+ });
|
|
|
|
|
+ this.form.finishCoupons = (ids || []).map(id => ({
|
|
|
|
|
+ couponId: id,
|
|
|
|
|
+ count: prevMap[id] || 1
|
|
|
|
|
+ }));
|
|
|
|
|
+ this.syncLegacyCouponFields();
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.$refs.completionForm && this.$refs.completionForm.validateField('finishCoupons');
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ removeFinishCoupon(idx) {
|
|
|
|
|
+ const list = [...(this.form.finishCoupons || [])];
|
|
|
|
|
+ list.splice(idx, 1);
|
|
|
|
|
+ this.form.finishCoupons = list;
|
|
|
|
|
+ this.finishCouponSelectedIds = list.map(item => item.couponId);
|
|
|
|
|
+ this.syncLegacyCouponFields();
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleCompletionType(val) {
|
|
|
|
|
+ const list = (this.form.completionTypes || []).map(String);
|
|
|
|
|
+ const idx = list.indexOf(String(val));
|
|
|
|
|
+ if (idx >= 0) {
|
|
|
|
|
+ list.splice(idx, 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ list.push(String(val));
|
|
|
|
|
+ }
|
|
|
|
|
+ this.form.completionTypes = list;
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.$refs.completionForm && this.$refs.completionForm.validateField('completionTypes');
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ hasCompletionType(type) {
|
|
|
|
|
+ return Array.isArray(this.form.completionTypes)
|
|
|
|
|
+ && this.form.completionTypes.map(String).includes(String(type));
|
|
|
|
|
+ },
|
|
|
|
|
+ hasValidWatchConfig(config) {
|
|
|
|
|
+ const watchDuration = config && config.watchDuration;
|
|
|
|
|
+ const actions = config && config.actions;
|
|
|
|
|
+ const hasDuration = watchDuration !== null && watchDuration !== undefined
|
|
|
|
|
+ && String(watchDuration).trim() !== ''
|
|
|
|
|
+ && Number(watchDuration) > 0;
|
|
|
|
|
+ const hasActions = Array.isArray(actions) && actions.length > 0;
|
|
|
|
|
+ return hasDuration && hasActions;
|
|
|
|
|
+ },
|
|
|
|
|
+ applyParsedConfig(parsedData) {
|
|
|
|
|
+ const data = parsedData || {};
|
|
|
|
|
+ this.existingConfig = Object.assign({}, data);
|
|
|
|
|
+
|
|
|
|
|
+ let completionTypes = data.completionTypes;
|
|
|
|
|
+ if (String(data.participateCondition) === '3') {
|
|
|
|
|
+ if (!completionTypes || !completionTypes.length) {
|
|
|
|
|
+ completionTypes = ['3'];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (String(data.participateCondition) === '2') {
|
|
|
|
|
+ if (!completionTypes || !completionTypes.length) {
|
|
|
|
|
+ completionTypes = ['2'];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!Array.isArray(completionTypes)) {
|
|
|
|
|
+ completionTypes = [];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ completionTypes = completionTypes.map(String);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const finishCoupons = this.normalizeCouponItems(
|
|
|
|
|
+ data.finishCoupons,
|
|
|
|
|
+ data.finishCouponId,
|
|
|
|
|
+ data.finishCouponCount
|
|
|
|
|
+ );
|
|
|
|
|
+ const pointsConfig = Array.isArray(data.pointsConfig) && data.pointsConfig.length
|
|
|
|
|
+ ? data.pointsConfig
|
|
|
|
|
+ : [...DEFAULT_POINTS_CONFIG];
|
|
|
|
|
+
|
|
|
|
|
+ const completionEnabled = !!data.enabled && completionTypes.length > 0;
|
|
|
|
|
+
|
|
|
|
|
+ this.form = {
|
|
|
|
|
+ id: data.id != null ? data.id : null,
|
|
|
|
|
+ liveId: this.liveId,
|
|
|
|
|
+ completionEnabled,
|
|
|
|
|
+ completionRate: data.completionRate != null ? data.completionRate : 90,
|
|
|
|
|
+ pointsConfig,
|
|
|
|
|
+ completionTypes: completionTypes.length ? completionTypes : ['2'],
|
|
|
|
|
+ finishCouponId: null,
|
|
|
|
|
+ finishCouponCount: 1,
|
|
|
|
|
+ finishCoupons,
|
|
|
|
|
+ finishQuestionIds: data.finishQuestionIds || ''
|
|
|
|
|
+ };
|
|
|
|
|
+ this.syncLegacyCouponFields();
|
|
|
|
|
+ this.finishCouponSelectedIds = (this.form.finishCoupons || []).map(item => item.couponId);
|
|
|
|
|
+ this.loadSelectedQuestions();
|
|
|
|
|
+ },
|
|
|
|
|
+ getLiveConfig() {
|
|
|
|
|
+ this.loadCouponList().then(() => {
|
|
|
|
|
+ getConfig(this.liveId).then(response => {
|
|
|
|
|
+ if (response.code === 200 && response.data != null && response.data.length > 0) {
|
|
|
|
|
+ const parsedData = JSON.parse(response.data);
|
|
|
|
|
+ this.applyParsedConfig(parsedData);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.loading = false;
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.loading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ loadCouponList() {
|
|
|
|
|
+ return listLiveCouponOn({ liveId: this.liveId }).then(response => {
|
|
|
|
|
+ const list = response.rows || [];
|
|
|
|
|
+ this.couponListData = list;
|
|
|
|
|
+ this.couponSelectOptions = list.map(item => ({
|
|
|
|
|
+ value: item.couponId,
|
|
|
|
|
+ label: item.title
|
|
|
|
|
+ }));
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ buildMergedPayload() {
|
|
|
|
|
+ this.syncLegacyCouponFields();
|
|
|
|
|
+ this.syncFinishQuestionIds();
|
|
|
|
|
+ const base = Object.assign({}, this.existingConfig);
|
|
|
|
|
+ const completionEnabled = !!this.form.completionEnabled;
|
|
|
|
|
+ const completionTypes = completionEnabled
|
|
|
|
|
+ ? (this.form.completionTypes || []).map(String)
|
|
|
|
|
+ : [];
|
|
|
|
|
+ const finishCoupons = completionEnabled && this.hasCompletionType('3')
|
|
|
|
|
+ ? (this.form.finishCoupons || []).map(item => ({
|
|
|
|
|
+ couponId: item.couponId,
|
|
|
|
|
+ count: item.count && item.count > 0 ? item.count : 1
|
|
|
|
|
+ }))
|
|
|
|
|
+ : [];
|
|
|
|
|
+
|
|
|
|
|
+ base.id = this.form.id;
|
|
|
|
|
+ base.liveId = this.liveId;
|
|
|
|
|
+ base.completionRate = this.form.completionRate;
|
|
|
|
|
+ base.pointsConfig = this.form.pointsConfig;
|
|
|
|
|
+ base.completionTypes = completionTypes;
|
|
|
|
|
+ base.finishCoupons = finishCoupons;
|
|
|
|
|
+ base.finishCouponId = finishCoupons.length ? finishCoupons[0].couponId : null;
|
|
|
|
|
+ base.finishCouponCount = finishCoupons.length ? (finishCoupons[0].count || 1) : 1;
|
|
|
|
|
+ base.finishQuestionIds = completionEnabled && this.hasCompletionType('3')
|
|
|
|
|
+ ? this.form.finishQuestionIds
|
|
|
|
|
+ : '';
|
|
|
|
|
+
|
|
|
|
|
+ // 与观看奖励共用 enabled:任一开启则保持 true
|
|
|
|
|
+ const watchOn = this.hasValidWatchConfig(base);
|
|
|
|
|
+ base.enabled = completionEnabled || watchOn;
|
|
|
|
|
+
|
|
|
|
|
+ if (completionEnabled) {
|
|
|
|
|
+ if (!watchOn) {
|
|
|
|
|
+ base.participateCondition = '2';
|
|
|
|
|
+ } else if (base.participateCondition == null || base.participateCondition === '') {
|
|
|
|
|
+ base.participateCondition = '1';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ base.participateCondition = String(base.participateCondition);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (watchOn) {
|
|
|
|
|
+ base.participateCondition = '1';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return base;
|
|
|
|
|
+ },
|
|
|
|
|
+ fetchLatestAndMerge() {
|
|
|
|
|
+ return getConfig(this.liveId).then(response => {
|
|
|
|
|
+ if (response.code === 200 && response.data != null && response.data.length > 0) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.existingConfig = Object.assign({}, JSON.parse(response.data));
|
|
|
|
|
+ if (this.existingConfig.id != null) {
|
|
|
|
|
+ this.form.id = this.existingConfig.id;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // keep existingConfig
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.buildMergedPayload();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ persistPayload(payload, successMsg) {
|
|
|
|
|
+ if (payload.id == null) {
|
|
|
|
|
+ return addConfig(payload, this.liveId).then(res => {
|
|
|
|
|
+ if (res.code == 200) {
|
|
|
|
|
+ this.msgSuccess(successMsg);
|
|
|
|
|
+ if (res.data && res.data.id) {
|
|
|
|
|
+ this.form.id = res.data.id;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.existingConfig = Object.assign({}, payload, { id: this.form.id });
|
|
|
|
|
+ }
|
|
|
|
|
+ return res;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return updateConfig(payload, this.liveId).then(response => {
|
|
|
|
|
+ if (response.code == 200) {
|
|
|
|
|
+ this.msgSuccess(successMsg);
|
|
|
|
|
+ this.existingConfig = Object.assign({}, payload);
|
|
|
|
|
+ }
|
|
|
|
|
+ return response;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ autoSaveEnabled() {
|
|
|
|
|
+ this.fetchLatestAndMerge().then(payload => {
|
|
|
|
|
+ this.persistPayload(payload, '已关闭完课奖励').catch(() => {
|
|
|
|
|
+ this.form.completionEnabled = true;
|
|
|
|
|
+ });
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.form.completionEnabled = true;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ saveCompletionReward() {
|
|
|
|
|
+ this.syncFinishQuestionIds();
|
|
|
|
|
+ this.$refs['completionForm'].validate(valid => {
|
|
|
|
|
+ if (!valid) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.fetchLatestAndMerge().then(payload => {
|
|
|
|
|
+ this.persistPayload(payload, '修改成功');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ handleToQuestionBank() {
|
|
|
|
|
+ this.$router.push('/live/liveQuestionBank');
|
|
|
|
|
+ },
|
|
|
|
|
+ openQuestionDialog() {
|
|
|
|
|
+ this.questionDialogVisible = true;
|
|
|
|
|
+ this.tempSelectedQuestions = [...this.selectedQuestionList];
|
|
|
|
|
+ this.loadQuestionOptions();
|
|
|
|
|
+ },
|
|
|
|
|
+ loadQuestionOptions() {
|
|
|
|
|
+ this.questionLoading = true;
|
|
|
|
|
+ listLiveQuestionBank(this.questionQueryParams).then(response => {
|
|
|
|
|
+ this.questionOptionList = response.rows || [];
|
|
|
|
|
+ this.questionOptionTotal = response.total || 0;
|
|
|
|
|
+ this.questionLoading = false;
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ if (this.$refs.questionTable) {
|
|
|
|
|
+ this.questionOptionList.forEach(row => {
|
|
|
|
|
+ const selected = this.tempSelectedQuestions.some(item => item.id === row.id);
|
|
|
|
|
+ this.$refs.questionTable.toggleRowSelection(row, selected);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ this.questionLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ handleQuestionSearch() {
|
|
|
|
|
+ this.questionQueryParams.pageNum = 1;
|
|
|
|
|
+ this.questionQueryParams.title = this.questionSearchTitle;
|
|
|
|
|
+ this.loadQuestionOptions();
|
|
|
|
|
+ },
|
|
|
|
|
+ handleQuestionSelectionChange(selection) {
|
|
|
|
|
+ const currentPageIds = this.questionOptionList.map(item => item.id);
|
|
|
|
|
+ const otherPageSelected = this.tempSelectedQuestions.filter(item => !currentPageIds.includes(item.id));
|
|
|
|
|
+ this.tempSelectedQuestions = [...otherPageSelected, ...selection];
|
|
|
|
|
+ },
|
|
|
|
|
+ handleQuestionRowClick(row, column) {
|
|
|
|
|
+ if (column.type === 'selection') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const table = this.$refs.questionTable;
|
|
|
|
|
+ if (!table) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const isSelected = this.tempSelectedQuestions.some(item => item.id === row.id);
|
|
|
|
|
+ table.toggleRowSelection(row, !isSelected);
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmQuestionSelection() {
|
|
|
|
|
+ if (this.tempSelectedQuestions.length === 0) {
|
|
|
|
|
+ this.$message.warning('请至少选择一题');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.selectedQuestionList = [...this.tempSelectedQuestions];
|
|
|
|
|
+ this.syncFinishQuestionIds();
|
|
|
|
|
+ this.questionDialogVisible = false;
|
|
|
|
|
+ },
|
|
|
|
|
+ removeSelectedQuestion(row) {
|
|
|
|
|
+ this.selectedQuestionList = this.selectedQuestionList.filter(item => item.id !== row.id);
|
|
|
|
|
+ this.syncFinishQuestionIds();
|
|
|
|
|
+ },
|
|
|
|
|
+ syncFinishQuestionIds() {
|
|
|
|
|
+ this.form.finishQuestionIds = this.selectedQuestionList.map(item => item.id).join(',');
|
|
|
|
|
+ },
|
|
|
|
|
+ loadSelectedQuestions() {
|
|
|
|
|
+ if (!this.form.finishQuestionIds) {
|
|
|
|
|
+ this.selectedQuestionList = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const ids = this.form.finishQuestionIds.split(',').filter(Boolean);
|
|
|
|
|
+ if (ids.length === 0) {
|
|
|
|
|
+ this.selectedQuestionList = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Promise.all(ids.map(id => getLiveQuestionBank(id))).then(results => {
|
|
|
|
|
+ this.selectedQuestionList = results
|
|
|
|
|
+ .filter(res => res.code === 200 && res.data)
|
|
|
|
|
+ .map(res => res.data);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.watch-reward-page {
|
|
|
|
|
+ min-height: 100%;
|
|
|
|
|
+ padding: 4px 4px 40px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-banner {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ margin-bottom: 14px;
|
|
|
|
|
+ padding: 16px 18px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ background: linear-gradient(135deg, #f0faf3 0%, #f5f9ff 55%, #eef7f3 100%);
|
|
|
|
|
+ border: 1px solid #e4f0e8;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.banner-icon {
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background: linear-gradient(135deg, #1890ff, #36a3ff);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tone-green-banner {
|
|
|
|
|
+ background: linear-gradient(135deg, #52c41a, #73d13d);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.banner-title {
|
|
|
|
|
+ font-size: 17px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1f2d3d;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.banner-desc {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #6b7c93;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reward-switch-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 14px;
|
|
|
|
|
+ padding: 16px 18px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border: 1px solid #eef2f7;
|
|
|
|
|
+ box-shadow: 0 1px 2px rgba(31, 45, 61, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.switch-title {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.switch-hint {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.disabled-hint {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ padding: 28px 18px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border: 1px dashed #d9e2ec;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.config-flow {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flow-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border: 1px solid #eef2f7;
|
|
|
|
|
+ box-shadow: 0 1px 2px rgba(31, 45, 61, 0.04);
|
|
|
|
|
+ padding: 18px 20px 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flow-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.step-no {
|
|
|
|
|
+ width: 26px;
|
|
|
|
|
+ height: 26px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #1890ff;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-top: 1px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flow-title {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1f2d3d;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.flow-sub {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ margin-top: 2px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reward-type-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reward-type-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 14px 16px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border: 1px solid #e8eef5;
|
|
|
|
|
+ background: #fafbfc;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.18s ease;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ min-height: 118px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reward-type-card:hover {
|
|
|
|
|
+ border-color: #cfe3ff;
|
|
|
|
|
+ background: #f7fbff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.reward-type-card.active {
|
|
|
|
|
+ border-color: #1890ff;
|
|
|
|
|
+ background: linear-gradient(135deg, #f0f7ff 0%, #f7fbff 100%);
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rtype-icon {
|
|
|
|
|
+ width: 36px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tone-green { background: linear-gradient(135deg, #52c41a, #73d13d); }
|
|
|
|
|
+.tone-orange { background: linear-gradient(135deg, #fa8c16, #ffa940); }
|
|
|
|
|
+
|
|
|
|
|
+.rtype-name {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rtype-desc {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rtype-check {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cond-fields {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ padding-top: 8px;
|
|
|
|
|
+ border-top: 1px dashed #eef2f7;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-form-item {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.type-form-item >>> .el-form-item__content {
|
|
|
|
|
+ margin-left: 0 !important;
|
|
|
|
|
+ line-height: normal;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-panel {
|
|
|
|
|
+ margin: 8px 0 16px;
|
|
|
|
|
+ padding: 14px 16px 4px;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border: 1px solid #eef2f7;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #4a5d73;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-head i {
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.field-sm { width: 160px; }
|
|
|
|
|
+.field-md { width: 100%; max-width: 280px; }
|
|
|
|
|
+.field-full { width: 100%; }
|
|
|
|
|
+
|
|
|
|
|
+.field-tip {
|
|
|
|
|
+ margin-left: 10px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.field-lg {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: 420px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.coupon-multi-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin: 0 0 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.coupon-multi-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ padding: 12px 14px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ border: 1px solid #e8eef5;
|
|
|
|
|
+ background: #fafbfc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cmi-main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cmi-title {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cmi-meta {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cmi-count {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cmi-count-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 8px 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-day {
|
|
|
|
|
+ width: 52px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-input {
|
|
|
|
|
+ width: 110px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-unit {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.question-item {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.question-block {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.question-toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.question-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.empty-hint {
|
|
|
|
|
+ margin-top: 4px;
|
|
|
|
|
+ padding: 12px 14px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px dashed #d9e2ec;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.danger-text {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-actions {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 8px 0 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-actions .el-button {
|
|
|
|
|
+ min-width: 140px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-search {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-selected-count {
|
|
|
|
|
+ float: left;
|
|
|
|
|
+ line-height: 32px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dialog-selected-count em {
|
|
|
|
|
+ font-style: normal;
|
|
|
|
|
+ color: #1890ff;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 900px) {
|
|
|
|
|
+ .reward-type-grid {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|
|
|
|
|
+<style>
|
|
|
|
|
+.question-select-dialog .el-dialog__body {
|
|
|
|
|
+ max-height: 65vh;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|