index.vue 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="营期名称" prop="periodName">
  5. <el-input
  6. v-model="queryParams.periodName"
  7. placeholder="请输入营期名称"
  8. clearable
  9. size="small"
  10. @keyup.enter.native="handleQuery"
  11. />
  12. </el-form-item>
  13. <el-form-item label="开营日期开始" prop="periodStartingTime" label-width="120px">
  14. <el-date-picker clearable size="small" style="width: 200px"
  15. v-model="queryParams.periodStartingTime"
  16. type="date"
  17. value-format="yyyy-MM-dd"
  18. placeholder="请选择开营日期开始时间">
  19. </el-date-picker>
  20. </el-form-item>
  21. <el-form-item label="开营日期结束" prop="periodEndTime" label-width="120px">
  22. <el-date-picker clearable size="small" style="width: 300px"
  23. v-model="queryParams.periodEndTime"
  24. type="date"
  25. value-format="yyyy-MM-dd"
  26. placeholder="请选择开营日期结束时间">
  27. </el-date-picker>
  28. </el-form-item>
  29. <el-form-item>
  30. <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  31. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  32. </el-form-item>
  33. </el-form>
  34. <el-row :gutter="10" class="mb8">
  35. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  36. </el-row>
  37. <el-table v-loading="loading" :data="periodList" @selection-change="handleSelectionChange">
  38. <el-table-column type="selection" width="55" align="center"/>
  39. <el-table-column label="营期名称" align="center" prop="periodName"/>
  40. <el-table-column label="营期状态" align="center" prop="periodStatus" width="100"
  41. :formatter="periodStatusFormatter"/>
  42. <el-table-column label="开营开始时间" align="center" prop="periodStartingTime" width="180"/>
  43. <el-table-column label="开营结束时间" align="center" prop="periodEndTime" width="180"/>
  44. <el-table-column label="创建时间" align="center" prop="createTime" width="180"/>
  45. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  46. <template slot-scope="scope">
  47. <el-button
  48. size="mini"
  49. type="text"
  50. icon="el-icon-edit"
  51. @click="handleUpdate(scope.row)"
  52. v-hasPermi="['course:period:edit']"
  53. >详情
  54. </el-button>
  55. <el-button
  56. size="mini"
  57. type="text"
  58. icon="el-icon-setting"
  59. @click="handlePeriodSettings(scope.row)"
  60. >营期相关设置
  61. </el-button>
  62. <!-- 根据 isNeedRegisterMember 动态显示按钮 -->
  63. <el-button
  64. v-if="scope.row.isNeedRegisterMember === 0"
  65. size="mini"
  66. type="text"
  67. icon="el-icon-open"
  68. @click="handlePeriodUser(scope.row, 1)"
  69. >开启单独注册会员
  70. </el-button>
  71. <el-button
  72. v-else-if="scope.row.isNeedRegisterMember === 1"
  73. size="mini"
  74. type="text"
  75. icon="el-icon-turn-off"
  76. @click="handlePeriodUser(scope.row, 0)"
  77. >关闭单独注册会员
  78. </el-button>
  79. <el-button
  80. v-else
  81. size="mini"
  82. type="text"
  83. icon="el-icon-open"
  84. @click="handlePeriodUser(scope.row, 1)"
  85. >开启单独注册会员
  86. </el-button>
  87. </template>
  88. </el-table-column>
  89. </el-table>
  90. <pagination
  91. v-show="total>0"
  92. :total="total"
  93. :page.sync="queryParams.pageNum"
  94. :limit.sync="queryParams.pageSize"
  95. @pagination="getList"
  96. />
  97. <!-- 添加或修改会员营期对话框-->
  98. <el-drawer :title="title" :visible.sync="open" size="700px" append-to-body>
  99. <el-form ref="form" :model="form" :rules="rules" label-width="80px">
  100. <el-form-item label="营期名称" prop="periodName">
  101. <el-input disabled v-model="form.periodName" placeholder="请输入营期名称"/>
  102. </el-form-item>
  103. <el-form-item label="课程风格" prop="courseStyle">
  104. <image-upload disabled v-model="form.courseStyle" :limit="1"/>
  105. </el-form-item>
  106. <el-form-item label="直播间风格" prop="liveRoomStyle">
  107. <image-upload disabled v-model="form.liveRoomStyle" :limit="1"/>
  108. </el-form-item>
  109. <el-form-item label="红包发放方式" prop="redPacketGrantMethod">
  110. <el-radio-group disabled v-model="form.redPacketGrantMethod">
  111. <el-radio :label="1">按课程</el-radio>
  112. <el-radio :label="2">按营期</el-radio>
  113. </el-radio-group>
  114. </el-form-item>
  115. <el-form-item label="营期类型" prop="periodType">
  116. <el-radio-group disabled v-model="form.periodType">
  117. <el-radio :label="1">多课程</el-radio>
  118. <el-radio :label="2">单课程</el-radio>
  119. </el-radio-group>
  120. </el-form-item>
  121. <el-form-item label="开营日期" prop="periodStartingTime">
  122. <el-date-picker
  123. disabled
  124. :style="{display: form.periodType == 1 ? '' : 'none !important'}"
  125. v-model="form.dateRange"
  126. @change="timeChange(1)"
  127. type="daterange"
  128. range-separator="至"
  129. start-placeholder="开始日期"
  130. end-placeholder="结束日期"
  131. value-format="yyyy-MM-dd">
  132. </el-date-picker>
  133. <el-date-picker
  134. disabled
  135. :style="{display: form.periodType == 2 ? '' : 'none !important'}"
  136. @change="timeChange(2)"
  137. v-model="form.date"
  138. type="date"
  139. value-format="yyyy-MM-dd"
  140. placeholder="选择日期">
  141. </el-date-picker>
  142. </el-form-item>
  143. </el-form>
  144. </el-drawer>
  145. <!-- 添加训练营对话框 -->
  146. <el-dialog :title="campForm.trainingCampId ? '修改训练营' : '新建训练营'" :visible.sync="campDialogVisible"
  147. width="500px" append-to-body>
  148. <el-form ref="campForm" :model="campForm" :rules="campRules" label-width="100px">
  149. <el-form-item label="训练营名称" prop="trainingCampName">
  150. <el-input v-model="campForm.trainingCampName" placeholder="请输入训练营名称"/>
  151. </el-form-item>
  152. </el-form>
  153. <div slot="footer" class="dialog-footer">
  154. <el-button type="primary" @click="submitCampForm">确 定</el-button>
  155. <el-button @click="cancelCampForm">取 消</el-button>
  156. </div>
  157. </el-dialog>
  158. <!-- 添加课程对话框-->
  159. <el-dialog title="添加课程" :visible.sync="course.addOpen" width="500px" append-to-body>
  160. <el-form ref="courseAddForm" :model="course.form" label-width="100px">
  161. <el-form-item label="课程" prop="courseId">
  162. <el-select filterable v-model="course.form.courseId" placeholder="请选择课程" clearable size="small"
  163. @change="courseChange(course.form.courseId)" style="width: 100%" :value-key="'dictValue'">
  164. <el-option
  165. v-for="dict in courseList"
  166. :key="dict.dictValue"
  167. :label="dict.dictLabel"
  168. :value="parseInt(dict.dictValue)"
  169. />
  170. </el-select>
  171. </el-form-item>
  172. <el-form-item label="小节" prop="videoId">
  173. <el-select filterable v-model="course.form.videoIds" placeholder="请选择小节"
  174. :multiple-limit="getDiff(course.row.periodStartingTime, course.row.periodEndTime) - course.total"
  175. multiple clearable size="small" style="width: 100%" :value-key="'dictValue'">
  176. <el-option
  177. v-for="dict in videoList"
  178. :key="dict.dictValue"
  179. :label="dict.dictLabel"
  180. :value="parseInt(dict.dictValue)"
  181. />
  182. </el-select>
  183. </el-form-item>
  184. </el-form>
  185. <div slot="footer" class="dialog-footer">
  186. <el-button type="primary" @click="submitCourseForm">确 定</el-button>
  187. <el-button @click="closeAddCourse">取 消</el-button>
  188. </div>
  189. </el-dialog>
  190. <el-dialog title="修改看课时间" :visible.sync="updateCourse.open" width="500px" append-to-body>
  191. <el-form ref="courseUpdateForm" :model="updateCourse.form" label-width="100px">
  192. <el-form-item label="看课时间" prop="timeRange">
  193. <el-time-picker
  194. is-range
  195. v-model="updateCourse.form.timeRange"
  196. range-separator="至"
  197. start-placeholder="开始时间"
  198. value-format="HH:mm:ss"
  199. end-placeholder="结束时间"
  200. placeholder="选择时间范围">
  201. </el-time-picker>
  202. </el-form-item>
  203. <el-form-item label="领取红包时间" prop="lastJoinTime">
  204. <el-time-picker
  205. v-model="updateCourse.form.joinTime"
  206. :selectableRange="updateCourse.form.timeRange"
  207. value-format="HH:mm:ss"
  208. placeholder="选择时间范围">
  209. </el-time-picker>
  210. <p style="color: red;margin: 0;font-size: 12px">超过领取红包时间,只允许看课,不允许领取红包</p>
  211. </el-form-item>
  212. </el-form>
  213. <div slot="footer" class="dialog-footer">
  214. <el-button type="primary" @click="submitUpdateCourseForm">确 定</el-button>
  215. <el-button @click="closeUpdateCourse">取 消</el-button>
  216. </div>
  217. </el-dialog>
  218. <el-dialog title="修改营期时间" :visible.sync="updateDateOpen" width="500px" append-to-body>
  219. <el-form ref="courseUpdateForm" :model="form" label-width="100px">
  220. <el-form-item label="营期时间" prop="dayDate">
  221. <el-date-picker
  222. v-model="form.dayDate"
  223. :selectableRange="form.dayDate"
  224. value-format="yyyy-MM-dd"
  225. type="date"
  226. placeholder="选择时间">
  227. </el-date-picker>
  228. </el-form-item>
  229. </el-form>
  230. <div slot="footer" class="dialog-footer">
  231. <el-button type="primary" @click="updateDate">确 定</el-button>
  232. <el-button @click="updateDateOpen = false">取 消</el-button>
  233. </div>
  234. </el-dialog>
  235. <!-- 营期相关设置抽屉 -->
  236. <el-drawer
  237. title="营期相关设置"
  238. :visible.sync="periodSettingsVisible"
  239. direction="rtl"
  240. size="70%"
  241. :destroy-on-close="true"
  242. append-to-body
  243. custom-class="period-settings-drawer"
  244. >
  245. <div class="drawer-content" style="margin-left: 25px">
  246. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  247. <el-tab-pane label="课程管理" name="course">
  248. <el-table v-loading="course.loading" :data="course.list" @selection-change="handleSelectionCourseChange">
  249. <el-table-column label="课程" align="center" prop="courseName" width="180"/>
  250. <el-table-column label="小节" align="center" prop="videoName"/>
  251. <el-table-column label="营期时间" align="center" prop="dayDate"/>
  252. <el-table-column label="开始时间" align="center" prop="startDateTime" width="100">
  253. <template slot-scope="scope">
  254. <el-tag>{{ parseTime(scope.row.startDateTime, '{h}:{i}:{s}') }}</el-tag>
  255. </template>
  256. </el-table-column>
  257. <el-table-column label="结束时间" align="center" prop="endDateTime" width="100">
  258. <template slot-scope="scope">
  259. <el-tag type="success">{{ parseTime(scope.row.endDateTime, '{h}:{i}:{s}') }}</el-tag>
  260. </template>
  261. </el-table-column>
  262. <el-table-column label="领取红包时间" align="center" prop="lastJoinTime" width="100">
  263. <template slot-scope="scope">
  264. <el-tag type="danger">{{ parseTime(scope.row.lastJoinTime, '{h}:{i}:{s}') }}</el-tag>
  265. </template>
  266. </el-table-column>
  267. </el-table>
  268. </el-tab-pane>
  269. <el-tab-pane label="课程统计" name="statistics">
  270. <course-statistics
  271. :periodId="periodSettingsData.periodId"
  272. :active="activeTab === 'statistics'"
  273. />
  274. </el-tab-pane>
  275. </el-tabs>
  276. </div>
  277. </el-drawer>
  278. <batch-red-packet
  279. :visible.sync="batchRedPacketVisible"
  280. :selected-data="selectedPeriods"
  281. @success="handleBatchRedPacketSuccess"
  282. />
  283. </div>
  284. </template>
  285. <script>
  286. import {
  287. addPeriod,
  288. delPeriod,
  289. exportPeriod,
  290. getPeriod,
  291. pagePeriod,
  292. updatePeriod,
  293. updatePeriodIsNeedRegisterMember,
  294. getDays,
  295. addCourse,
  296. updateCourseTime,
  297. updateCourseDate,
  298. updateListCourseData,
  299. periodCourseMove,
  300. closePeriod
  301. } from "@/api/course/userCoursePeriod";
  302. import {getCompanyList} from "@/api/company/company";
  303. import {courseList, videoList} from '@/api/course/courseRedPacketLog'
  304. import RedPacket from './redPacket.vue'
  305. import BatchRedPacket from './batchRedPacket.vue'
  306. import CourseStatistics from './statistics.vue'
  307. export default {
  308. name: "Period",
  309. components: {
  310. RedPacket,
  311. BatchRedPacket,
  312. CourseStatistics
  313. },
  314. data() {
  315. return {
  316. // 遮罩层
  317. loading: true,
  318. updateDateOpen: false,
  319. // 左侧遮罩层
  320. leftLoading: true,
  321. // 选中数组
  322. ids: [],
  323. // 非单个禁用
  324. single: true,
  325. // 非多个禁用
  326. multiple: true,
  327. // 显示搜索条件
  328. showSearch: true,
  329. // 总条数
  330. total: 0,
  331. // 左侧总条数
  332. leftTotal: 0,
  333. // 会员营期表格数据
  334. periodList: [],
  335. // 左侧列表数据
  336. leftList: [],
  337. videoList: [],
  338. // 弹出层标题
  339. title: "",
  340. isDisabledDateRange: false, //是否禁用开营日期
  341. // 是否显示弹出层
  342. open: false,
  343. // 查询参数
  344. queryParams: {
  345. pageNum: 1,
  346. pageSize: 10,
  347. periodName: null,
  348. periodStartingTime: null,
  349. periodEndTime: null,
  350. companyIdList: []
  351. },
  352. // 左侧查询参数
  353. leftQueryParams: {
  354. pageNum: 1,
  355. pageSize: 10,
  356. hasNextPage: false,
  357. scs: 'order_number(desc),training_camp_id(desc)',
  358. trainingCampName: null
  359. },
  360. // 表单参数
  361. form: {},
  362. course: {
  363. open: false,
  364. row: {},
  365. list: [],
  366. queryParams: {
  367. pageNum: 1,
  368. pageSize: 10,
  369. },
  370. loading: true,
  371. total: 0,
  372. addOpen: false,
  373. form: {},
  374. },
  375. updateCourse: {
  376. open: false,
  377. loading: true,
  378. ids: [],
  379. form: {},
  380. },
  381. // 表单校验
  382. rules: {},
  383. // 公司选项
  384. companyOptions: [],
  385. // 训练营列表
  386. campList: [],
  387. // 激活的训练营索引
  388. activeCampIndex: null,
  389. // 训练营对话框是否显示
  390. campDialogVisible: false,
  391. courseList: false,
  392. // 训练营表单
  393. campForm: {
  394. trainingCampId: null,
  395. trainingCampName: ''
  396. },
  397. // 训练营表单校验
  398. campRules: {
  399. trainingCampName: [
  400. {required: true, message: '训练营名称不能为空', trigger: 'blur'},
  401. {min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur'}
  402. ]
  403. },
  404. // 滚动节流标志
  405. scrollThrottle: false,
  406. // 加载更多状态
  407. loadingMore: false,
  408. // 设置红包对话框
  409. redPacketVisible: false,
  410. periodCompanyList: [],
  411. currentRedPacketData: {
  412. periodId: '',
  413. videoId: ''
  414. },
  415. // 营期相关设置抽屉
  416. periodSettingsVisible: false,
  417. activeTab: 'course',
  418. periodSettingsData: {},
  419. companyList: [],
  420. courseDialogVisible: false,
  421. redPacketList: [],
  422. currentCompany: null,
  423. // 选中的营期数据
  424. selectedPeriods: [],
  425. // 批量设置红包按钮是否禁用
  426. batchSetRedPacketDisabled: true,
  427. // 批量设置红包弹出框
  428. batchRedPacketVisible: false,
  429. };
  430. },
  431. created() {
  432. courseList().then(response => {
  433. this.courseList = response.list;
  434. });
  435. this.getCompanyList();
  436. this.getList();
  437. },
  438. methods: {
  439. /** 查询会员营期列表 */
  440. getList() {
  441. this.loading = true;
  442. const params = {...this.queryParams};
  443. pagePeriod(params).then(response => {
  444. this.periodList = response.rows;
  445. this.total = response.total;
  446. this.loading = false;
  447. });
  448. },
  449. /** 查询左侧列表 */
  450. getLeftList() {
  451. this.leftLoading = true;
  452. // 重置页码和加载更多状态
  453. this.leftQueryParams.pageNum = 1;
  454. this.loadingMore = false;
  455. // 训练营数据
  456. listCamp(this.leftQueryParams).then(response => {
  457. if (response && response.code === 200) {
  458. this.campList = response.data.list || [];
  459. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  460. this.activeCampIndex = this.campList.length > 0 ? 0 : null;
  461. this.selectCamp(this.activeCampIndex);
  462. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  463. this.$nextTick(() => {
  464. const scrollEl = this.$refs.campList;
  465. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  466. this.loadMoreCamps();
  467. }
  468. });
  469. } else {
  470. this.$message.error(response.msg || '获取训练营列表失败');
  471. this.campList = [];
  472. this.leftQueryParams.hasNextPage = false;
  473. }
  474. this.leftLoading = false;
  475. }).catch(error => {
  476. console.error('获取训练营列表失败:', error);
  477. this.$message.error('获取训练营列表失败');
  478. this.campList = [];
  479. this.leftQueryParams.hasNextPage = false;
  480. this.leftLoading = false;
  481. });
  482. },
  483. /** 搜索按钮操作 */
  484. handleQuery() {
  485. this.queryParams.pageNum = 1;
  486. this.getList();
  487. },
  488. /** 左侧搜索按钮操作 */
  489. handleLeftQuery() {
  490. // 重置页码和列表
  491. this.leftQueryParams.pageNum = 1;
  492. this.campList = [];
  493. this.getLeftList();
  494. },
  495. /** 重置按钮操作 */
  496. resetQuery() {
  497. this.resetForm("queryForm");
  498. this.queryParams.companyIdList = [];
  499. this.handleQuery();
  500. },
  501. /** 多选框选中数据 */
  502. handleSelectionChange(selection) {
  503. this.ids = selection.map(item => item.periodId)
  504. this.single = selection.length !== 1
  505. this.multiple = !selection.length
  506. // 更新批量设置红包相关数据
  507. this.selectedPeriods = selection;
  508. this.batchSetRedPacketDisabled = selection.length === 0;
  509. },
  510. handleSelectionCourseChange(selection) {
  511. this.updateCourse.ids = selection.map(item => item.id)
  512. },
  513. /** 新增按钮操作 */
  514. handleAdd() {
  515. this.reset();
  516. this.open = true;
  517. this.title = "添加会员营期";
  518. this.isDisabledDateRange = false;
  519. },
  520. /** 修改按钮操作 */
  521. handleUpdate(row) {
  522. this.reset();
  523. const periodId = row.periodId || this.ids
  524. getPeriod(periodId).then(response => {
  525. this.form = response.data;
  526. if (this.form.companyId) {
  527. this.form.companyId = this.form.companyId.split(',').map(id => Number(id));
  528. }
  529. // 设置看课时间范围(回显)
  530. if (this.form.viewStartTime && this.form.viewEndTime) {
  531. this.form.timeRange = [this.form.viewStartTime, this.form.viewEndTime];
  532. }
  533. if (this.form.periodType == 1) {
  534. this.form.dateRange = [this.form.periodStartingTime, this.form.periodEndTime];
  535. }
  536. if (this.form.periodType == 1) {
  537. this.form.date = this.form.periodStartingTime;
  538. }
  539. this.open = true;
  540. this.title = "修改会员营期";
  541. this.isDisabledDateRange = true;
  542. });
  543. },
  544. /** 处理单独注册会员开关 */
  545. handlePeriodUser(data, open) {
  546. const actionText = open === 1 ? '开启' : '关闭';
  547. this.$confirm(`确定要${actionText}【${data.periodName}】的单独注册会员功能吗?`, '提示', {
  548. confirmButtonText: '确定',
  549. cancelButtonText: '取消',
  550. type: 'warning'
  551. }).then(() => {
  552. var data1 = {
  553. periodId:data.periodId,
  554. isNeedRegisterMember:open,
  555. }
  556. updatePeriodIsNeedRegisterMember(data1).then(response => {
  557. if (response.code === 200) {
  558. this.msgSuccess(`${actionText}成功`);
  559. this.getList();
  560. } else {
  561. this.msgError(response.msg || `${actionText}失败`);
  562. }
  563. }).catch(error => {
  564. console.error(`${actionText}单独注册会员失败:`, error);
  565. this.msgError(`${actionText}失败`);
  566. });
  567. }).catch(() => {
  568. this.$message.info('已取消操作');
  569. });
  570. },
  571. /** 提交按钮 */
  572. submitForm() {
  573. this.$refs["form"].validate(valid => {
  574. if (valid) {
  575. let data = JSON.parse(JSON.stringify(this.form));
  576. // 处理看课时间范围
  577. if (data.timeRange && data.timeRange.length === 2) {
  578. data.viewStartTime = data.timeRange[0];
  579. data.viewEndTime = data.timeRange[1];
  580. }
  581. data.companyId = data.companyId.join()
  582. data.trainingCampId = this.queryParams.trainingCampId
  583. if (data.periodId != null) {
  584. updatePeriod(data).then(response => {
  585. if (response.code === 200) {
  586. this.msgSuccess("修改成功");
  587. this.open = false;
  588. this.getList();
  589. }
  590. });
  591. } else {
  592. addPeriod(data).then(response => {
  593. if (response.code === 200) {
  594. this.msgSuccess("新增成功");
  595. this.open = false;
  596. this.getList();
  597. }
  598. });
  599. }
  600. }
  601. });
  602. },
  603. /** 删除按钮操作 */
  604. handleDelete(row) {
  605. //添加删除判断,只能删除未开始的营期
  606. console.log(row.periodStatus)
  607. if (row.periodStatus !== 1) {
  608. this.$message.error('营期处于进行中或者结束,不能删除');
  609. return;
  610. }
  611. const periodIds = row.periodId || this.ids;
  612. this.$confirm('是否确认删除该营期?', "警告", {
  613. confirmButtonText: "确定",
  614. cancelButtonText: "取消",
  615. type: "warning"
  616. }).then(function () {
  617. return delPeriod(periodIds);
  618. }).then(() => {
  619. this.getList();
  620. this.msgSuccess("删除成功");
  621. }).catch(function () {
  622. });
  623. },
  624. /** 导出按钮操作 */
  625. handleExport() {
  626. const queryParams = this.queryParams;
  627. this.$confirm('是否确认导出所有会员营期数据项?', "警告", {
  628. confirmButtonText: "确定",
  629. cancelButtonText: "取消",
  630. type: "warning"
  631. }).then(function () {
  632. return exportPeriod(queryParams);
  633. }).then(response => {
  634. this.download(response.msg);
  635. }).catch(function () {
  636. });
  637. },
  638. /** 批量设置红包 */
  639. handleBatchSetRedPacket() {
  640. if (this.selectedPeriods.length === 0) {
  641. this.$message.warning('请至少选择一个营期');
  642. return;
  643. }
  644. this.batchRedPacketVisible = true;
  645. },
  646. /** 获取公司下拉列表*/
  647. getCompanyList() {
  648. this.loading = true;
  649. getCompanyList().then(response => {
  650. this.companyOptions = response.data;
  651. this.loading = false;
  652. });
  653. },
  654. // 取消按钮
  655. cancel() {
  656. this.open = false;
  657. this.reset();
  658. },
  659. // 表单重置
  660. reset() {
  661. this.form = {
  662. periodId: null,
  663. periodName: null,
  664. companyId: null,
  665. courseId: null,
  666. videoId: null,
  667. trainingCampId: null,
  668. createTime: null,
  669. updateTime: null,
  670. courseStyle: null,
  671. liveRoomStyle: null,
  672. redPacketGrantMethod: 1,
  673. periodType: 1,
  674. periodStartingTime: null,
  675. dateRange: [],
  676. date: null,
  677. days: [],
  678. periodEndTime: null,
  679. timeRange: [], // 看课时间范围
  680. viewStartTime: null, // 看课开始时间
  681. viewEndTime: null, // 看课结束时间
  682. lastJoinTime: null // 领取红包时间
  683. };
  684. this.resetForm("form");
  685. },
  686. // 处理训练营列表的逻辑
  687. handleDeleteCamp(item) {
  688. this.$confirm(`确定要删除训练营"${item.trainingCampName}"吗?`, '提示', {
  689. confirmButtonText: '删除',
  690. cancelButtonText: '取消',
  691. type: 'warning'
  692. }).then(() => {
  693. // 调用删除训练营API
  694. const trainingCampId = item.trainingCampId;
  695. this.leftLoading = true;
  696. delCamp(trainingCampId).then(response => {
  697. if (response.code === 200) {
  698. this.$message.success('删除成功');
  699. // 从列表中移除
  700. const index = this.campList.findIndex(camp => camp.trainingCampId === trainingCampId);
  701. if (index !== -1) {
  702. this.campList.splice(index, 1);
  703. }
  704. // 如果删除的是当前选中的训练营,则重置选中状态
  705. if (this.activeCampIndex === index) {
  706. this.activeCampIndex = this.campList.length > 0 ? 0 : null;
  707. if (this.activeCampIndex !== null) {
  708. // 更新右侧列表
  709. this.selectCamp(this.activeCampIndex);
  710. } else {
  711. // 没有训练营了,清空右侧列表
  712. this.periodList = [];
  713. }
  714. }
  715. } else {
  716. this.$message.error(response.msg || '删除失败');
  717. }
  718. this.leftLoading = false;
  719. }).catch(error => {
  720. this.$message.error('删除失败: ' + error.message);
  721. this.leftLoading = false;
  722. });
  723. }).catch(() => {
  724. this.$message.info('已取消删除');
  725. });
  726. },
  727. /** 复制训练营 */
  728. handleCopyCamp(item) {
  729. // 调用添加训练营API
  730. copyCamp(item.trainingCampId).then(response => {
  731. if (response.code === 200) {
  732. this.$message.success('复制成功');
  733. // 重新加载训练营列表
  734. this.getLeftList();
  735. } else {
  736. this.$message.error(response.msg || '复制训练营失败');
  737. }
  738. }).catch(error => {
  739. this.$message.error('复制训练营失败: ' + error.message);
  740. });
  741. },
  742. /** 修改训练营按钮操作 */
  743. handleEditCamp(item) {
  744. this.resetCampForm();
  745. this.leftLoading = true;
  746. // 获取最新的训练营数据
  747. const trainingCampId = item.trainingCampId;
  748. // 应该调用获取训练营详情的API
  749. setTimeout(() => {
  750. // 填充表单数据
  751. this.campForm = {
  752. trainingCampId: item.trainingCampId,
  753. trainingCampName: item.trainingCampName,
  754. orderNumber: item.orderNumber || 1,
  755. status: item.status !== undefined ? item.status : 1
  756. };
  757. this.campDialogVisible = true;
  758. this.leftLoading = false;
  759. }, 300);
  760. },
  761. /** 新建训练营按钮操作 */
  762. handleAddTrainingCamp() {
  763. this.resetCampForm();
  764. this.campDialogVisible = true;
  765. },
  766. /** 重置训练营表单 */
  767. resetCampForm() {
  768. this.campForm = {
  769. trainingCampId: null,
  770. trainingCampName: ''
  771. };
  772. // 如果表单已经创建,则重置校验结果
  773. if (this.$refs.campForm) {
  774. this.$refs.campForm.resetFields();
  775. }
  776. },
  777. /** 取消训练营表单 */
  778. cancelCampForm() {
  779. this.campDialogVisible = false;
  780. this.resetCampForm();
  781. },
  782. /** 提交训练营表单 */
  783. submitCampForm() {
  784. this.$refs.campForm.validate(valid => {
  785. if (valid) {
  786. // 显示加载中
  787. this.leftLoading = true;
  788. // 准备提交的数据
  789. const submitData = JSON.parse(JSON.stringify(this.campForm));
  790. // 判断是新增还是修改
  791. if (submitData.trainingCampId) {
  792. // 修改训练营
  793. editCamp(submitData).then(response => {
  794. if (response.code === 200) {
  795. this.$message.success('修改训练营成功');
  796. this.campDialogVisible = false;
  797. // 更新列表中的数据
  798. const index = this.campList.findIndex(camp => camp.trainingCampId === submitData.trainingCampId);
  799. if (index !== -1) {
  800. this.campList[index] = {...this.campList[index], ...submitData};
  801. // 如果修改的是当前选中的训练营,更新右侧列表
  802. if (this.activeCampIndex === index) {
  803. this.selectCamp(index);
  804. }
  805. }
  806. // 重新加载训练营列表
  807. this.getLeftList();
  808. } else {
  809. this.$message.error(response.msg || '修改训练营失败');
  810. this.leftLoading = false;
  811. }
  812. }).catch(error => {
  813. this.$message.error('修改训练营失败: ' + (error.message || '未知错误'));
  814. this.leftLoading = false;
  815. });
  816. } else {
  817. // 新增训练营
  818. addCamp(submitData).then(response => {
  819. if (response.code === 200) {
  820. this.$message.success('新建训练营成功');
  821. this.campDialogVisible = false;
  822. // 重新加载训练营列表
  823. this.getLeftList();
  824. } else {
  825. this.$message.error(response.msg || '新建训练营失败');
  826. this.leftLoading = false;
  827. }
  828. }).catch(error => {
  829. this.$message.error('新建训练营失败: ' + (error.message || '未知错误'));
  830. this.leftLoading = false;
  831. });
  832. }
  833. }
  834. });
  835. },
  836. /** 排序方式改变 */
  837. handleSortChange(value) {
  838. this.leftQueryParams.scs = value;
  839. // 重置页码和列表
  840. this.leftQueryParams.pageNum = 1;
  841. this.campList = [];
  842. this.getLeftList();
  843. },
  844. /** 选中训练营 */
  845. selectCamp(index) {
  846. if (index == null || index == undefined) return;
  847. this.activeCampIndex = index;
  848. // 加载对应的训练营营期数据
  849. const selectedCamp = this.campList[index];
  850. this.queryParams.trainingCampId = selectedCamp.trainingCampId;
  851. this.getList();
  852. },
  853. /** 处理滚动事件,实现滚动到底部加载更多 */
  854. handleScroll() {
  855. // 如果正在节流中或者正在加载中,则不处理
  856. if (this.scrollThrottle || this.loadingMore) return;
  857. // 设置节流,200ms内不再处理滚动事件
  858. this.scrollThrottle = true;
  859. setTimeout(() => {
  860. this.scrollThrottle = false;
  861. }, 200);
  862. const scrollEl = this.$refs.campList;
  863. if (!scrollEl) return;
  864. // 判断是否滚动到底部:滚动高度 + 可视高度 >= 总高度 - 30(添加30px的容差,提前触发加载)
  865. const isBottom = scrollEl.scrollTop + scrollEl.clientHeight >= scrollEl.scrollHeight - 30;
  866. // 如果滚动到底部,且有下一页数据,且当前不在加载中,则加载更多
  867. if (isBottom && this.leftQueryParams.hasNextPage && !this.leftLoading && !this.loadingMore) {
  868. this.loadMoreCamps();
  869. }
  870. },
  871. /** 加载更多训练营数据 */
  872. loadMoreCamps() {
  873. // 已在加载中,防止重复加载
  874. if (this.leftLoading || this.loadingMore) return;
  875. // 设置加载状态
  876. this.loadingMore = true;
  877. // 页码加1
  878. this.leftQueryParams.pageNum += 1;
  879. // 加载下一页数据
  880. listCamp(this.leftQueryParams).then(response => {
  881. if (response && response.code === 200) {
  882. // 将新数据追加到列表中
  883. const newList = response.data.list || [];
  884. if (newList.length > 0) {
  885. this.campList = [...this.campList, ...newList];
  886. }
  887. // 更新是否有下一页的标志
  888. this.leftQueryParams.hasNextPage = response.data.hasNextPage;
  889. // 如果当前显示的列表高度不足以触发滚动,但还有更多数据,自动加载下一页
  890. this.$nextTick(() => {
  891. const scrollEl = this.$refs.campList;
  892. if (scrollEl && this.leftQueryParams.hasNextPage && scrollEl.scrollHeight <= scrollEl.clientHeight) {
  893. // 延迟一点再加载下一页,避免过快加载
  894. setTimeout(() => {
  895. this.loadMoreCamps();
  896. }, 300);
  897. }
  898. });
  899. } else {
  900. this.$message.error(response.msg || '加载更多训练营失败');
  901. }
  902. this.loadingMore = false;
  903. }).catch(error => {
  904. console.error('加载更多训练营失败:', error);
  905. this.$message.error('加载更多训练营失败');
  906. this.loadingMore = false;
  907. });
  908. },
  909. timeChange(type) {
  910. if (type == 1) {
  911. this.form.periodStartingTime = this.form.dateRange[0];
  912. this.form.periodEndTime = this.form.dateRange[1];
  913. // 转换为天数
  914. let days = this.getDiff(this.form.periodStartingTime, this.form.periodEndTime);
  915. for (let i = 0; i < days; i++) {
  916. this.form.days.push({lesson: i + 1});
  917. }
  918. }
  919. if (type == 2) {
  920. this.form.periodStartingTime = this.form.date;
  921. this.form.periodEndTime = this.form.date;
  922. }
  923. },
  924. getDiff(start, end) {
  925. if (start == null || start == undefined || start == '') return 0;
  926. if (end == null || end == undefined || end == '') return 0;
  927. if (start == end) 1;
  928. const startDate = this.getUTCDate(start);
  929. const endDate = this.getUTCDate(end);
  930. const timeDiff = endDate - startDate;
  931. return (Math.floor(timeDiff / (1000 * 3600 * 24))) + 1; // 直接取整
  932. },
  933. getUTCDate(dateStr) {
  934. const [year, month, day] = dateStr.split('-').map(Number);
  935. return new Date(Date.UTC(year, month - 1, day)); // 月份从0开始
  936. },
  937. handleCourse(row) {
  938. this.course = {
  939. open: false,
  940. row: {},
  941. list: [],
  942. queryParams: {
  943. pageNum: 1,
  944. pageSize: 9999,
  945. },
  946. loading: true,
  947. total: 0,
  948. addOpen: false,
  949. form: {},
  950. };
  951. this.course.open = true;
  952. this.course.row = row;
  953. this.course.queryParams.periodId = row.periodId;
  954. this.getCourseList();
  955. },
  956. getCourseList() {
  957. this.course.loading = true;
  958. getDays(this.course.queryParams).then(e => {
  959. this.course.list = e.rows;
  960. this.course.total = e.total;
  961. this.course.loading = false;
  962. });
  963. },
  964. handleAddCourse() {
  965. this.course.addOpen = true;
  966. this.course.form = {
  967. periodId: this.course.queryParams.periodId,
  968. courseId: null,
  969. videoIds: []
  970. };
  971. // 重置表单
  972. this.$nextTick(() => {
  973. if (this.$refs.courseAddForm) {
  974. this.$refs.courseAddForm.resetFields();
  975. }
  976. });
  977. },
  978. handleUpdateCourse() {
  979. this.updateCourse.open = true;
  980. this.updateCourse.form = {
  981. ids: this.updateCourse.ids,
  982. joinTime: [],
  983. };
  984. },
  985. closeAddCourse() {
  986. this.course.addOpen = false;
  987. this.course.form = {
  988. periodId: null,
  989. courseId: null,
  990. videoIds: []
  991. };
  992. // 重置表单
  993. if (this.$refs.courseAddForm) {
  994. this.$refs.courseAddForm.resetFields();
  995. }
  996. },
  997. closeUpdateCourse() {
  998. this.course.open = false;
  999. },
  1000. courseChange(row) {
  1001. this.course.form.videoIds = [];
  1002. videoList(row).then(response => {
  1003. this.videoList = response.list
  1004. });
  1005. },
  1006. submitCourseForm() {
  1007. this.$refs.courseAddForm.validate(valid => {
  1008. if (valid) {
  1009. if (this.course.form.timeRange != null && this.course.form.timeRange.length === 2) {
  1010. this.course.form.startTime = this.course.form.timeRange[0];
  1011. this.course.form.endTime1 = this.course.form.timeRange[1];
  1012. }
  1013. // 提交数据
  1014. addCourse(this.course.form).then(response => {
  1015. this.$message.success('添加成功');
  1016. this.course.addOpen = false;
  1017. // 重新加载训练营列表
  1018. this.getCourseList();
  1019. });
  1020. }
  1021. });
  1022. },
  1023. submitUpdateCourseForm() {
  1024. this.$refs.courseUpdateForm.validate(valid => {
  1025. if (valid) {
  1026. if (this.updateCourse.form.timeRange != null && this.updateCourse.form.timeRange.length === 2) {
  1027. this.updateCourse.form.startTime = this.updateCourse.form.timeRange[0];
  1028. this.updateCourse.form.endTime1 = this.updateCourse.form.timeRange[1];
  1029. }
  1030. // 提交数据
  1031. updateCourseTime(this.updateCourse.form).then(response => {
  1032. this.$message.success('添加成功');
  1033. this.updateCourse.open = false;
  1034. // 重新加载训练营列表
  1035. this.getCourseList();
  1036. });
  1037. }
  1038. });
  1039. },
  1040. updateDate() {
  1041. updateCourseDate(this.form).then(response => {
  1042. this.$message.success('修改成功');
  1043. this.updateDateOpen = false;
  1044. // 重新加载训练营列表
  1045. this.getCourseList();
  1046. });
  1047. },
  1048. saveCourseData() {
  1049. updateListCourseData(this.course.list).then(response => {
  1050. this.$message.success('保存成功');
  1051. this.getCourseList();
  1052. });
  1053. },
  1054. setRedPacket(row) {
  1055. this.currentRedPacketData = {
  1056. periodId: row.periodId
  1057. // videoId: row.videoId
  1058. };
  1059. this.redPacketVisible = true;
  1060. },
  1061. handleRedPacketSuccess() {
  1062. this.getCourseList();
  1063. },
  1064. handlePeriodSettings(row) {
  1065. this.periodSettingsData = row;
  1066. this.periodSettingsVisible = true;
  1067. // 初始化课程列表
  1068. this.course.queryParams.periodId = row.periodId;
  1069. // 根据当前激活的tab加载对应数据
  1070. this.handleTabClick({name: this.activeTab});
  1071. },
  1072. // 结束营期
  1073. handleClosePeriod(row) {
  1074. const msg = `注: 1.确认结束营期,该营期的开营结束时间改为当天24点。2.当天正在播放中的课程不变。3.第二天如有未开始的课程,统一改为已结束。是否确认结束 ${row.periodName} 营期吗?`
  1075. this.$confirm(msg, "警告", {
  1076. confirmButtonText: "确定",
  1077. cancelButtonText: "取消",
  1078. type: "warning"
  1079. }).then(() => {
  1080. closePeriod({id: row.periodId}).then(response => {
  1081. if (response.code === 200) {
  1082. this.getList()
  1083. } else {
  1084. this.$message.error(response.msg)
  1085. }
  1086. })
  1087. }).catch(() => {
  1088. })
  1089. },
  1090. handleBatchRedPacketSuccess() {
  1091. this.batchRedPacketVisible = false;
  1092. this.getCourseList();
  1093. },
  1094. /** 处理tab切换 */
  1095. handleTabClick(tab) {
  1096. if (tab.name === 'course') {
  1097. this.getCourseList();
  1098. } else if (tab.name === 'company') {
  1099. this.redPacketVisible = true;
  1100. }
  1101. },
  1102. /** 上移课程 */
  1103. handleTop(row) {
  1104. const currentIndex = this.course.list.findIndex(item => item.id === row.id);
  1105. if (currentIndex <= 0) {
  1106. this.$message.warning('已经是第一条数据');
  1107. return;
  1108. }
  1109. // 获取上一条数据
  1110. const prevRow = this.course.list[currentIndex - 1];
  1111. console.log({
  1112. id: row.id,
  1113. targetId: prevRow.id,
  1114. type: 1 //上移
  1115. })
  1116. periodCourseMove({
  1117. id: row.id,
  1118. targetId: prevRow.id,
  1119. type: 1 //上移
  1120. }).then(response => {
  1121. if (response.code === 200) {
  1122. this.$message.success('上移成功');
  1123. this.getCourseList();
  1124. } else {
  1125. this.$message.error(response.msg || '上移失败');
  1126. }
  1127. }).catch(() => {
  1128. this.$message.error('上移失败');
  1129. });
  1130. },
  1131. /** 下移课程 */
  1132. handleBottom(row) {
  1133. const currentIndex = this.course.list.findIndex(item => item.id === row.id);
  1134. if (currentIndex === -1 || currentIndex >= this.course.list.length - 1) {
  1135. this.$message.warning('已经是最后一条数据');
  1136. return;
  1137. }
  1138. // 获取下一条数据
  1139. const nextRow = this.course.list[currentIndex + 1];
  1140. periodCourseMove({
  1141. id: row.id,
  1142. targetId: nextRow.id,
  1143. type: 2 //下移
  1144. }).then(response => {
  1145. if (response.code === 200) {
  1146. this.$message.success('下移成功');
  1147. this.getCourseList(); // 重新加载列表
  1148. } else {
  1149. this.$message.error(response.msg || '下移失败');
  1150. }
  1151. }).catch(() => {
  1152. this.$message.error('下移失败');
  1153. });
  1154. },
  1155. /** 营期状态格式化 */
  1156. periodStatusFormatter(row) {
  1157. const statusMap = {
  1158. 1: '未开始',
  1159. 2: '进行中',
  1160. 3: '已结束'
  1161. };
  1162. return statusMap[row.periodStatus] || '未知状态';
  1163. },
  1164. /** 开课状态格式化 */
  1165. courseStatusFormatter(row) {
  1166. const statusMap = {
  1167. 0: '未开始',
  1168. 1: '进行中',
  1169. 2: '已结束'
  1170. };
  1171. return statusMap[row.status] || '未知状态';
  1172. },
  1173. /** 营期状态格式化 */
  1174. handleUpdateDate(row) {
  1175. this.form = {id: row.id, dayDate: row.dayDate};
  1176. this.updateDateOpen = true;
  1177. },
  1178. },
  1179. };
  1180. </script>
  1181. <style scoped>
  1182. .left-aside {
  1183. background-color: #fff;
  1184. border-right: 1px solid #EBEEF5;
  1185. padding: 0;
  1186. display: flex;
  1187. flex-direction: column;
  1188. height: 800px;
  1189. }
  1190. .left-header {
  1191. padding: 10px;
  1192. border-bottom: 1px solid #EBEEF5;
  1193. }
  1194. .left-header-top {
  1195. display: flex;
  1196. justify-content: space-between;
  1197. align-items: center;
  1198. margin-bottom: 10px;
  1199. }
  1200. .search-btn {
  1201. width: 50%;
  1202. height: 36px;
  1203. background-color: #409EFF;
  1204. color: white;
  1205. border: none;
  1206. }
  1207. .search-input-wrapper {
  1208. margin-bottom: 10px;
  1209. }
  1210. .sort-wrapper {
  1211. display: flex;
  1212. align-items: center;
  1213. margin-bottom: 10px;
  1214. }
  1215. .sort-label {
  1216. width: 70px;
  1217. font-size: 14px;
  1218. font-weight: 600;
  1219. color: #909399;
  1220. }
  1221. .sort-select {
  1222. margin-left: 10px;
  1223. width: 280px;
  1224. }
  1225. .color-wrapper {
  1226. display: flex;
  1227. align-items: center;
  1228. margin-bottom: 10px;
  1229. }
  1230. .color-label {
  1231. width: 70px;
  1232. color: #606266;
  1233. }
  1234. .color-hint {
  1235. margin-left: 10px;
  1236. color: #606266;
  1237. font-size: 12px;
  1238. }
  1239. .color-help {
  1240. margin-left: 5px;
  1241. color: #909399;
  1242. cursor: pointer;
  1243. }
  1244. .hint-text {
  1245. color: #606266;
  1246. font-size: 12px;
  1247. margin-top: 5px;
  1248. }
  1249. .camp-list {
  1250. flex: 1;
  1251. overflow-y: auto;
  1252. padding: 3px;
  1253. }
  1254. .camp-item {
  1255. margin-bottom: 5px;
  1256. padding: 15px;
  1257. background-color: #ffffff;
  1258. position: relative;
  1259. cursor: pointer;
  1260. display: flex;
  1261. justify-content: space-between;
  1262. border: 1px solid #eaedf2;
  1263. }
  1264. .camp-item:last-child {
  1265. margin-bottom: 0;
  1266. }
  1267. .camp-item:hover {
  1268. background-color: #f5f9ff;
  1269. }
  1270. .camp-item.active {
  1271. background-color: #eaf4ff;
  1272. border-left: 1px solid #75b8fc;
  1273. }
  1274. .camp-content {
  1275. flex: 1;
  1276. padding-right: 10px;
  1277. }
  1278. .camp-title {
  1279. font-weight: bold;
  1280. font-size: 16px;
  1281. margin-bottom: 8px;
  1282. color: #333;
  1283. display: flex;
  1284. align-items: center;
  1285. }
  1286. .camp-icon {
  1287. font-size: 16px;
  1288. margin-right: 6px;
  1289. color: #409EFF;
  1290. }
  1291. .camp-info {
  1292. display: flex;
  1293. justify-content: space-between;
  1294. margin-bottom: 8px;
  1295. font-size: 12px;
  1296. color: #c4c1c1;
  1297. line-height: 1.5;
  1298. }
  1299. .camp-stats {
  1300. display: flex;
  1301. justify-content: space-between;
  1302. font-size: 12px;
  1303. color: #666;
  1304. background-color: #f5f9ff;
  1305. padding: 6px 10px;
  1306. border-radius: 4px;
  1307. line-height: 1.5;
  1308. }
  1309. .stat-item {
  1310. display: flex;
  1311. align-items: center;
  1312. }
  1313. .stat-item i {
  1314. margin-right: 4px;
  1315. font-size: 14px;
  1316. color: #409EFF;
  1317. }
  1318. .camp-actions {
  1319. display: flex;
  1320. flex-direction: column;
  1321. justify-content: center;
  1322. align-items: flex-end;
  1323. gap: 8px;
  1324. border-left: 1px dashed #eaedf2;
  1325. padding-left: 12px;
  1326. min-width: 50px;
  1327. }
  1328. .action-btn {
  1329. padding: 2px 5px;
  1330. font-size: 12px;
  1331. border-radius: 4px;
  1332. transition: all 0.2s;
  1333. }
  1334. .action-btn:hover {
  1335. background-color: rgba(255, 255, 255, 0.8);
  1336. }
  1337. .delete-btn {
  1338. color: #f56c6c;
  1339. }
  1340. .delete-btn:hover {
  1341. background-color: rgba(245, 108, 108, 0.1);
  1342. }
  1343. .copy-btn {
  1344. color: #409EFF;
  1345. }
  1346. .copy-btn:hover {
  1347. background-color: rgba(64, 158, 255, 0.1);
  1348. }
  1349. .warning-icon {
  1350. color: #E6A23C;
  1351. font-size: 16px;
  1352. margin-left: 5px;
  1353. }
  1354. .el-main {
  1355. padding: 10px;
  1356. }
  1357. /* 添加训练营表单样式 */
  1358. .drawer-footer {
  1359. position: absolute;
  1360. bottom: 0;
  1361. left: 0;
  1362. right: 0;
  1363. padding: 20px;
  1364. background: #fff;
  1365. text-align: right;
  1366. border-top: 1px solid #e8e8e8;
  1367. }
  1368. .el-input-number {
  1369. width: 100%;
  1370. }
  1371. /* 加载更多样式 */
  1372. .loading-more {
  1373. display: flex;
  1374. align-items: center;
  1375. justify-content: center;
  1376. padding: 12px 0;
  1377. color: #909399;
  1378. font-size: 14px;
  1379. }
  1380. .loading-more i {
  1381. margin-right: 5px;
  1382. font-size: 16px;
  1383. }
  1384. /* 无更多数据提示 */
  1385. .no-more-data {
  1386. display: flex;
  1387. align-items: center;
  1388. justify-content: center;
  1389. padding: 12px 0;
  1390. color: #c0c4cc;
  1391. font-size: 13px;
  1392. }
  1393. .no-more-data span {
  1394. position: relative;
  1395. display: flex;
  1396. align-items: center;
  1397. }
  1398. </style>