index.vue 55 KB

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