index.vue 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981
  1. <template>
  2. <div class="app-container">
  3. <el-container>
  4. <!-- 左侧区域 -->
  5. <el-aside width="360px" class="left-aside">
  6. <!-- 顶部区域 -->
  7. <div class="left-header">
  8. <div class="left-header-top">
  9. <el-button type="primary" class="search-btn" @click="handleLeftQuery" v-hasPermi="['course:trainingCamp:list']">搜索</el-button>
  10. <el-button type="primary" style="width: 50%" icon="el-icon-plus" @click="handleAddTrainingCamp" v-hasPermi="['course:trainingCamp:add']">新建训练营</el-button>
  11. </div>
  12. <div class="search-input-wrapper">
  13. <el-input
  14. v-model="leftQueryParams.trainingCampName"
  15. placeholder="请输入训练营名称"
  16. prefix-icon="el-icon-search"
  17. clearable
  18. size="small"
  19. @keyup.enter.native="handleLeftQuery"
  20. />
  21. </div>
  22. <div class="sort-wrapper">
  23. <span class="sort-label">排序方式</span>
  24. <el-select v-model="leftQueryParams.scs"
  25. placeholder="按序号倒序"
  26. size="small"
  27. class="sort-select"
  28. @change="handleSortChange"
  29. >
  30. <el-option label="按序号倒序" value="order_number(desc),training_camp_id(desc)" />
  31. <el-option label="按序号顺序" value="order_number(asc),training_camp_id(desc)" />
  32. </el-select>
  33. </div>
  34. </div>
  35. <!-- 训练营列表 -->
  36. <div class="camp-list" ref="campList" @scroll="handleScroll" v-loading="leftLoading">
  37. <div
  38. v-for="(item, index) in campList"
  39. :key="index"
  40. class="camp-item"
  41. :class="{ 'active': activeCampIndex === index }"
  42. @click="selectCamp(index)"
  43. >
  44. <div class="camp-content">
  45. <div class="camp-title">
  46. <i class="el-icon-s-flag camp-icon"></i>
  47. {{ item.trainingCampName }}
  48. </div>
  49. <div class="camp-info">
  50. <span>序号:{{ item.orderNumber }}</span>
  51. <span>最新营期开课:{{ item.recentDate || '-' }}</span>
  52. </div>
  53. <div class="camp-stats">
  54. <span class="stat-item">
  55. <i class="el-icon-s-data"></i>
  56. 营期数:{{ item.periodCount || 0 }}
  57. </span>
  58. <span class="stat-item">
  59. <i class="el-icon-user"></i>
  60. 会员总数:{{ item.vipCount || 0 }}
  61. </span>
  62. </div>
  63. </div>
  64. <div class="camp-actions">
  65. <el-button type="text" class="action-btn delete-btn" @click.stop="handleDeleteCamp(item)" v-hasPermi="['course:trainingCamp:remove']">删除</el-button>
  66. <!-- <el-button type="text" class="action-btn copy-btn" @click.stop="handleCopyCamp(item)" v-hasPermi="['course:trainingCamp:copy']">复制</el-button>-->
  67. <el-button type="text" class="action-btn copy-btn" @click.stop="handleEditCamp(item)" v-hasPermi="['course:trainingCamp:edit']">编辑</el-button>
  68. </div>
  69. </div>
  70. <!-- 底部加载更多提示 -->
  71. <div v-if="loadingMore" class="loading-more">
  72. <i class="el-icon-loading"></i>
  73. <span>加载中...</span>
  74. </div>
  75. <!-- 所有数据加载完毕提示 -->
  76. <div v-if="campList.length > 0 && !leftQueryParams.hasNextPage && !loadingMore" class="no-more-data">
  77. <span>—— 已加载全部训练营 ——</span>
  78. </div>
  79. </div>
  80. </el-aside>
  81. <!-- 右侧区域 -->
  82. <el-main>
  83. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  84. <el-form-item label="营期名称" prop="periodName">
  85. <el-input
  86. v-model="queryParams.periodName"
  87. placeholder="请输入营期名称"
  88. clearable
  89. size="small"
  90. @keyup.enter.native="handleQuery"
  91. />
  92. </el-form-item>
  93. <el-form-item label="公司" prop="companyIdList">
  94. <el-select v-model="queryParams.companyIdList" placeholder="请选择公司" clearable size="small" multiple>
  95. <el-option
  96. v-for="item in companyOptions"
  97. :key="item.companyId"
  98. :label="item.companyName"
  99. :value="item.companyId"
  100. />
  101. </el-select>
  102. </el-form-item>
  103. <el-form-item label="开营日期开始" prop="periodStartingTime" label-width="120px">
  104. <el-date-picker clearable size="small" style="width: 200px"
  105. v-model="queryParams.periodStartingTime"
  106. type="date"
  107. value-format="yyyy-MM-dd"
  108. placeholder="请选择开营日期开始时间">
  109. </el-date-picker>
  110. </el-form-item>
  111. <el-form-item label="开营日期结束" prop="periodEndTime" label-width="120px">
  112. <el-date-picker clearable size="small" style="width: 300px"
  113. v-model="queryParams.periodEndTime"
  114. type="date"
  115. value-format="yyyy-MM-dd"
  116. placeholder="请选择开营日期结束时间">
  117. </el-date-picker>
  118. </el-form-item>
  119. <el-form-item>
  120. <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery" v-hasPermi="['course:period:list']">搜索</el-button>
  121. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  122. </el-form-item>
  123. </el-form>
  124. <el-row :gutter="10" class="mb8">
  125. <el-col :span="1.5">
  126. <el-button
  127. type="primary"
  128. plain
  129. icon="el-icon-plus"
  130. size="mini"
  131. @click="handleAdd"
  132. v-hasPermi="['course:period:add']"
  133. >新增</el-button>
  134. </el-col>
  135. <el-col :span="1.5">
  136. <el-button
  137. type="warning"
  138. plain
  139. icon="el-icon-download"
  140. size="mini"
  141. @click="handleExport"
  142. v-hasPermi="['course:period:export']"
  143. >导出</el-button>
  144. </el-col>
  145. <el-col :span="1.5">
  146. <el-button
  147. type="primary"
  148. plain
  149. icon="el-icon-edit"
  150. size="mini"
  151. @click="handleBatchSetRedPacket"
  152. v-hasPermi="['course:period:setRedPacket']"
  153. :disabled="batchSetRedPacketDisabled"
  154. >批量设置红包</el-button>
  155. </el-col>
  156. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  157. </el-row>
  158. <el-table v-loading="loading" :data="periodList" @selection-change="handleSelectionChange" border>
  159. <el-table-column type="selection" width="55" align="center" />
  160. <el-table-column label="营期名称" align="center" prop="periodName" />
  161. <el-table-column label="公司名称" align="center" prop="companyName" />
  162. <el-table-column label="营期状态" align="center" prop="periodStatus" width="100" :formatter="periodStatusFormatter" />
  163. <el-table-column label="营期线" align="center" prop="periodLine" width="180" />
  164. <el-table-column label="开营开始时间" align="center" prop="periodStartingTime" width="180" />
  165. <el-table-column label="开营结束时间" align="center" prop="periodEndTime" width="180" />
  166. <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
  167. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  168. <template slot-scope="scope">
  169. <el-button
  170. size="mini"
  171. type="text"
  172. icon="el-icon-edit"
  173. @click="handleUpdate(scope.row)"
  174. v-hasPermi="['course:period:query']"
  175. >修改</el-button>
  176. <!-- <el-button-->
  177. <!-- size="mini"-->
  178. <!-- type="text"-->
  179. <!-- icon="el-icon-edit"-->
  180. <!-- @click="handleCourse(scope.row)"-->
  181. <!-- v-hasPermi="['course:period:edit']"-->
  182. <!-- >课程管理</el-button>-->
  183. <!-- <el-button-->
  184. <!-- size="mini"-->
  185. <!-- type="text"-->
  186. <!-- icon="el-icon-money"-->
  187. <!-- @click="setRedPacket(scope.row)"-->
  188. <!-- >设置红包</el-button>-->
  189. <el-button
  190. size="mini"
  191. type="text"
  192. icon="el-icon-setting"
  193. @click="handlePeriodSettings(scope.row)"
  194. >营期相关设置</el-button>
  195. <el-button
  196. size="mini"
  197. type="text"
  198. icon="el-icon-circle-close"
  199. @click="handleClosePeriod(scope.row)"
  200. v-hasPermi="['course:period:close']"
  201. >结束营期</el-button>
  202. <el-button
  203. size="mini"
  204. type="text"
  205. icon="el-icon-delete"
  206. @click="handleDelete(scope.row)"
  207. v-hasPermi="['course:period:remove']"
  208. >删除</el-button>
  209. </template>
  210. </el-table-column>
  211. </el-table>
  212. <pagination
  213. v-show="total>0"
  214. :total="total"
  215. :page.sync="queryParams.pageNum"
  216. :limit.sync="queryParams.pageSize"
  217. @pagination="getList"
  218. />
  219. </el-main>
  220. </el-container>
  221. <!-- 添加或修改会员营期对话框-->
  222. <el-drawer :title="title" :visible.sync="open" size="700px" append-to-body>
  223. <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  224. <el-form-item label="营期名称" prop="periodName">
  225. <el-input v-model="form.periodName" placeholder="请输入营期名称" />
  226. </el-form-item>
  227. <el-form-item label="公司" prop="companyId">
  228. <el-select v-model="form.companyId" placeholder="请选择公司" multiple>
  229. <el-option
  230. v-for="item in companyOptions"
  231. :key="item.companyId"
  232. :label="item.companyName"
  233. :value="item.companyId"
  234. />
  235. </el-select>
  236. </el-form-item>
  237. <el-form-item label="课程风格" prop="courseStyle">
  238. <image-upload v-model="form.courseStyle" :limit="1" />
  239. </el-form-item>
  240. <el-form-item label="直播间风格" prop="liveRoomStyle">
  241. <image-upload v-model="form.liveRoomStyle" :limit="1" />
  242. </el-form-item>
  243. <el-form-item label="课程LOGO" prop="courseLogo">
  244. <image-upload v-model="form.courseLogo" :limit="1" />
  245. </el-form-item>
  246. <!-- <el-form-item label="红包发放方式" prop="redPacketGrantMethod">-->
  247. <!-- <el-radio-group v-model="form.redPacketGrantMethod">-->
  248. <!-- <el-radio :label="1" >按课程</el-radio>-->
  249. <!-- <el-radio :label="2" >按营期</el-radio>-->
  250. <!-- </el-radio-group>-->
  251. <!-- </el-form-item>-->
  252. <el-form-item label="营期类型" prop="periodType">
  253. <el-radio-group v-model="form.periodType">
  254. <el-radio :label="1" >多课程</el-radio>
  255. <el-radio :label="2" >单课程</el-radio>
  256. </el-radio-group>
  257. </el-form-item>
  258. <el-form-item label="销售提前查看天数" prop="maxViewNum">
  259. <el-input-number :min="0" v-model="form.maxViewNum" style="width: 200px" />
  260. </el-form-item>
  261. <el-form-item label="开营日期" prop="periodStartingTime">
  262. <el-date-picker
  263. :style="{display: form.periodType == 1 ? '' : 'none !important'}"
  264. v-model="form.dateRange"
  265. @change="timeChange(1)"
  266. type="daterange"
  267. range-separator="至"
  268. start-placeholder="开始日期"
  269. end-placeholder="结束日期"
  270. value-format="yyyy-MM-dd"
  271. :picker-options="{disabledDate}"
  272. :clearable="false"
  273. >
  274. </el-date-picker>
  275. <el-date-picker
  276. :disabled = "isDisabledDateRange"
  277. :style="{display: form.periodType == 2 ? '' : 'none !important'}"
  278. @change="timeChange(2)"
  279. v-model="form.date"
  280. type="date"
  281. value-format="yyyy-MM-dd"
  282. placeholder="选择日期"
  283. :picker-options="{disabledDate}"
  284. :clearable="false"
  285. >
  286. </el-date-picker>
  287. </el-form-item>
  288. <el-form-item prop="periodLine">
  289. <span slot="label">
  290. 营期线
  291. <el-tooltip content="营期首次播放课程的日期" placement="top">
  292. <i class="el-icon-question" style="margin-left: 5px; color: #909399; cursor: help;"></i>
  293. </el-tooltip>
  294. </span>
  295. <el-date-picker
  296. v-model="form.periodLine"
  297. type="date"
  298. placeholder="请选择营期线"
  299. value-format="yyyy-MM-dd"
  300. :clearable="false">
  301. </el-date-picker>
  302. </el-form-item>
  303. </el-form>
  304. <div class="drawer-footer">
  305. <el-button type="primary" @click="submitForm">确 定</el-button>
  306. <el-button @click="cancel">取 消</el-button>
  307. </div>
  308. </el-drawer>
  309. <!-- 添加训练营对话框 -->
  310. <el-dialog :title="campForm.trainingCampId ? '修改训练营' : '新建训练营'" :visible.sync="campDialogVisible" width="500px" append-to-body>
  311. <el-form ref="campForm" :model="campForm" :rules="campRules" label-width="100px">
  312. <el-form-item label="训练营名称" prop="trainingCampName">
  313. <el-input v-model="campForm.trainingCampName" placeholder="请输入训练营名称" />
  314. </el-form-item>
  315. </el-form>
  316. <div slot="footer" class="dialog-footer">
  317. <el-button type="primary" @click="submitCampForm">确 定</el-button>
  318. <el-button @click="cancelCampForm">取 消</el-button>
  319. </div>
  320. </el-dialog>
  321. <!-- &lt;!&ndash; 添加或修改会员营期对话框&ndash;&gt;-->
  322. <!-- <el-dialog title="课程管理" :visible.sync="course.open" width="75%" top="10px" append-to-body style="padding-bottom: 10px">-->
  323. <!-- <el-row :gutter="10" class="mb8">-->
  324. <!-- <el-col :span="1.5">-->
  325. <!-- <el-button-->
  326. <!-- v-if="(getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total) > 0"-->
  327. <!-- type="primary"-->
  328. <!-- icon="el-icon-plus"-->
  329. <!-- size="mini"-->
  330. <!-- @click="handleAddCourse"-->
  331. <!-- v-hasPermi="['course:period:add']"-->
  332. <!-- >添加课程</el-button>-->
  333. <!-- </el-col>-->
  334. <!-- </el-row>-->
  335. <!-- <el-table v-loading="course.loading" :data="course.list">-->
  336. <!-- <el-table-column label="课程" align="center" prop="courseName" width="180" />-->
  337. <!-- <el-table-column label="小节" align="center" prop="videoName" />-->
  338. <!-- <el-table-column label="营期时间" align="center" prop="dayDate" width="150" />-->
  339. <!-- <el-table-column label="创建时间" align="center" prop="createTime" width="150" />-->
  340. <!-- </el-table>-->
  341. <!-- <div slot="footer" class="dialog-footer">-->
  342. <!--&lt;!&ndash; <el-button type="primary" @click="saveCourseData">保存</el-button>&ndash;&gt;-->
  343. <!-- </div>-->
  344. <!-- </el-dialog>-->
  345. <!-- 添加课程对话框-->
  346. <el-dialog title="添加课程" :visible.sync="course.addOpen" width="500px" append-to-body>
  347. <el-form ref="courseAddForm" :model="course.form" :rules="courseAddRules" label-width="100px">
  348. <el-form-item label="课程" prop="courseId">
  349. <el-select filterable v-model="course.form.courseId" placeholder="请选择课程" clearable size="small" @change="courseChange(course.form.courseId)" style="width: 100%" :value-key="'dictValue'">
  350. <el-option
  351. v-for="dict in courseList"
  352. :key="dict.dictValue"
  353. :label="dict.dictLabel"
  354. :value="parseInt(dict.dictValue)"
  355. />
  356. </el-select>
  357. </el-form-item>
  358. <el-form-item label="小节" prop="videoIds">
  359. <el-select filterable v-model="course.form.videoIds" placeholder="请选择小节" :multiple-limit="getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total" multiple clearable size="small" style="width: 100%" :value-key="'dictValue'">
  360. <el-option
  361. v-for="dict in videoList"
  362. :key="dict.dictValue"
  363. :label="dict.dictLabel"
  364. :value="parseInt(dict.dictValue)"
  365. />
  366. </el-select>
  367. </el-form-item>
  368. <el-form-item label="看课时间" prop="timeRange">
  369. <el-time-picker
  370. is-range
  371. v-model="course.form.timeRange"
  372. :picker-options="{
  373. selectableRange: '00:00:00 - 23:59:59'
  374. }"
  375. range-separator="至"
  376. start-placeholder="开始时间"
  377. value-format="HH:mm:ss"
  378. end-placeholder="结束时间">
  379. </el-time-picker>
  380. </el-form-item>
  381. <el-form-item label="领取红包时间" prop="lastJoinTime">
  382. <el-time-picker
  383. v-model="course.form.joinTime"
  384. value-format="HH:mm:ss"
  385. placeholder="选择时间范围">
  386. </el-time-picker>
  387. <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
  388. </el-form-item>
  389. </el-form>
  390. <div slot="footer" class="dialog-footer">
  391. <el-button type="primary" @click="submitCourseForm">确 定</el-button>
  392. <el-button @click="closeAddCourse">取 消</el-button>
  393. </div>
  394. </el-dialog>
  395. <el-dialog title="修改看课时间" :visible.sync="updateCourse.open" width="600px" append-to-body>
  396. <el-form ref="courseUpdateForm" :model="updateCourse.form" :rules="courseUpdateRules" label-width="100px">
  397. <el-form-item label="看课时间" prop="timeRange" label-width="110px">
  398. <el-date-picker
  399. v-model="updateCourse.form.timeRange"
  400. type="datetimerange"
  401. range-separator="至"
  402. start-placeholder="开始时间"
  403. end-placeholder="结束时间"
  404. value-format="yyyy-MM-dd HH:mm:ss"
  405. :default-time="['00:00:00', '23:59:59']"
  406. :picker-options="{
  407. shortcuts: [{
  408. text: '今天',
  409. onClick(picker) {
  410. const end = new Date();
  411. const start = new Date();
  412. picker.$emit('pick', [start, end]);
  413. }
  414. }]
  415. }"
  416. >
  417. </el-date-picker>
  418. </el-form-item>
  419. <el-form-item label="领取红包时间" prop="joinTime" label-width="110px">
  420. <el-date-picker
  421. v-model="updateCourse.form.joinTime"
  422. type="datetime"
  423. placeholder="选择时间"
  424. value-format="yyyy-MM-dd HH:mm:ss"
  425. :default-time="'23:59:59'"
  426. >
  427. </el-date-picker>
  428. <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
  429. </el-form-item>
  430. </el-form>
  431. <div slot="footer" class="dialog-footer">
  432. <el-button type="primary" @click="submitUpdateCourseForm">确 定</el-button>
  433. <el-button @click="closeUpdateCourse">取 消</el-button>
  434. </div>
  435. </el-dialog>
  436. <el-dialog title="修改营期时间" :visible.sync="updatePeriodDate.open" width="500px" append-to-body>
  437. <el-form ref="courseUpdateForm" :model="updatePeriodDate.form" label-width="100px">
  438. <el-form-item label="营期时间" prop="dayDate">
  439. <el-date-picker
  440. v-model="updatePeriodDate.form.dayDate"
  441. :selectableRange="updatePeriodDate.form.dayDate"
  442. value-format="yyyy-MM-dd"
  443. type="date"
  444. placeholder="选择日期">
  445. </el-date-picker>
  446. </el-form-item>
  447. </el-form>
  448. <div slot="footer" class="dialog-footer">
  449. <el-button type="primary" @click="updatePeriodDateSubmit">确 定</el-button>
  450. <el-button @click="updatePeriodDate.open = false">取 消</el-button>
  451. </div>
  452. </el-dialog>
  453. <!-- <red-packet-->
  454. <!-- :visible.sync="redPacketVisible"-->
  455. <!-- :periodId="currentRedPacketData.periodId"-->
  456. <!-- :videoId="currentRedPacketData.videoId"-->
  457. <!-- @success="handleRedPacketSuccess"-->
  458. <!-- />-->
  459. <!-- 营期相关设置抽屉 -->
  460. <el-drawer
  461. title="营期相关设置"
  462. :visible.sync="periodSettingsVisible"
  463. direction="rtl"
  464. size="74%"
  465. :destroy-on-close="true"
  466. append-to-body
  467. custom-class="period-settings-drawer"
  468. >
  469. <div class="drawer-content" style="margin-left: 25px">
  470. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  471. <el-tab-pane label="课程管理" name="course">
  472. <el-row :gutter="10" class="mb8">
  473. <el-col :span="1.5">
  474. <el-button
  475. v-if="(getDiff(periodSettingsData.periodStartingTime, periodSettingsData.periodEndTime) - course.total) > 0"
  476. type="primary"
  477. icon="el-icon-plus"
  478. size="mini"
  479. @click="handleAddCourse"
  480. v-hasPermi="['course:period:addCourse']"
  481. >添加课程</el-button>
  482. </el-col>
  483. <!-- <el-col :span="1.5">-->
  484. <!-- <el-button-->
  485. <!-- type="primary"-->
  486. <!-- size="mini"-->
  487. <!-- :disabled="updateCourse.ids.length <= 0"-->
  488. <!-- @click="handleUpdatePeriodDate"-->
  489. <!-- >修改营期时间</el-button>-->
  490. <!-- </el-col>-->
  491. <el-col :span="1.5">
  492. <el-button
  493. type="primary"
  494. size="mini"
  495. :disabled="updateCourse.ids.length <= 0"
  496. @click="handleUpdateCourse"
  497. v-hasPermi="['course:period:updateCourseTime']"
  498. >修改看课时间</el-button>
  499. </el-col>
  500. <el-col :span="1.5">
  501. <el-button
  502. type="warning"
  503. size="mini"
  504. icon="el-icon-delete"
  505. :disabled="updateCourse.ids.length <= 0"
  506. @click="handleDeleteCourse"
  507. v-hasPermi="['course:period:dayRemove']"
  508. >删除课程</el-button>
  509. </el-col>
  510. </el-row>
  511. <el-table ref="courseTable" v-loading="course.loading" :data="course.list" @selection-change="handleSelectionCourseChange" border>
  512. <el-table-column type="selection" width="55" align="center" />
  513. <el-table-column label="课程" align="center" prop="courseName" width="180" />
  514. <el-table-column label="小节" align="center" prop="videoName" />
  515. <el-table-column label="开课状态" align="center" prop="status" width="100" :formatter="courseStatusFormatter" />
  516. <el-table-column label="营期时间" align="center" prop="dayDate" width="100"/>
  517. <el-table-column label="开始时间" align="center" prop="startDateTime" width="100">
  518. <!-- <template slot-scope="scope">-->
  519. <!-- <el-tag>{{scope.row.startDateTime}}</el-tag>-->
  520. <!-- </template>-->
  521. </el-table-column>
  522. <el-table-column label="结束时间" align="center" prop="endDateTime" width="100">
  523. <!-- <template slot-scope="scope">-->
  524. <!-- <el-tag type="success">{{parseTime(scope.row.endDateTime, '{h}:{i}:{s}')}}</el-tag>-->
  525. <!-- </template>-->
  526. </el-table-column>
  527. <el-table-column label="领取红包时间" align="center" prop="lastJoinTime" width="160">
  528. <template slot-scope="scope">
  529. <el-tag type="danger">{{scope.row.lastJoinTime}}</el-tag>
  530. </template>
  531. </el-table-column>
  532. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  533. <template slot-scope="scope">
  534. <el-button
  535. size="mini"
  536. type="text"
  537. icon="el-icon-top"
  538. @click="handleTop(scope.row)"
  539. v-hasPermi="['course:period:courseMove']"
  540. >上移</el-button>
  541. <el-button
  542. size="mini"
  543. type="text"
  544. icon="el-icon-bottom"
  545. @click="handleBottom(scope.row)"
  546. v-hasPermi="['course:period:courseMove']"
  547. >下移</el-button>
  548. <el-button
  549. size="mini"
  550. type="text"
  551. icon="el-icon-edit"
  552. @click="handleUpdateDate(scope.row)"
  553. >修改营期时间</el-button>
  554. <el-button
  555. size="mini"
  556. type="text"
  557. icon="el-icon-delete"
  558. @click="handleDeleteCourse(scope.row)"
  559. v-hasPermi="['course:period:dayRemove']"
  560. >删除课程</el-button>
  561. </template>
  562. </el-table-column>
  563. </el-table>
  564. <pagination
  565. v-show="course.total > 0"
  566. :total="course.total"
  567. :page.sync="course.queryParams.pageNum"
  568. :limit.sync="course.queryParams.pageSize"
  569. @pagination="getCourseList"
  570. style="height: 40px"
  571. />
  572. </el-tab-pane>
  573. <el-tab-pane label="公司列表" name="company">
  574. <red-packet
  575. :visible.sync="redPacketVisible"
  576. :activeTab="activeTab"
  577. :periodId="periodSettingsData.periodId"
  578. @success="handleRedPacketSuccess"
  579. />
  580. </el-tab-pane>
  581. <el-tab-pane label="课程统计" name="statistics">
  582. <course-statistics
  583. :periodId="periodSettingsData.periodId"
  584. :active="activeTab === 'statistics'"
  585. />
  586. </el-tab-pane>
  587. </el-tabs>
  588. </div>
  589. </el-drawer>
  590. <batch-red-packet
  591. :visible.sync="batchRedPacketVisible"
  592. :selected-data="selectedPeriods"
  593. @success="handleBatchRedPacketSuccess"
  594. />
  595. </div>
  596. </template>
  597. <script>
  598. import {addPeriod, delPeriod, exportPeriod, getPeriod, pagePeriod, updatePeriod,delPeriodDay, getDays, addCourse, updateCourseTime, updateCourseDate, updateListCourseData, periodCourseMove, closePeriod} from "@/api/course/userCoursePeriod";
  599. import {getCompanyList} from "@/api/company/company";
  600. import { listCamp, addCamp, editCamp, delCamp, copyCamp } from "@/api/course/userCourseCamp";
  601. import { courseList,videoList } from '@/api/course/courseRedPacketLog'
  602. import RedPacket from './redPacket.vue'
  603. import BatchRedPacket from './batchRedPacket.vue'
  604. import CourseStatistics from './statistics.vue'
  605. import Da from "element-ui/src/locale/lang/da";
  606. export default {
  607. name: "Period",
  608. components: {
  609. RedPacket,
  610. BatchRedPacket,
  611. CourseStatistics
  612. },
  613. data() {
  614. return {
  615. // 遮罩层
  616. loading: true,
  617. updateDateOpen: false,
  618. // 左侧遮罩层
  619. leftLoading: true,
  620. // 选中数组
  621. ids: [],
  622. // 非单个禁用
  623. single: true,
  624. // 非多个禁用
  625. multiple: true,
  626. // 显示搜索条件
  627. showSearch: true,
  628. // 总条数
  629. total: 0,
  630. // 左侧总条数
  631. leftTotal: 0,
  632. // 会员营期表格数据
  633. periodList: [],
  634. // 左侧列表数据
  635. leftList: [],
  636. videoList: [],
  637. // 弹出层标题
  638. title: "",
  639. isDisabledDateRange: false, //是否禁用开营日期
  640. // 是否显示弹出层
  641. open: false,
  642. // 查询参数
  643. queryParams: {
  644. pageNum: 1,
  645. pageSize: 10,
  646. periodName: null,
  647. periodStartingTime: null,
  648. periodEndTime: null,
  649. companyIdList: []
  650. },
  651. // 左侧查询参数
  652. leftQueryParams: {
  653. pageNum: 1,
  654. pageSize: 10,
  655. hasNextPage: false,
  656. scs: 'order_number(desc),training_camp_id(desc)',
  657. trainingCampName: null
  658. },
  659. // 表单参数
  660. form: {},
  661. // 课程相关数据
  662. course: {
  663. open: false,
  664. row:{},
  665. list:[],
  666. queryParams: {
  667. pageNum: 1,
  668. pageSize: 10,
  669. },
  670. loading: true,
  671. total: 0,
  672. addOpen: false,
  673. form: {},
  674. },
  675. //修改营期时间参数
  676. updatePeriodDate: {
  677. open: false,
  678. loading: true,
  679. ids: [],
  680. form: {},
  681. },
  682. updateCourse: {
  683. open: false,
  684. loading: true,
  685. ids: [],
  686. form: {
  687. timeRange: null,
  688. joinTime: null
  689. },
  690. },
  691. // 表单校验
  692. rules: {
  693. periodName: [
  694. { required: true, message: '营期名称不能为空', trigger: 'blur' }
  695. ],
  696. companyId: [
  697. { required: true, message: '公司不能为空', trigger: 'change' }
  698. ],
  699. courseStyle: [
  700. { required: true, message: '课程风格不能为空', trigger: 'change' }
  701. ],
  702. // redPacketGrantMethod: [
  703. // { required: true, message: '红包发放方式不能为空', trigger: 'change' }
  704. // ],
  705. periodType: [
  706. { required: true, message: '营期类型不能为空', trigger: 'change' }
  707. ],
  708. maxViewNum: [
  709. { required: true, message: '销售可查看天数不能为空', trigger: 'blur' }
  710. ],
  711. periodStartingTime: [
  712. { required: true, message: '开营日期不能为空', trigger: 'change' }
  713. ],
  714. periodLine: [
  715. { required: true, message: '营期线不能为空', trigger: 'change' }
  716. ]
  717. },
  718. // 公司选项
  719. companyOptions: [],
  720. // 训练营列表
  721. campList: [],
  722. // 激活的训练营索引
  723. activeCampIndex: null,
  724. // 训练营对话框是否显示
  725. campDialogVisible: false,
  726. courseList: false,
  727. // 训练营表单
  728. campForm: {
  729. trainingCampId: null,
  730. trainingCampName: ''
  731. },
  732. // 训练营表单校验
  733. campRules: {
  734. trainingCampName: [
  735. { required: true, message: '训练营名称不能为空', trigger: 'blur' },
  736. { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
  737. ]
  738. },
  739. // 添加课程表单校验
  740. courseAddRules: {
  741. courseId: [
  742. { required: true, message: '请选择课程', trigger: 'change' }
  743. ],
  744. videoIds: [
  745. { required: true, message: '请选择小节', trigger: 'change' },
  746. { type: 'array', min: 1, message: '请至少选择一个小节', trigger: 'change' }
  747. ]
  748. },
  749. // 修改看课时间表单校验
  750. courseUpdateRules: {
  751. timeRange: [
  752. {
  753. required: true,
  754. validator: (rule, value, callback) => {
  755. if (!value || !Array.isArray(value) || value.length !== 2) {
  756. callback(new Error('请选择完整的看课时间范围'));
  757. } else {
  758. // 检查时间格式是否正确(yyyy-MM-dd HH:mm:ss)
  759. const timeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
  760. if (!timeRegex.test(value[0]) || !timeRegex.test(value[1])) {
  761. callback(new Error('时间格式不正确'));
  762. } else if (value[0] >= value[1]) {
  763. callback(new Error('结束时间必须大于开始时间'));
  764. } else {
  765. callback();
  766. }
  767. }
  768. },
  769. trigger: 'change'
  770. }
  771. ],
  772. joinTime: [
  773. {
  774. required: true,
  775. validator: (rule, value, callback) => {
  776. if (!value) {
  777. callback(new Error('请选择领取红包时间'));
  778. } else {
  779. // 检查时间格式是否正确(yyyy-MM-dd HH:mm:ss)
  780. const timeRegex = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
  781. if (!timeRegex.test(value)) {
  782. callback(new Error('时间格式不正确'));
  783. } else {
  784. // 检查领取红包时间是否在看课时间范围内
  785. const timeRange = this.updateCourse.form.timeRange;
  786. if (timeRange && Array.isArray(timeRange) && timeRange.length === 2) {
  787. if (value < timeRange[0] || value > timeRange[1]) {
  788. callback(new Error('领取红包时间必须在看课时间范围内'));
  789. } else {
  790. callback();
  791. }
  792. } else {
  793. callback();
  794. }
  795. }
  796. }
  797. },
  798. trigger: 'change'
  799. }
  800. ]
  801. },
  802. // 滚动节流标志
  803. scrollThrottle: false,
  804. // 加载更多状态
  805. loadingMore: false,
  806. // 设置红包对话框
  807. redPacketVisible: false,
  808. periodCompanyList: [],
  809. currentRedPacketData: {
  810. periodId: '',
  811. videoId: ''
  812. },
  813. // 营期相关设置抽屉
  814. periodSettingsVisible: false,
  815. activeTab: 'course',
  816. periodSettingsData: {},
  817. companyList: [],
  818. courseDialogVisible: false,
  819. redPacketList: [],
  820. currentCompany: null,
  821. // 选中的营期数据
  822. selectedPeriods: [],
  823. // 批量设置红包按钮是否禁用
  824. batchSetRedPacketDisabled: true,
  825. // 批量设置红包弹出框
  826. batchRedPacketVisible: false,
  827. };
  828. },
  829. created() {
  830. courseList().then(response => {
  831. this.courseList = response.list;
  832. });
  833. // this.getList();
  834. this.getLeftList();
  835. this.getCompanyList();
  836. },
  837. methods: {
  838. /** 查询会员营期列表 */
  839. getList() {
  840. this.loading = true;
  841. const params = { ...this.queryParams };
  842. pagePeriod(params).then(response => {
  843. this.periodList = response.rows;
  844. this.total = response.total;
  845. this.loading = false;
  846. });
  847. },
  848. /** 查询左侧列表 */
  849. getLeftList() {
  850. this.leftLoading = true;
  851. // 重置页码和加载更多状态
  852. this.leftQueryParams.pageNum = 1;
  853. this.loadingMore = false;
  854. // 训练营数据
  855. listCamp(this.leftQueryParams).then(response => {
  856. if (response && response.code === 200) {
  857. this.campList = response.data.list || [];
  858. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  859. this.activeCampIndex = this.campList.length > 0 ? 0 : null;
  860. this.selectCamp(this.activeCampIndex);
  861. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  862. this.$nextTick(() => {
  863. const scrollEl = this.$refs.campList;
  864. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  865. this.loadMoreCamps();
  866. }
  867. });
  868. } else {
  869. this.$message.error(response.msg || '获取训练营列表失败');
  870. this.campList = [];
  871. this.leftQueryParams.hasNextPage = false;
  872. }
  873. this.leftLoading = false;
  874. }).catch(error => {
  875. console.error('获取训练营列表失败:', error);
  876. this.$message.error('获取训练营列表失败');
  877. this.campList = [];
  878. this.leftQueryParams.hasNextPage = false;
  879. this.leftLoading = false;
  880. });
  881. },
  882. /** 搜索按钮操作 */
  883. handleQuery() {
  884. this.queryParams.pageNum = 1;
  885. this.getList();
  886. },
  887. /** 左侧搜索按钮操作 */
  888. handleLeftQuery() {
  889. // 重置页码和列表
  890. this.leftQueryParams.pageNum = 1;
  891. this.campList = [];
  892. this.getLeftList();
  893. },
  894. /** 重置按钮操作 */
  895. resetQuery() {
  896. this.resetForm("queryForm");
  897. this.queryParams.companyIdList = [];
  898. this.handleQuery();
  899. },
  900. /** 多选框选中数据 */
  901. handleSelectionChange(selection) {
  902. this.ids = selection.map(item => item.periodId)
  903. this.single = selection.length!==1
  904. this.multiple = !selection.length
  905. // 更新批量设置红包相关数据
  906. this.selectedPeriods = selection;
  907. this.batchSetRedPacketDisabled = selection.length === 0;
  908. },
  909. handleSelectionCourseChange(selection) {
  910. this.updateCourse.ids = selection.map(item => item.id)
  911. },
  912. /** 新增按钮操作 */
  913. handleAdd() {
  914. this.reset();
  915. this.open = true;
  916. this.title = "添加会员营期";
  917. this.isDisabledDateRange = false;
  918. },
  919. /** 修改按钮操作 */
  920. handleUpdate(row) {
  921. this.reset();
  922. const periodId = row.periodId || this.ids
  923. getPeriod(periodId).then(response => {
  924. this.form = response.data;
  925. if (this.form.companyId) {
  926. this.form.companyId = this.form.companyId.split(',').map(id => Number(id));
  927. }
  928. // 设置看课时间范围(回显)
  929. if (this.form.viewStartTime && this.form.viewEndTime) {
  930. this.form.timeRange = [this.form.viewStartTime, this.form.viewEndTime];
  931. }
  932. if(this.form.periodType == 1){
  933. // this.form.dateRange = [this.form.periodStartingTime, this.form.periodEndTime];
  934. this.$set(this.form,'dateRange',[this.form.periodStartingTime, this.form.periodEndTime])
  935. // console.log('this.form.dateRange',this.form.dateRange)
  936. }
  937. if(this.form.periodType == 2){
  938. this.$set(this.form,'date',this.form.periodStartingTime)
  939. // this.form.date = this.form.periodStartingTime;
  940. }
  941. this.open = true;
  942. this.title = "修改会员营期";
  943. this.isDisabledDateRange = true;
  944. });
  945. },
  946. /** 提交按钮 */
  947. submitForm() {
  948. this.$refs["form"].validate(valid => {
  949. if (valid) {
  950. let data = JSON.parse(JSON.stringify(this.form));
  951. // 处理看课时间范围
  952. if (data.timeRange && data.timeRange.length === 2) {
  953. data.viewStartTime = data.timeRange[0];
  954. data.viewEndTime = data.timeRange[1];
  955. }
  956. data.companyId = data.companyId.join()
  957. data.trainingCampId = this.queryParams.trainingCampId
  958. if (data.periodId != null) {
  959. updatePeriod(data).then(response => {
  960. if (response.code === 200) {
  961. this.msgSuccess("修改成功");
  962. this.open = false;
  963. this.getList();
  964. }
  965. });
  966. } else {
  967. addPeriod(data).then(response => {
  968. if (response.code === 200) {
  969. this.msgSuccess("新增成功");
  970. this.open = false;
  971. this.getList();
  972. }
  973. });
  974. }
  975. }
  976. });
  977. },
  978. /** 删除按钮操作 */
  979. async handleDelete(row) {
  980. const periodIds = row.periodId || this.ids;
  981. try {
  982. await this.$confirm('是否确认删除该营期?', "提示", {
  983. confirmButtonText: "确定",
  984. cancelButtonText: "取消",
  985. type: "warning"
  986. });
  987. const res = await delPeriod(periodIds);
  988. if (res && res.code === 200) {
  989. this.getList(); // 刷新列表
  990. this.$message.success('删除成功');
  991. } else {
  992. this.$message.error(res.msg);
  993. }
  994. } catch (error) {
  995. }
  996. },
  997. /** 删除按钮操作 */
  998. async handleDeleteCourse(row) {
  999. const periodDayIds = row.id || this.updateCourse.ids;
  1000. try {
  1001. await this.$confirm('是否确认删除该课程?', "提示", {
  1002. confirmButtonText: "确定",
  1003. cancelButtonText: "取消",
  1004. type: "warning"
  1005. });
  1006. const res = await delPeriodDay(periodDayIds);
  1007. if (res && res.code === 200) {
  1008. this.getCourseList();// 刷新列表
  1009. this.$message.success('删除成功');
  1010. } else {
  1011. this.$message.error(res.msg);
  1012. }
  1013. } catch (error) {
  1014. }
  1015. },
  1016. /** 导出按钮操作 */
  1017. handleExport() {
  1018. const queryParams = this.queryParams;
  1019. this.$confirm('是否确认导出所有会员营期数据项?', "警告", {
  1020. confirmButtonText: "确定",
  1021. cancelButtonText: "取消",
  1022. type: "warning"
  1023. }).then(function() {
  1024. return exportPeriod(queryParams);
  1025. }).then(response => {
  1026. this.download(response.msg);
  1027. }).catch(function() {});
  1028. },
  1029. /** 批量设置红包 */
  1030. handleBatchSetRedPacket() {
  1031. if (this.selectedPeriods.length === 0) {
  1032. this.$message.warning('请至少选择一个营期');
  1033. return;
  1034. }
  1035. this.batchRedPacketVisible = true;
  1036. },
  1037. /** 处理批量设置红包保存 */
  1038. // handleBatchRedPacketSave(data) {
  1039. // // 这里等待接口提供后补充具体实现
  1040. // // 示例代码:
  1041. // // batchSetRedPacket(data).then(response => {
  1042. // // if (response.code === 200) {
  1043. // // this.$message.success('批量设置成功');
  1044. // // this.getList();
  1045. // // }
  1046. // // });
  1047. // this.batchRedPacketVisible = false;
  1048. // },
  1049. /** 获取公司下拉列表*/
  1050. getCompanyList() {
  1051. this.loading = true;
  1052. getCompanyList().then(response => {
  1053. this.companyOptions = response.data;
  1054. this.loading = false;
  1055. });
  1056. },
  1057. // 取消按钮
  1058. cancel() {
  1059. this.open = false;
  1060. this.reset();
  1061. },
  1062. // 表单重置
  1063. reset() {
  1064. this.form = {
  1065. periodId: null,
  1066. periodName: null,
  1067. companyId: null,
  1068. courseId: null,
  1069. videoId: null,
  1070. trainingCampId: null,
  1071. createTime: null,
  1072. updateTime: null,
  1073. courseStyle: null,
  1074. liveRoomStyle: null,
  1075. redPacketGrantMethod: 1,
  1076. periodType: 1,
  1077. periodStartingTime: null,
  1078. dateRange: [],
  1079. date: null,
  1080. days: [],
  1081. maxViewNum: 0,
  1082. periodEndTime: null,
  1083. timeRange: [], // 看课时间范围
  1084. viewStartTime: null, // 看课开始时间
  1085. viewEndTime: null, // 看课结束时间
  1086. lastJoinTime: null // 领取红包时间
  1087. };
  1088. this.resetForm("form");
  1089. },
  1090. // 处理训练营列表的逻辑
  1091. handleDeleteCamp(item) {
  1092. this.$confirm(`确定要删除训练营"${item.trainingCampName}"吗?`, '提示', {
  1093. confirmButtonText: '删除',
  1094. cancelButtonText: '取消',
  1095. type: 'warning'
  1096. }).then(() => {
  1097. // 调用删除训练营API
  1098. const trainingCampId = item.trainingCampId;
  1099. this.leftLoading = true;
  1100. delCamp(trainingCampId).then(response => {
  1101. if (response.code === 200) {
  1102. this.$message.success('删除成功');
  1103. // 从列表中移除
  1104. const index = this.campList.findIndex(camp => camp.trainingCampId === trainingCampId);
  1105. if (index !== -1) {
  1106. this.campList.splice(index, 1);
  1107. }
  1108. // 如果删除的是当前选中的训练营,则重置选中状态
  1109. if (this.activeCampIndex === index) {
  1110. this.activeCampIndex = this.campList.length > 0 ? 0 : null;
  1111. if (this.activeCampIndex !== null) {
  1112. // 更新右侧列表
  1113. this.selectCamp(this.activeCampIndex);
  1114. } else {
  1115. // 没有训练营了,清空右侧列表
  1116. this.periodList = [];
  1117. }
  1118. }
  1119. } else {
  1120. this.$message.error(response.msg || '删除失败');
  1121. }
  1122. this.leftLoading = false;
  1123. }).catch(error => {
  1124. this.$message.error('删除失败: ' + error.message);
  1125. this.leftLoading = false;
  1126. });
  1127. }).catch(() => {
  1128. this.$message.info('已取消删除');
  1129. });
  1130. },
  1131. /** 复制训练营 */
  1132. handleCopyCamp(item) {
  1133. // 调用添加训练营API
  1134. copyCamp(item.trainingCampId).then(response => {
  1135. if (response.code === 200) {
  1136. this.$message.success('复制成功');
  1137. // 重新加载训练营列表
  1138. this.getLeftList();
  1139. } else {
  1140. this.$message.error(response.msg || '复制训练营失败');
  1141. }
  1142. }).catch(error => {
  1143. this.$message.error('复制训练营失败: ' + error.message);
  1144. });
  1145. },
  1146. /** 修改训练营按钮操作 */
  1147. handleEditCamp(item) {
  1148. this.resetCampForm();
  1149. this.leftLoading = true;
  1150. // 获取最新的训练营数据
  1151. const trainingCampId = item.trainingCampId;
  1152. // 应该调用获取训练营详情的API
  1153. setTimeout(() => {
  1154. // 填充表单数据
  1155. this.campForm = {
  1156. trainingCampId: item.trainingCampId,
  1157. trainingCampName: item.trainingCampName,
  1158. orderNumber: item.orderNumber || 1,
  1159. status: item.status !== undefined ? item.status : 1
  1160. };
  1161. this.campDialogVisible = true;
  1162. this.leftLoading = false;
  1163. }, 300);
  1164. },
  1165. /** 新建训练营按钮操作 */
  1166. handleAddTrainingCamp() {
  1167. this.resetCampForm();
  1168. this.campDialogVisible = true;
  1169. },
  1170. /** 重置训练营表单 */
  1171. resetCampForm() {
  1172. this.campForm = {
  1173. trainingCampId: null,
  1174. trainingCampName: ''
  1175. };
  1176. // 如果表单已经创建,则重置校验结果
  1177. if (this.$refs.campForm) {
  1178. this.$refs.campForm.resetFields();
  1179. }
  1180. },
  1181. /** 取消训练营表单 */
  1182. cancelCampForm() {
  1183. this.campDialogVisible = false;
  1184. this.resetCampForm();
  1185. },
  1186. /** 提交训练营表单 */
  1187. submitCampForm() {
  1188. this.$refs.campForm.validate(valid => {
  1189. if (valid) {
  1190. // 显示加载中
  1191. this.leftLoading = true;
  1192. // 准备提交的数据
  1193. const submitData = JSON.parse(JSON.stringify(this.campForm));
  1194. // 判断是新增还是修改
  1195. if (submitData.trainingCampId) {
  1196. // 修改训练营
  1197. editCamp(submitData).then(response => {
  1198. if (response.code === 200) {
  1199. this.$message.success('修改训练营成功');
  1200. this.campDialogVisible = false;
  1201. // 更新列表中的数据
  1202. const index = this.campList.findIndex(camp => camp.trainingCampId === submitData.trainingCampId);
  1203. if (index !== -1) {
  1204. this.campList[index] = { ...this.campList[index], ...submitData };
  1205. // 如果修改的是当前选中的训练营,更新右侧列表
  1206. if (this.activeCampIndex === index) {
  1207. this.selectCamp(index);
  1208. }
  1209. }
  1210. // 重新加载训练营列表
  1211. this.getLeftList();
  1212. } else {
  1213. this.$message.error(response.msg || '修改训练营失败');
  1214. this.leftLoading = false;
  1215. }
  1216. }).catch(error => {
  1217. this.$message.error('修改训练营失败: ' + (error.message || '未知错误'));
  1218. this.leftLoading = false;
  1219. });
  1220. } else {
  1221. // 新增训练营
  1222. addCamp(submitData).then(response => {
  1223. if (response.code === 200) {
  1224. this.$message.success('新建训练营成功');
  1225. this.campDialogVisible = false;
  1226. // 重新加载训练营列表
  1227. this.getLeftList();
  1228. } else {
  1229. this.$message.error(response.msg || '新建训练营失败');
  1230. this.leftLoading = false;
  1231. }
  1232. }).catch(error => {
  1233. this.$message.error('新建训练营失败: ' + (error.message || '未知错误'));
  1234. this.leftLoading = false;
  1235. });
  1236. }
  1237. }
  1238. });
  1239. },
  1240. /** 排序方式改变 */
  1241. handleSortChange(value) {
  1242. this.leftQueryParams.scs = value;
  1243. // 重置页码和列表
  1244. this.leftQueryParams.pageNum = 1;
  1245. this.campList = [];
  1246. this.getLeftList();
  1247. },
  1248. /** 选中训练营 */
  1249. selectCamp(index) {
  1250. if(index == null || index == undefined) return;
  1251. this.activeCampIndex = index;
  1252. // 加载对应的训练营营期数据
  1253. const selectedCamp = this.campList[index];
  1254. this.queryParams.trainingCampId = selectedCamp.trainingCampId;
  1255. this.getList();
  1256. },
  1257. /** 处理滚动事件,实现滚动到底部加载更多 */
  1258. handleScroll() {
  1259. // 如果正在节流中或者正在加载中,则不处理
  1260. if (this.scrollThrottle || this.loadingMore) return;
  1261. // 设置节流,200ms内不再处理滚动事件
  1262. this.scrollThrottle = true;
  1263. setTimeout(() => {
  1264. this.scrollThrottle = false;
  1265. }, 200);
  1266. const scrollEl = this.$refs.campList;
  1267. if (!scrollEl) return;
  1268. // 判断是否滚动到底部:滚动高度 + 可视高度 >= 总高度 - 30(添加30px的容差,提前触发加载)
  1269. const isBottom = scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 30;
  1270. // 如果滚动到底部,且有下一页数据,且当前不在加载中,则加载更多
  1271. if (isBottom && this.leftQueryParams.hasNextPage && !this.leftLoading && !this.loadingMore) {
  1272. this.loadMoreCamps();
  1273. }
  1274. },
  1275. /** 加载更多训练营数据 */
  1276. loadMoreCamps() {
  1277. // 已在加载中,防止重复加载
  1278. if (this.leftLoading || this.loadingMore) return;
  1279. // 设置加载状态
  1280. this.loadingMore = true;
  1281. // 页码加1
  1282. this.leftQueryParams.pageNum += 1;
  1283. // 加载下一页数据
  1284. listCamp(this.leftQueryParams).then(response => {
  1285. if (response && response.code === 200) {
  1286. // 将新数据追加到列表中
  1287. const newList = response.data.list || [];
  1288. if (newList.length > 0) {
  1289. this.campList = [...this.campList, ...newList];
  1290. }
  1291. // 更新是否有下一页的标志
  1292. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  1293. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  1294. this.$nextTick(() => {
  1295. const scrollEl = this.$refs.campList;
  1296. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  1297. // 延迟一点再加载下一页,避免过快加载
  1298. setTimeout(() => {
  1299. this.loadMoreCamps();
  1300. }, 300);
  1301. }
  1302. });
  1303. } else {
  1304. this.$message.error(response.msg || '加载更多训练营失败');
  1305. }
  1306. this.loadingMore = false;
  1307. }).catch(error => {
  1308. console.error('加载更多训练营失败:', error);
  1309. this.$message.error('加载更多训练营失败');
  1310. this.loadingMore = false;
  1311. });
  1312. },
  1313. timeChange(type) {
  1314. if (type == 1) {
  1315. this.form.periodStartingTime = this.form.dateRange[0];
  1316. this.form.periodEndTime = this.form.dateRange[1];
  1317. if (!Array.isArray(this.form.days)) {
  1318. this.form.days = [];
  1319. }
  1320. this.form.days = [];
  1321. let days = this.getDiff(this.form.periodStartingTime, this.form.periodEndTime);
  1322. for (let i = 0; i < days; i++) {
  1323. this.form.days.push({ lesson: i + 1 });
  1324. }
  1325. }
  1326. if (type == 2) {
  1327. this.form.periodStartingTime = this.form.date;
  1328. this.form.periodEndTime = this.form.date;
  1329. this.form.days = [];
  1330. }
  1331. },
  1332. getDiff(start, end) {
  1333. if(start == null || start == undefined || start == '') return 0;
  1334. if(end == null || end == undefined || end == '') return 0;
  1335. if(start == end) 1;
  1336. const startDate = this.getUTCDate(start);
  1337. const endDate = this.getUTCDate(end);
  1338. const timeDiff = endDate - startDate;
  1339. return (Math.floor(timeDiff / (1000 * 3600 * 24))) + 1; // 直接取整
  1340. },
  1341. getUTCDate(dateStr) {
  1342. const [year, month, day] = dateStr.split('-').map(Number);
  1343. return new Date(Date.UTC(year, month - 1, day)); // 月份从0开始
  1344. },
  1345. // handleCourse(row){
  1346. // this.course = {
  1347. // open: false,
  1348. // row:{},
  1349. // list:[],
  1350. // queryParams: {
  1351. // pageNum: 1,
  1352. // pageSize: 3, // 修改为每页显示3条
  1353. // },
  1354. // loading: true,
  1355. // total: 0,
  1356. // addOpen: false,
  1357. // form: {},
  1358. // };
  1359. // this.course.open = true;
  1360. // this.course.row = row;
  1361. // this.course.queryParams.periodId = row.periodId;
  1362. // this.getCourseList();
  1363. // },
  1364. getCourseList(){
  1365. this.course.loading = true;
  1366. getDays(this.course.queryParams).then(e => {
  1367. this.course.list = e.rows;
  1368. this.course.total = e.total;
  1369. this.course.loading = false;
  1370. });
  1371. },
  1372. handleAddCourse() {
  1373. this.course.addOpen = true;
  1374. this.course.form = {
  1375. periodId: this.course.queryParams.periodId,
  1376. courseId: null,
  1377. timeRange: ['00:00:00', '23:59:59'],
  1378. joinTime: '23:59:59',
  1379. videoIds: []
  1380. };
  1381. // 重置表单
  1382. this.$nextTick(() => {
  1383. if (this.$refs.courseAddForm) {
  1384. this.$refs.courseAddForm.resetFields();
  1385. }
  1386. });
  1387. },
  1388. handleUpdateCourse() {
  1389. this.updateCourse.open = true;
  1390. this.updateCourse.form = {
  1391. ids: this.updateCourse.ids,
  1392. timeRange: null,
  1393. joinTime: null
  1394. };
  1395. // 重置表单校验状态(但不清空选中状态)
  1396. this.$nextTick(() => {
  1397. if (this.$refs.courseUpdateForm) {
  1398. this.$refs.courseUpdateForm.resetFields();
  1399. }
  1400. });
  1401. },
  1402. closeAddCourse() {
  1403. this.course.addOpen = false;
  1404. this.course.form = {
  1405. periodId: null,
  1406. courseId: null,
  1407. timeRange: null,
  1408. joinTime: null,
  1409. videoIds: []
  1410. };
  1411. // 重置表单
  1412. if (this.$refs.courseAddForm) {
  1413. this.$refs.courseAddForm.resetFields();
  1414. }
  1415. },
  1416. closeUpdateCourse() {
  1417. this.updateCourse.open = false;
  1418. // 清空选中的课程id,使【修改看课时间】按钮变为禁用状态
  1419. this.updateCourse.ids = [];
  1420. // 清空课程列表的选中状态
  1421. this.$nextTick(() => {
  1422. if (this.$refs.courseTable) {
  1423. this.$refs.courseTable.clearSelection();
  1424. }
  1425. });
  1426. // 重置表单数据
  1427. this.updateCourse.form = {
  1428. ids: [],
  1429. timeRange: null,
  1430. joinTime: null
  1431. };
  1432. // 重置表单校验
  1433. this.$nextTick(() => {
  1434. if (this.$refs.courseUpdateForm) {
  1435. this.$refs.courseUpdateForm.resetFields();
  1436. }
  1437. });
  1438. },
  1439. courseChange(row){
  1440. this.course.form.videoIds = [];
  1441. videoList(row).then(response => {
  1442. this.videoList=response.list
  1443. });
  1444. },
  1445. submitCourseForm(){
  1446. this.$refs.courseAddForm.validate(valid => {
  1447. if (valid) {
  1448. if(this.course.form.timeRange != null && this.course.form.timeRange.length === 2){
  1449. this.course.form.startTime = this.course.form.timeRange[0];
  1450. this.course.form.endTime1 = this.course.form.timeRange[1];
  1451. }
  1452. // 提交数据
  1453. addCourse(this.course.form).then(response => {
  1454. this.$message.success('添加成功');
  1455. this.course.addOpen = false;
  1456. // 重新加载训练营列表
  1457. this.getCourseList();
  1458. });
  1459. }
  1460. });
  1461. },
  1462. submitUpdateCourseForm(){
  1463. this.$refs.courseUpdateForm.validate(valid => {
  1464. if (valid) {
  1465. // 检查时间范围是否正确
  1466. if (this.updateCourse.form.timeRange &&
  1467. Array.isArray(this.updateCourse.form.timeRange) &&
  1468. this.updateCourse.form.timeRange.length === 2) {
  1469. this.updateCourse.form.startTime = this.updateCourse.form.timeRange[0];
  1470. this.updateCourse.form.endTime1 = this.updateCourse.form.timeRange[1];
  1471. } else {
  1472. this.$message.error('请先选择正确的看课时间范围');
  1473. return;
  1474. }
  1475. // 检查领取红包时间是否正确
  1476. if (!this.updateCourse.form.joinTime) {
  1477. this.$message.error('请选择领取红包时间');
  1478. return;
  1479. }
  1480. // 提交数据
  1481. updateCourseTime(this.updateCourse.form).then(response => {
  1482. this.$message.success('修改成功');
  1483. this.updateCourse.open = false;
  1484. // 清空选中的课程ID,使【修改看课时间】按钮变为禁用状态
  1485. this.updateCourse.ids = [];
  1486. // 清空课程列表的选中状态
  1487. this.$nextTick(() => {
  1488. if (this.$refs.courseTable) {
  1489. this.$refs.courseTable.clearSelection();
  1490. }
  1491. });
  1492. // 重新加载训练营列表
  1493. this.getCourseList();
  1494. }).catch(error => {
  1495. this.$message.error('修改失败:' + (error.message || '未知错误'));
  1496. });
  1497. }
  1498. });
  1499. },
  1500. updatePeriodDateSubmit(){
  1501. updateCourseDate(this.updatePeriodDate.form).then(response => {
  1502. this.$message.success('修改成功');
  1503. this.updatePeriodDate.open = false;
  1504. // 重新加载课程列表
  1505. this.getCourseList();
  1506. });
  1507. },
  1508. // saveCourseData(){
  1509. // updateListCourseData(this.course.list).then(response => {
  1510. // this.$message.success('保存成功');
  1511. // this.getCourseList();
  1512. // });
  1513. // },
  1514. // setRedPacket(row) {
  1515. // this.currentRedPacketData = {
  1516. // periodId: row.periodId
  1517. // // videoId: row.videoId
  1518. // };
  1519. // this.redPacketVisible = true;
  1520. // },
  1521. handleRedPacketSuccess() {
  1522. this.getCourseList();
  1523. },
  1524. handlePeriodSettings(row) {
  1525. this.periodSettingsData = row;
  1526. this.periodSettingsVisible = true;
  1527. // 初始化课程列表
  1528. this.course.queryParams.periodId = row.periodId;
  1529. // 根据当前激活的tab加载对应数据
  1530. this.handleTabClick({ name: this.activeTab });
  1531. },
  1532. // 结束营期
  1533. handleClosePeriod(row) {
  1534. const msg = `注: 1.确认结束营期,该营期的开营结束时间改为当天24点。2.当天正在播放中的课程不变。3.第二天如有未开始的课程,统一改为已结束。是否确认结束 ${row.periodName} 营期吗?`
  1535. this.$confirm(msg, "警告", {
  1536. confirmButtonText: "确定",
  1537. cancelButtonText: "取消",
  1538. type: "warning"
  1539. }).then(() => {
  1540. closePeriod({id: row.periodId}).then(response => {
  1541. if (response.code === 200) {
  1542. this.getList()
  1543. } else {
  1544. this.$message.error(response.msg)
  1545. }
  1546. })
  1547. }).catch(() => {})
  1548. },
  1549. handleBatchRedPacketSuccess() {
  1550. this.batchRedPacketVisible = false;
  1551. this.getCourseList();
  1552. },
  1553. /** 处理tab切换 */
  1554. handleTabClick(tab) {
  1555. if (tab.name === 'course') {
  1556. this.getCourseList();
  1557. } else if (tab.name === 'company') {
  1558. this.redPacketVisible = true;
  1559. }
  1560. },
  1561. /** 上移课程 */
  1562. handleTop(row) {
  1563. const currentIndex = this.course.list.findIndex(item => item.id === row.id);
  1564. if (currentIndex <= 0) {
  1565. this.$message.warning('已经是第一条数据');
  1566. return;
  1567. }
  1568. // 获取上一条数据
  1569. const prevRow = this.course.list[currentIndex - 1];
  1570. console.log({
  1571. id: row.id,
  1572. targetId: prevRow.id,
  1573. type: 1 //上移
  1574. })
  1575. periodCourseMove({
  1576. id: row.id,
  1577. targetId: prevRow.id,
  1578. type: 1 //上移
  1579. }).then(response => {
  1580. if (response.code === 200) {
  1581. this.$message.success('上移成功');
  1582. this.getCourseList();
  1583. } else {
  1584. this.$message.error(response.msg || '上移失败');
  1585. }
  1586. }).catch(() => {
  1587. this.$message.error('上移失败');
  1588. });
  1589. },
  1590. /** 下移课程 */
  1591. handleBottom(row) {
  1592. const currentIndex = this.course.list.findIndex(item => item.id === row.id);
  1593. if (currentIndex === -1 || currentIndex >= this.course.list.length - 1) {
  1594. this.$message.warning('已经是最后一条数据');
  1595. return;
  1596. }
  1597. // 获取下一条数据
  1598. const nextRow = this.course.list[currentIndex + 1];
  1599. periodCourseMove({
  1600. id: row.id,
  1601. targetId: nextRow.id,
  1602. type: 2 //下移
  1603. }).then(response => {
  1604. if (response.code === 200) {
  1605. this.$message.success('下移成功');
  1606. this.getCourseList(); // 重新加载列表
  1607. } else {
  1608. this.$message.error(response.msg || '下移失败');
  1609. }
  1610. }).catch(() => {
  1611. this.$message.error('下移失败');
  1612. });
  1613. },
  1614. /** 营期状态格式化 */
  1615. periodStatusFormatter(row) {
  1616. const statusMap = {
  1617. 1: '未开始',
  1618. 2: '进行中',
  1619. 3: '已结束'
  1620. };
  1621. return statusMap[row.periodStatus] || '未知状态';
  1622. },
  1623. /** 开课状态格式化 */
  1624. courseStatusFormatter(row) {
  1625. const statusMap = {
  1626. 0: '未开始',
  1627. 1: '进行中',
  1628. 2: '已结束'
  1629. };
  1630. return statusMap[row.status] || '未知状态';
  1631. },
  1632. /** 营期状态格式化 */
  1633. handleUpdateDate(row) {
  1634. this.updatePeriodDate.form = {id: row.id, dayDate: row.dayDate};
  1635. this.updatePeriodDate.open = true;
  1636. },
  1637. disabledDate(time) {
  1638. return time.getTime() < new Date(new Date().setHours(0,0,0,0));
  1639. },
  1640. getPickerOptions() {
  1641. // 如果已选择看课时间范围,则领取红包时间应在看课时间范围内
  1642. if (this.updateCourse.form.timeRange &&
  1643. Array.isArray(this.updateCourse.form.timeRange) &&
  1644. this.updateCourse.form.timeRange.length === 2) {
  1645. return {
  1646. selectableRange: `${this.updateCourse.form.timeRange[0]} - ${this.updateCourse.form.timeRange[1]}`
  1647. };
  1648. }
  1649. // 默认全天可选
  1650. return {
  1651. selectableRange: '00:00:00 - 23:59:59'
  1652. };
  1653. },
  1654. onTimeRangeChange() {
  1655. // 当看课时间范围改变时,清空领取红包时间
  1656. this.updateCourse.form.joinTime = null;
  1657. // 强制更新组件以刷新领取红包时间的可选范围
  1658. this.$forceUpdate();
  1659. },
  1660. },
  1661. };
  1662. </script>
  1663. <style scoped>
  1664. .left-aside {
  1665. background-color: #fff;
  1666. border-right: 1px solid #EBEEF5;
  1667. padding: 0;
  1668. display: flex;
  1669. flex-direction: column;
  1670. height: 800px;
  1671. }
  1672. .left-header {
  1673. padding: 10px;
  1674. border-bottom: 1px solid #EBEEF5;
  1675. }
  1676. .left-header-top {
  1677. display: flex;
  1678. justify-content: space-between;
  1679. align-items: center;
  1680. margin-bottom: 10px;
  1681. }
  1682. .search-btn {
  1683. width: 50%;
  1684. height: 36px;
  1685. background-color: #409EFF;
  1686. color: white;
  1687. border: none;
  1688. }
  1689. .search-input-wrapper {
  1690. margin-bottom: 10px;
  1691. }
  1692. .sort-wrapper {
  1693. display: flex;
  1694. align-items: center;
  1695. margin-bottom: 10px;
  1696. }
  1697. .sort-label {
  1698. width: 70px;
  1699. font-size: 14px;
  1700. font-weight: 600;
  1701. color: #909399;
  1702. }
  1703. .sort-select {
  1704. margin-left: 10px;
  1705. width: 280px;
  1706. }
  1707. .color-wrapper {
  1708. display: flex;
  1709. align-items: center;
  1710. margin-bottom: 10px;
  1711. }
  1712. .color-label {
  1713. width: 70px;
  1714. color: #606266;
  1715. }
  1716. .color-hint {
  1717. margin-left: 10px;
  1718. color: #606266;
  1719. font-size: 12px;
  1720. }
  1721. .color-help {
  1722. margin-left: 5px;
  1723. color: #909399;
  1724. cursor: pointer;
  1725. }
  1726. .hint-text {
  1727. color: #606266;
  1728. font-size: 12px;
  1729. margin-top: 5px;
  1730. }
  1731. .camp-list {
  1732. flex: 1;
  1733. overflow-y: auto;
  1734. padding: 3px;
  1735. }
  1736. .camp-item {
  1737. margin-bottom: 5px;
  1738. padding: 15px;
  1739. background-color: #ffffff;
  1740. position: relative;
  1741. cursor: pointer;
  1742. display: flex;
  1743. justify-content: space-between;
  1744. border: 1px solid #eaedf2;
  1745. }
  1746. .camp-item:last-child {
  1747. margin-bottom: 0;
  1748. }
  1749. .camp-item:hover {
  1750. background-color: #f5f9ff;
  1751. }
  1752. .camp-item.active {
  1753. background-color: #eaf4ff;
  1754. border-left: 1px solid #75b8fc;
  1755. }
  1756. .camp-content {
  1757. flex: 1;
  1758. padding-right: 10px;
  1759. }
  1760. .camp-title {
  1761. font-weight: bold;
  1762. font-size: 16px;
  1763. margin-bottom: 8px;
  1764. color: #333;
  1765. display: flex;
  1766. align-items: center;
  1767. }
  1768. .camp-icon {
  1769. font-size: 16px;
  1770. margin-right: 6px;
  1771. color: #409EFF;
  1772. }
  1773. .camp-info {
  1774. display: flex;
  1775. justify-content: space-between;
  1776. margin-bottom: 8px;
  1777. font-size: 12px;
  1778. color: #c4c1c1;
  1779. line-height: 1.5;
  1780. }
  1781. .camp-stats {
  1782. display: flex;
  1783. justify-content: space-between;
  1784. font-size: 12px;
  1785. color: #666;
  1786. background-color: #f5f9ff;
  1787. padding: 6px 10px;
  1788. border-radius: 4px;
  1789. line-height: 1.5;
  1790. }
  1791. .stat-item {
  1792. display: flex;
  1793. align-items: center;
  1794. }
  1795. .stat-item i {
  1796. margin-right: 4px;
  1797. font-size: 14px;
  1798. color: #409EFF;
  1799. }
  1800. .camp-actions {
  1801. display: flex;
  1802. flex-direction: column;
  1803. justify-content: center;
  1804. align-items: flex-end;
  1805. gap: 8px;
  1806. border-left: 1px dashed #eaedf2;
  1807. padding-left: 12px;
  1808. min-width: 50px;
  1809. }
  1810. .action-btn {
  1811. padding: 2px 5px;
  1812. font-size: 12px;
  1813. border-radius: 4px;
  1814. transition: all 0.2s;
  1815. }
  1816. .action-btn:hover {
  1817. background-color: rgba(255, 255, 255, 0.8);
  1818. }
  1819. .delete-btn {
  1820. color: #f56c6c;
  1821. }
  1822. .delete-btn:hover {
  1823. background-color: rgba(245, 108, 108, 0.1);
  1824. }
  1825. .copy-btn {
  1826. color: #409EFF;
  1827. }
  1828. .copy-btn:hover {
  1829. background-color: rgba(64, 158, 255, 0.1);
  1830. }
  1831. .warning-icon {
  1832. color: #E6A23C;
  1833. font-size: 16px;
  1834. margin-left: 5px;
  1835. }
  1836. .el-main {
  1837. padding: 10px;
  1838. }
  1839. /* 添加训练营表单样式 */
  1840. .drawer-footer {
  1841. /* position: absolute; */
  1842. bottom: 0;
  1843. left: 0;
  1844. right: 0;
  1845. padding: 20px;
  1846. background: #fff;
  1847. text-align: right;
  1848. border-top: 1px solid #e8e8e8;
  1849. }
  1850. .el-input-number {
  1851. width: 100%;
  1852. }
  1853. /* 加载更多样式 */
  1854. .loading-more {
  1855. display: flex;
  1856. align-items: center;
  1857. justify-content: center;
  1858. padding: 12px 0;
  1859. color: #909399;
  1860. font-size: 14px;
  1861. }
  1862. .loading-more i {
  1863. margin-right: 5px;
  1864. font-size: 16px;
  1865. }
  1866. /* 无更多数据提示 */
  1867. .no-more-data {
  1868. display: flex;
  1869. align-items: center;
  1870. justify-content: center;
  1871. padding: 12px 0;
  1872. color: #c0c4cc;
  1873. font-size: 13px;
  1874. }
  1875. .no-more-data span {
  1876. position: relative;
  1877. display: flex;
  1878. align-items: center;
  1879. }
  1880. </style>