bonusPointsPopup.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. <template>
  2. <div>
  3. <!-- 奖扣ab分弹窗 -->
  4. <el-dialog :title="title" :visible.sync="visible" top="3%" :close-on-click-modal="false"
  5. :before-close="closePopup" width="700px" @open="initData()">
  6. <div>
  7. <el-form :model="dialogData" ref="dialogData" label-width="100px" v-loading="loading">
  8. <el-form-item label="奖扣对象" prop="members"
  9. :rules="[{ required: true, message: '请选择奖扣对象', trigger: 'change' }]">
  10. <div class="width_400">
  11. <el-input auto-complete="off" v-model="employeeName" placeholder="请选择奖扣对象"></el-input>
  12. <div @click="noPersonnelListTips"
  13. style=" position: absolute; top: 0; right: 0; left: 0; bottom: 0; z-index: 9;"></div>
  14. </div>
  15. <slot></slot>
  16. </el-form-item>
  17. <div v-for="(item, index) in dialogData.items" :key="index" @click.stop="setIndex(index)">
  18. <div class="title flex-box-ce flex-d-center">
  19. <span class="span">录入明细({{ index + 1 }})</span>
  20. <el-link type="danger" v-show="index > 0 || dialogData.items.length > 1"
  21. @click="delItem(index)">删除</el-link>
  22. </div>
  23. <el-form-item label="指定规则">
  24. <el-switch :disabled="ruleOnoff" @change="switchChange(index, item.rule_switch)"
  25. v-model="item.rule_switch"></el-switch>
  26. </el-form-item>
  27. <el-form-item label="选择分类" v-if="!item.rule_switch"
  28. :prop="'items.' + index + '.rule_list_value'"
  29. :rules="[{ required: true, message: '请选择规则分类', trigger: 'blur' }]">
  30. <el-cascader v-model="item.rule_list_value" :ref="'ruleCascader' + index"
  31. :popper-class="'ruleClass'" filterable class="width_400"
  32. @expand-change="setIndex(index)" clearable :options="rule_list" @change="ruleChange"
  33. :props="{ children: 'child', label: 'name', value: 'id', checkStrictly: true }"></el-cascader>
  34. </el-form-item>
  35. <el-form-item label="选择规则" v-if="item.rule_switch"
  36. :prop="'items.' + index + '.rule_item_list_value'"
  37. :rules="[{ required: true, message: '请选择规则', trigger: 'blur' }]">
  38. <el-cascader v-model="item.rule_item_list_value" ref="ruleItem" class="width_400"
  39. :popper-class="'itemClass'" filterable clearable @expand-change="setIndex(index)"
  40. :options="rule_item_list" @change="ruleItemChange" :show-all-levels="false"
  41. :props="{ children: 'child', label: 'name', value: 'id' }"></el-cascader>
  42. </el-form-item>
  43. <el-form-item>
  44. <div style="line-height: 24px;" v-show="item.rule_item_details.range_type == 1">
  45. {{ item.rule_item_details.min_point }}
  46. <span class="blue">{{ $getTypsName(ptid) }}</span>
  47. </div>
  48. <div style="line-height: 24px;" v-show="item.rule_item_details.range_type == 2">
  49. {{ item.rule_item_details.min_point }} ~ {{ item.rule_item_details.max_point }}
  50. <span class="blue">{{ $getTypsName(ptid) }}</span>
  51. </div>
  52. <div style="line-height: 24px;">{{ item.rule_item_details.name }}</div>
  53. </el-form-item>
  54. <el-form-item label="积分" :prop="'items.' + index + '.point'"
  55. :rules="[{ required: true, message: '请输入分值', trigger: 'blur' }]">
  56. <el-input-number style="width: 220px;" v-if="item.rule_item_details.range_type == 2"
  57. :min="item.min" :max="item.max" v-model.number="item.point"></el-input-number>
  58. <el-input-number style="width: 220px;" v-else
  59. :disabled="item.rule_item_details.range_type == 1"
  60. v-model.number="item.point"></el-input-number>
  61. </el-form-item>
  62. <el-form-item label="发生时间" :prop="'items.' + index + '.event_time'"
  63. :rules="[{ required: true, message: '请选择时间', trigger: 'blur' }]">
  64. <el-date-picker :clearable="false" v-model="item.event_time"
  65. :picker-options="pickerBeginDateBefore" type="date" placeholder="请选择时间"
  66. value-format="yyyy-MM-dd"></el-date-picker>
  67. </el-form-item>
  68. <el-form-item label="发放奖票">
  69. <template slot="label">
  70. <el-tooltip effect="dark" placement="top">
  71. <div slot="content">选择递交审批人时,发放奖票将失效,由审批上级来发放奖票</div>
  72. <span>发放奖票<i style="padding-left: 5px;font-size: 16px;"
  73. class="el-icon-question fontColorD"></i></span>
  74. </el-tooltip>
  75. </template>
  76. <el-switch :disabled="item.approvalName ? true : false" :active-value="1" :inactive-value="0"
  77. v-model="item.ticket_count"></el-switch>
  78. </el-form-item>
  79. <el-form-item label="事件内容" :prop="'items.' + index + '.remark'"
  80. :rules="[{ required: true, message: '请输入事件内容', trigger: 'blur' }, { min: 2, max: 300, message: '长度在 2 到 300 个字符', trigger: 'blur' }]">
  81. <el-input type="textarea" placeholder="请输入事件内容" clearable maxlength="300" show-word-limit
  82. rows="4" v-model="item.remark"></el-input>
  83. <el-button @click="item.remark = ''" type="danger" plain size="mini"
  84. style="position: absolute;right: 0px;bottom: -30px;z-index: 99999;">清空</el-button>
  85. </el-form-item>
  86. <el-form-item label="图片">
  87. <upload :headers="$xtoken" :action="$action" :limit="3" list_type="picture-card"
  88. :accept="$acceptImg" :multiple="true" ref="clearPicture" :show-file-list="true"
  89. :file-list="item.fileList" :on-success="handleFilesSuccess" :on-preview="onFilePreView"
  90. :before-upload="beforeUpload" :on-remove="onFileRemove">
  91. <i class="el-icon-plus"></i>
  92. </upload>
  93. </el-form-item>
  94. <el-form-item label="递交审批" v-if="$getUserData().is_creator == 0">
  95. <div class="width_400">
  96. <el-input auto-complete="off" v-model="item.approvalName"
  97. placeholder="请选择审批人"></el-input>
  98. <div @click="item.show_approval_selector = true"
  99. style=" position: absolute; top: 0; right: 0; left: 0; bottom: 0; z-index: 9;">
  100. </div>
  101. </div>
  102. <el-dialog title="选择人员" width="640px" :visible.sync="item.show_approval_selector"
  103. append-to-body :before-close="handleClose">
  104. <EmployeeSelector v-if="item.show_approval_selector" ref="superior" :multi="false"
  105. :isCreatorSelect="true" :user_no_select="false" :employee_list="superior_list"
  106. :selected="item.approval_selected" @confirm="approval_confirm" />
  107. <span slot="footer" class="dialog-footer">
  108. <el-button @click="item.show_approval_selector = false">取 消</el-button>
  109. <el-button type="primary" @click="submitEmployee('superior')">确 定</el-button>
  110. </span>
  111. </el-dialog>
  112. </el-form-item>
  113. </div>
  114. <el-form-item style="margin-bottom: 0;">
  115. <div>如需录入多条,请点击“增加一条”</div>
  116. </el-form-item>
  117. <el-form-item style="margin-bottom: 0;"><el-button type="primary" plain @click="addItem">+
  118. 增加一条</el-button></el-form-item>
  119. <el-form-item style="text-align: right; margin-bottom: 0;">
  120. <el-button @click="closeDialog('dialogData')" :disabled="btn_loading">取 消</el-button>
  121. <el-button type="primary" @click="subData('dialogData')" :disabled="btn_loading"
  122. :loading="btn_loading">确
  123. 认</el-button>
  124. </el-form-item>
  125. </el-form>
  126. </div>
  127. <el-dialog title="选择人员" width="640px" :visible.sync="show_employee_selector" append-to-body
  128. :before-close="handleClose">
  129. <EmployeeSelector v-if="show_employee_selector" ref="members" :isChecKedAll="false"
  130. :isCreatorSelect="true" :user_no_select="false" :max="30" :employee_list="manage_scope"
  131. :selected="employee_selected" @confirm="move_employee_confirm" />
  132. <span slot="footer" class="dialog-footer">
  133. <el-button @click="show_employee_selector = false">取 消</el-button>
  134. <el-button type="primary" @click="submitMembers()">确 定</el-button>
  135. </span>
  136. </el-dialog>
  137. </el-dialog>
  138. <el-dialog :title="'提交结果'" :visible.sync="error_list_show" :append-to-body="true" width="700px">
  139. <el-table :data="error_list">
  140. <el-table-column prop="target" label="员工"></el-table-column>
  141. <el-table-column prop="point" label="积分">
  142. <template slot-scope="scope">
  143. <span>
  144. {{ scope.row.point }}
  145. <span>{{ integralType == 2 ? 'A分' : 'B分' }}</span>
  146. </span>
  147. </template>
  148. </el-table-column>
  149. <el-table-column prop="status" label="处理状态">
  150. <template slot-scope="scope">
  151. <span :style="'color:' + (scope.row.status == 0 ? '#f70000' : '#47bf47')">{{ scope.row.msg
  152. }}</span>
  153. </template>
  154. </el-table-column>
  155. <el-table-column prop="remark" label="备注信息"></el-table-column>
  156. </el-table>
  157. </el-dialog>
  158. <el-dialog title="提交结果" :visible.sync="isResult" width="800" :close-on-click-modal="false"
  159. :close-on-press-escape="false" :show-close="false">
  160. <div v-if="!isShowError">
  161. <div style="text-align: center;margin-bottom: 10px;" class="red" v-if="isShowError2">{{ errorMsg }}</div>
  162. <el-progress :text-inside="true" :stroke-width="24" :percentage="percentage"></el-progress>
  163. <div class="orange" style="text-align: center;padding-top: 10px;"
  164. v-if="config.event_review_status && config.event_entry_review">复核开启后,积分需管理员复核后才计入排名和统计</div>
  165. <div style="margin-top: 20px;border: 1px solid #f1f1f1;max-height: 500px;overflow-y: auto;"
  166. class="scroll-bar">
  167. <div class="flex-box-ce results" style="font-weight: 600;">
  168. <div style="border-right: 1px solid #f1f1f1;width: 50px;">序号</div>
  169. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">奖扣对象</div>
  170. <div class="flex-2" style="border-right: 1px solid #f1f1f1;">事件内容</div>
  171. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">积分</div>
  172. <div class="flex-2">处理结果</div>
  173. </div>
  174. <div class="flex-box-ce results" v-for="(item, index) in results" :key="index">
  175. <div style="border-right: 1px solid #f1f1f1;width: 50px;">{{ results.length - index }}</div>
  176. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">{{ item.target }}</div>
  177. <div class="flex-2" style="border-right: 1px solid #f1f1f1;">{{ item.source_msg.remark }}</div>
  178. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">
  179. {{ item.point > 0 ? '+' + item.point : item.point }}
  180. <span>{{ item.source_msg.pt_id == 3 ? 'B分' : 'A分' }}</span>
  181. </div>
  182. <div class="flex-2" v-if="item.status == 1">
  183. <span v-if="item.msg == '奖扣成功'" class="green">{{ item.msg }}</span>
  184. <span v-else class="blue">{{ item.msg }}</span>
  185. </div>
  186. <div class="flex-2 red" v-else>{{ item.msg }}</div>
  187. </div>
  188. </div>
  189. <span slot="footer">
  190. <div class="flex-box-end" style="margin-top: 20px;"
  191. v-show="isShowError2 && results.length != resultList.length"><el-button type="primary"
  192. @click="isResult = false" size="small">确 定</el-button></div>
  193. <div class="flex-box-end" style="margin-top: 20px;" v-show="results.length == resultList.length">
  194. <el-button type="primary" @click="isResult = false" size="small">确 定</el-button></div>
  195. </span>
  196. </div>
  197. <div v-else>
  198. <div style="text-align: center;" class="red">{{ errorMsg }}</div>
  199. <span slot="footer">
  200. <div class="flex-box-end" style="margin-top: 20px;"><el-button type="primary"
  201. @click="isResult = false" size="small">确 定</el-button></div>
  202. </span>
  203. </div>
  204. </el-dialog>
  205. <!-- 缓存的奖扣 -->
  206. <el-dialog title="网络中断奖扣列表" :visible.sync="isShowBreak" width="800" :close-on-click-modal="false"
  207. :close-on-press-escape="false" :show-close="false">
  208. <div>
  209. <div style="margin-top: 20px;border: 1px solid #f1f1f1;max-height: 500px;overflow-y: auto;"
  210. class="scroll-bar">
  211. <div class="flex-box-ce results" style="font-weight: 600;">
  212. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">奖扣对象</div>
  213. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">分类</div>
  214. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">规则</div>
  215. <div class="flex-2" style="border-right: 1px solid #f1f1f1;">事件内容</div>
  216. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">积分</div>
  217. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">递交人员</div>
  218. </div>
  219. <div class="flex-box-ce results" v-for="(item, index) in breakList" :key="index">
  220. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">{{ item.name }}</div>
  221. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">
  222. <span v-if="item.rule_name">{{ item.rule_name }}</span>
  223. <span v-else>--</span>
  224. </div>
  225. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">
  226. <span v-if="item.item_name">{{ item.item_name }}</span>
  227. <span v-else>--</span>
  228. </div>
  229. <div class="flex-2" style="border-right: 1px solid #f1f1f1;">{{ item.remark }}</div>
  230. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">{{ item.point }}
  231. <span>{{ item.pt_id == 3 ? 'B分' : 'A分' }}</span>
  232. </div>
  233. <div class="flex-1" style="border-right: 1px solid #f1f1f1;">{{ item.reviewer_name }}</div>
  234. </div>
  235. </div>
  236. <span slot="footer">
  237. <div class="flex-box-end" style="margin-top: 20px;">
  238. <el-button type="primary" @click="colseBreak()" size="small">取 消</el-button>
  239. <el-button type="primary" @click="submitBreak()" size="small">再次提交</el-button>
  240. </div>
  241. </span>
  242. </div>
  243. </el-dialog>
  244. </div>
  245. </template>
  246. <script>
  247. import moment from 'moment';
  248. import EmployeeSelector from '@/components/EmployeeSelector.vue';
  249. import upload from '@/components/upload';
  250. export default {
  251. name: 'bonusPointsForm',
  252. // 数据
  253. model: {
  254. prop: 'list',
  255. event: 'value'
  256. },
  257. props: {
  258. title: {
  259. type: String,
  260. default: ''
  261. },
  262. visible: {
  263. type: Boolean,
  264. default: false
  265. },
  266. isBreak: { //是否打开缓存的未完成奖扣
  267. type: Boolean,
  268. default: false
  269. },
  270. refresh: {
  271. type: String,
  272. default: ''
  273. },
  274. integralType: {
  275. type: Number,
  276. default: 0
  277. // 1 是绩效分 , 2 是A分 , 3 是B分
  278. },
  279. ruleOnoff: {
  280. type: Boolean,
  281. default: false
  282. //true:选择规则; false:选择分类
  283. }
  284. },
  285. data() {
  286. return {
  287. pickerBeginDateBefore: {
  288. disabledDate(time) {
  289. return time.getTime() > Date.now();
  290. }
  291. },
  292. btn_loading: false,
  293. loading: false,
  294. dialogData: {
  295. members: [],
  296. items: [
  297. {
  298. ticket_count: 0,//奖票
  299. rule_switch: true,
  300. rule_id: '',
  301. item_id: '',
  302. point: '0',
  303. remark: '',
  304. event_time: moment().format('YYYY-MM-DD'),
  305. approval: '',
  306. approval_not_select: [],
  307. pt_id: this.integralType,
  308. // 积分填写限制
  309. pointShow: 1,
  310. max: 0,
  311. min: 0,
  312. // 审批人信息
  313. reviewer_id: '',
  314. approvalName: '',
  315. approval_not_select: [],
  316. approval_selected: { dept: [], employee: [] },
  317. show_approval_selector: false,
  318. // 附件
  319. fileList: [],
  320. files: [],
  321. // 规则分类 与 规则细则 名称
  322. rule_list_value: null,
  323. rule_item_list_value: null,
  324. // 规则细则详情
  325. rule_item_details: { range_type: '' }
  326. }
  327. ]
  328. },
  329. // 奖扣对象名称
  330. employeeName: '',
  331. employee_not_select: [],
  332. employee_selected: { dept: [], employee: [] },
  333. manage_scope: [], //下属人员
  334. superior_list: [], //上级人员
  335. show_employee_selector: false,
  336. // 规则分类
  337. rule_list: [],
  338. // 规则细则
  339. rule_item_list: [],
  340. flatteningIntegralRules: null,
  341. ptid: 0,
  342. itemIndex: 0,
  343. // 错误提示
  344. error_list: [], //错误信息数组
  345. error_list_show: false,//错误信息弹窗
  346. // 长连接结果
  347. results: [], //提交的返回结果集合
  348. isResult: false,
  349. percentage: 0,
  350. resultList: [],//要发送数据的集合
  351. resultIndex: 0,
  352. isShowError: false,
  353. isShowError2: false,
  354. errorMsg: '服务器繁忙,请稍后再试',
  355. breakList: [],
  356. isShowBreak: false,
  357. config: {},
  358. };
  359. },
  360. components: { EmployeeSelector, upload },
  361. watch: {
  362. isBreak() {
  363. let data = this.$getCache('award_punish').obj;
  364. data.forEach(item => {
  365. item.name = this.$getCache('userList')[item.employee_id] ? this.$getCache('userList')[item.employee_id].name : '--';
  366. item.reviewer_name = this.$getCache('userList')[item.reviewer_id] ? this.$getCache('userList')[item.reviewer_id].name : '--';
  367. })
  368. this.breakList = data;
  369. this.isShowBreak = true;
  370. },
  371. integralType(val) {
  372. this.dialogData.items[0].pt_id = val;
  373. this.ptid = val;
  374. },
  375. 'dialogData.members'(val) {
  376. if (val.length == 0) {
  377. this.employeeName = '';
  378. this.employee_selected = { dept: [], employee: [] };
  379. }
  380. },
  381. isResult(val) {
  382. if (!val) {
  383. this.isShowError = false;
  384. this.isShowBreak = false;
  385. this.$refs['dialogData'].resetFields();
  386. this.$emit('update:refresh', this.$moment().format().valueOf());
  387. this.closePopup();
  388. this.errorMsg = '服务器繁忙,请稍后再试';
  389. this.$socketApiTow.closewebsocket();
  390. }
  391. },
  392. },
  393. mounted() {
  394. var that = this;
  395. this.config = this.$store.state.config;
  396. this.getUserDetail(function (data) {
  397. that.manage_scope = data.manage_scope;
  398. that.superior_list = data.superior_list;
  399. });
  400. },
  401. methods: {
  402. initData() {
  403. this.getRuleData();
  404. this.getRuleItemData();
  405. },
  406. setIndex(index) {
  407. if (index == this.itemIndex) {
  408. return false
  409. }
  410. this.itemIndex = index;
  411. },
  412. // 关闭缓存弹窗
  413. colseBreak() {
  414. this.isShowBreak = false;
  415. this.breakList = [];
  416. this.$removeCache('award_punish');
  417. this.$emit('update:refresh', this.$moment().format().valueOf());
  418. this.closePopup();
  419. },
  420. // 提交缓存奖扣
  421. submitBreak() {
  422. this.$removeCache('award_punish');
  423. this.resultList = JSON.parse(JSON.stringify(this.breakList));
  424. this.resultIndex = 0;
  425. this.percentage = 0;
  426. this.results = [];
  427. this.isResult = true;
  428. this.opneWebSocket()
  429. },
  430. getUserDetail(func) {
  431. this.$axios('get', '/api/employee/detail').then(res => {
  432. this.$setUserData(res.data.data.user);
  433. func(res.data.data.user.employee_detail);
  434. });
  435. },
  436. submitEmployee(name) {
  437. this.$refs[name][0].confirm(); //调用组件的confirm();
  438. },
  439. submitMembers() {
  440. this.$refs.members.confirm(); //调用组件的confirm();
  441. },
  442. //关闭
  443. handleClose(done) {
  444. done();
  445. },
  446. // 附件上传
  447. beforeUpload(file) {
  448. const isJPG = /^image\/(jpeg|png|jpg)$/.test(file.type);
  449. const isLt2M = file.size / 1024 / 1024 < 5;
  450. if (!isJPG) {
  451. this.$message.error('上传图只能是 JPEG,PNG,JPG 格式!');
  452. }
  453. if (!isLt2M) {
  454. this.$message.error('上传图片大小不能超过 5MB!');
  455. }
  456. return isJPG && isLt2M;
  457. },
  458. onFilePreView(file) {
  459. if (file.response) {
  460. window.open(file.response.url, '_blank');
  461. }
  462. },
  463. onFileRemove(file, fileList) {
  464. this.dialogData.items[this.itemIndex].fileList = fileList;
  465. this.dialogData.items[this.itemIndex].files = [];
  466. fileList.forEach((element, index) => {
  467. this.dialogData.items[this.itemIndex].files.push(element.url);
  468. });
  469. },
  470. handleFilesSuccess(response, file, fileList) {
  471. let fileListData = fileList.filter(e => {
  472. return e.url
  473. })
  474. this.dialogData.items[this.itemIndex].fileList = fileListData;
  475. this.dialogData.items[this.itemIndex].files = [];
  476. fileListData.forEach((element, index) => {
  477. this.dialogData.items[this.itemIndex].files.push(element.url);
  478. });
  479. },
  480. // 没有人员提示
  481. noPersonnelListTips() {
  482. this.show_employee_selector = true;
  483. },
  484. // 当switch 改变了
  485. switchChange(index, value) {
  486. this.itemIndex = index;
  487. const item = this.dialogData.items[this.itemIndex];
  488. if (!value) {
  489. item.remark = '';
  490. item.rule_id = '';
  491. item.item_id = '';
  492. item.rule_item_list_value = '';
  493. item.rule_item_details = { range_type: '' };
  494. item.max = 0;
  495. item.min = 0;
  496. item.point = '0';
  497. } else {
  498. item.rule_id = '';
  499. item.rule_list_value = '';
  500. }
  501. },
  502. // 选择奖扣对象
  503. move_employee_confirm(data) {
  504. this.employee_selected = { dept: [], employee: [] };
  505. this.employeeName = '';
  506. this.dialogData.members = [];
  507. if (data.employee !== null && data.employee.length != 0) {
  508. let nameArr = [];
  509. data.employee.forEach(element => {
  510. this.employeeName += element.name + ',';
  511. this.employee_selected = data;
  512. this.dialogData.members.push(element.id);
  513. });
  514. }
  515. this.show_employee_selector = false;
  516. },
  517. // 获取规则类型
  518. getRuleData() {
  519. let data = { cycle_type: '1', pt_id: this.integralType };
  520. this.$axios('get', '/api/integral/rule/trees', data).then(res => {
  521. if (res.data.code == 1) {
  522. this.rule_list = this.getTreeData(res.data.data.rule_tree);
  523. }
  524. });
  525. },
  526. // 获取规则细则
  527. getRuleItemData() {
  528. let data = { cycle_type: '1', pt_id: this.integralType };
  529. this.loading = true;
  530. this.$axios('get', '/api/integral/rule/trees', data, 'v2').then(res => {
  531. if (res.data.code == 1) {
  532. const resultData = res.data.data;
  533. this.rule_item_list = resultData.tree;
  534. this.flatteningIntegralRules = this.getItemDetail(this.rule_item_list);
  535. this.loading = false;
  536. }
  537. });
  538. },
  539. // 规则细则变化关闭down
  540. ruleItemChange(value) {
  541. const item = this.dialogData.items[this.itemIndex];
  542. if (value.length > 0) {
  543. let ruleItemDetails = null;
  544. this.flatteningIntegralRules.forEach(element => {
  545. if (element.id == value[value.length - 1]) {
  546. ruleItemDetails = { ...element };
  547. }
  548. });
  549. item.rule_item_details = ruleItemDetails;
  550. item.remark = ruleItemDetails.name;
  551. item.rule_id = ruleItemDetails.pid;
  552. item.item_id = value[value.length - 1];
  553. item.max = ruleItemDetails.max_point * 1;
  554. item.min = ruleItemDetails.min_point * 1;
  555. item.point = ruleItemDetails.min_point;
  556. } else {
  557. item.remark = '';
  558. item.rule_id = '';
  559. item.item_id = '';
  560. item.rule_item_list_value = '';
  561. item.rule_item_details = { range_type: '' };
  562. item.max = 0;
  563. item.min = 0;
  564. item.point = '0';
  565. }
  566. },
  567. // 规则分类变化关闭dewn
  568. ruleChange(value) {
  569. const item = this.dialogData.items[this.itemIndex];
  570. if (value.length > 0) {
  571. const user_info = this.$getUserData();
  572. let ruleCascader = 'ruleCascader' + this.itemIndex;
  573. user_info.point_config.point_limit.forEach(element => {
  574. if (this.ptid == this.integralType) {
  575. item.max = element.point * 1;
  576. item.min = element.point * -1;
  577. }
  578. });
  579. this.$refs.[ruleCascader][0].dropDownVisible = false;
  580. item.rule_id = value[value.length - 1];
  581. item.rule_name = this.$refs.[ruleCascader][0].getCheckedNodes()[0].label;
  582. } else {
  583. item.rule_id = '';
  584. item.rule_list_value = '';
  585. item.rule_name = '';
  586. }
  587. },
  588. // 递归判断列表,把最后的child设为undefined
  589. getTreeData(data) {
  590. for (var i = 0; i < data.length; i++) {
  591. if (data[i].child.length < 1) {
  592. // child若为空数组,则将child设为undefined
  593. data[i].child = undefined;
  594. } else {
  595. // child若不为空数组,则继续 递归调用 本方法
  596. this.getTreeData(data[i].child);
  597. }
  598. }
  599. return data;
  600. },
  601. getItemDetail(arr) {
  602. let result = [];
  603. for (const item of arr) {
  604. var res = JSON.parse(JSON.stringify(item)); // 先克隆一份数据作为第一层级的填充
  605. delete res['child'];
  606. result.push(res);
  607. if (item.child instanceof Array && item.child.length > 0) {
  608. // 如果当前child为数组并且长度大于0,才可进入getItemDetail()方法
  609. result = result.concat(this.getItemDetail(item.child));
  610. }
  611. }
  612. return result;
  613. },
  614. // 加一条
  615. addItem() {
  616. if (this.dialogData.items.length == 10) {
  617. this.$message({
  618. type: 'warning',
  619. message: '一次只能添加10条奖扣'
  620. });
  621. return false;
  622. }
  623. this.dialogData.items.push({
  624. ticket_count: 0,//奖票
  625. rule_switch: true,
  626. rule_id: '',
  627. item_id: '',
  628. point: '0',
  629. remark: '',
  630. event_time: moment().format('YYYY-MM-DD'),
  631. approval: '',
  632. approval_not_select: [],
  633. pt_id: this.integralType,
  634. // 积分填写限制
  635. pointShow: 1,
  636. max: 0,
  637. min: 0,
  638. // 审批人信息
  639. reviewer_id: '',
  640. approvalName: '',
  641. approval_not_select: [],
  642. approval_selected: { dept: [], employee: [] },
  643. show_approval_selector: false,
  644. // 附件
  645. fileList: [],
  646. files: [],
  647. // 规则分类 与 规则细则 名称
  648. rule_list_value: null,
  649. rule_item_list_value: null,
  650. // 规则细则详情
  651. rule_item_details: { range_type: '' }
  652. });
  653. },
  654. closeDialog(formName) {
  655. this.dialogData.items.forEach(element => {
  656. element.rule_item_details = { range_type: '' };
  657. });
  658. this.$refs[formName].resetFields();
  659. this.closePopup();
  660. },
  661. delItem(index) {
  662. this.$confirm('你确定要删除奖扣明细' + parseInt(index + 1) + '吗?', '提示', {
  663. confirmButtonText: '确定',
  664. cancelButtonText: '取消',
  665. type: 'warning'
  666. }).then(() => {
  667. this.dialogData.items.splice(index, 1);
  668. this.$message({
  669. type: 'success',
  670. message: '删除成功!'
  671. });
  672. });
  673. },
  674. subData(formName) {
  675. this.$refs[formName].validate(valid => {
  676. if (valid) {
  677. this.save();
  678. }
  679. });
  680. },
  681. // 提交数据
  682. save() {
  683. let data = {
  684. members: this.dialogData.members,
  685. items: []
  686. };
  687. const user_info = this.$getUserData();
  688. const index = user_info.point_config.point_limit.findIndex(o => o.pt_id === this.integralType);
  689. let employeePointLimitMin = null;
  690. let employeePointLimitMax = null;
  691. user_info.point_config.point_limit.forEach(element => {
  692. if (this.ptid == this.integralType) {
  693. employeePointLimitMax = element.point * 1;
  694. employeePointLimitMin = element.point * 1;
  695. }
  696. });
  697. const ruleLimitCheck = user_info.site_config.rule_limit_check;
  698. let maxPointPermission = 0;
  699. let creator = user_info.employee_detail.role_list.findIndex(item => item.name == 'creator') >= 0;
  700. if (user_info.point_config.point_limit.length > 0) {
  701. maxPointPermission = parseInt(user_info.point_config.point_limit[index].point);
  702. } else {
  703. if (creator) {
  704. maxPointPermission = -1;
  705. }
  706. }
  707. try {
  708. if (index < 0 || user_info.is_creator === 1) {
  709. this.dialogData.items.forEach((element, i) => {
  710. data.items.push({
  711. rule_id: element.rule_id || 0,
  712. item_id: element.item_id || 0,
  713. point: element.point,
  714. remark: element.remark,
  715. event_time: element.event_time,
  716. pt_id: this.ptid,
  717. rule_name: element.rule_name,
  718. item_name: element.rule_item_details.name,
  719. reviewer_id: element.reviewer_id || 0,
  720. files: element.files,
  721. ticket_count: element.ticket_count,
  722. });
  723. });
  724. } else {
  725. this.dialogData.items.forEach((element, index) => {
  726. !element.reviewer_id ? (element.reviewer_id = 0) : '';
  727. !element.item_id ? (element.item_id = 0) : '';
  728. if (
  729. (element.reviewer_id && element.point !== 0 && element.rule_id > 0) ||
  730. (element.reviewer_id <= 0 &&
  731. element.item_id > 0 &&
  732. ruleLimitCheck &&
  733. element.point !== 0 &&
  734. element.point <= maxPointPermission &&
  735. Math.abs(element.point) <= maxPointPermission) ||
  736. (element.reviewer_id <= 0 && !ruleLimitCheck && this.integralType == 3 && element.rule_id > 0) ||
  737. (element.reviewer_id <= 0 &&
  738. element.item_id >= 0 &&
  739. element.point !== 0 &&
  740. element.point <= maxPointPermission &&
  741. Math.abs(element.point) <= maxPointPermission &&
  742. element.rule_id > 0)
  743. ) {
  744. data.items.push({
  745. rule_id: element.rule_id || 0,
  746. item_id: element.item_id || 0,
  747. point: element.point,
  748. remark: element.remark,
  749. event_time: element.event_time,
  750. pt_id: this.ptid,
  751. reviewer_id: element.reviewer_id || 0,
  752. files: element.files,
  753. rule_name: element.rule_name,
  754. item_name: element.rule_item_details.name,
  755. ticket_count: element.ticket_count,
  756. });
  757. } else {
  758. this.$message.error('第' + (index + 1) + '条输入积分分值超出权限,请选择审批人递交');
  759. throw new Error();
  760. }
  761. });
  762. }
  763. } catch (e) {
  764. this.btn_loading = false;
  765. return false;
  766. }
  767. this.webSocket(data);
  768. // 走长连接处理
  769. return false
  770. this.btn_loading = true;
  771. this.$axios('post', this.integralType === 1 ? '' : this.integralType === 2 ? '/api/integral/review/a/entry' : '/api/integral/point/entry', data)
  772. .then(res => {
  773. if (res.data.code == 1) {
  774. var is = true, msg;
  775. if (this.integralType == '3') {
  776. res.data.data.list.forEach(item => {
  777. if (item.status != 1) {
  778. is = false;
  779. msg = item.msg;
  780. }
  781. });
  782. } else {
  783. res.data.data.list.forEach(item => {
  784. if (item.status != 1) {
  785. is = false;
  786. msg = item.rule_item;
  787. }
  788. });
  789. }
  790. if (is) {
  791. this.dialogData.items.forEach(element => {
  792. element.rule_item_details = { range_type: '' };
  793. });
  794. this.$refs['dialogData'].resetFields();
  795. this.$emit('update:visible', false);
  796. this.$emit('update:refresh', this.$moment().format().valueOf());
  797. this.closePopup();
  798. this.error_list = res.data.data.list;
  799. this.error_list_show = true;
  800. }
  801. }
  802. })
  803. .finally(e => {
  804. this.btn_loading = false;
  805. });
  806. },
  807. webSocket(data) {
  808. let { members, items } = data;
  809. let arr = [];
  810. members.forEach(item => {
  811. items.forEach(item2 => {
  812. item2.type = this.integralType === 2 ? 'pea' : 'peb';
  813. item2.employee_id = item;
  814. arr.push(JSON.parse(JSON.stringify(item2)))
  815. })
  816. })
  817. this.resultList = arr;
  818. this.resultIndex = 0;
  819. this.percentage = 0;
  820. this.results = [];
  821. this.isResult = true;
  822. this.opneWebSocket()
  823. },
  824. opneWebSocket() {
  825. let wsData = this.resultList;
  826. if (wsData[this.resultIndex] && !this.isShowError) {
  827. this.$socketApiTow.sendData(wsData[this.resultIndex], this.onmessageWS)
  828. }
  829. },
  830. onmessageWS(e) {
  831. if (e.type == 'peb' || e.type == 'pea') {
  832. this.results.unshift(e.result);
  833. this.resultIndex++;
  834. this.opneWebSocket();
  835. // 进度条
  836. let lng = this.resultList.length;
  837. this.percentage = Math.floor(this.resultIndex / (lng / 100))
  838. if (lng == this.results.length) {
  839. this.percentage = 100;
  840. }
  841. }
  842. // 中途断开
  843. if (e.type == 'break') {
  844. let wsData = this.resultList;
  845. this.errorMsg = e.msg
  846. let data = {
  847. type: this.integralType,
  848. obj: wsData.slice(this.resultIndex, wsData.length)
  849. }
  850. this.$setCache('award_punish', data);
  851. this.isShowError2 = true;
  852. }
  853. // 连接不上
  854. if (e.type == 'error') {
  855. this.errorMsg = e.msg
  856. this.isShowError = true;
  857. }
  858. },
  859. // 选择审核人
  860. approval_confirm(data) {
  861. const item = this.dialogData.items[this.itemIndex];
  862. item.approvalName = '';
  863. if (item.ticket_count) {
  864. this.$message('选择递交审批人时,发放奖票将失效,由审批上级来发放奖票');
  865. }
  866. item.ticket_count = 0;
  867. item.approval_selected = { dept: [], employee: [] };
  868. item.reviewer_id = '';
  869. if (data.employee !== null && data.employee.length != 0) {
  870. item.approvalName = data.employee[0].name;
  871. item.approval_selected.employee = [{ name: data.employee[0].name, id: data.employee[0].id, img_url: data.employee[0].img_url }];
  872. item.reviewer_id = data.employee[0].id;
  873. }
  874. item.reviewer_id ? (item.pointShow = 3) : '';
  875. item.show_approval_selector = false;
  876. },
  877. // 关闭弹窗
  878. closePopup() {
  879. //关闭重置窗口状态
  880. this.dialogData.items = [
  881. {
  882. ticket_count: 0,//奖票
  883. rule_switch: true,
  884. rule_id: '',
  885. item_id: '',
  886. point: '0',
  887. remark: '',
  888. event_time: moment().format('YYYY-MM-DD'),
  889. approval: '',
  890. approval_not_select: [],
  891. pt_id: this.integralType,
  892. // 积分填写限制
  893. pointShow: 1,
  894. max: 0,
  895. min: 0,
  896. // 审批人信息
  897. reviewer_id: '',
  898. approvalName: '',
  899. approval_not_select: [],
  900. approval_selected: { dept: [], employee: [] },
  901. show_approval_selector: false,
  902. // 附件
  903. fileList: [],
  904. files: [],
  905. // 规则分类 与 规则细则 名称
  906. rule_list_value: null,
  907. rule_item_list_value: null,
  908. // 规则细则详情
  909. rule_item_details: { range_type: '' }
  910. }
  911. ];
  912. this.$nextTick(() => {
  913. this.$emit('update:visible', false);
  914. });
  915. }
  916. }
  917. };
  918. </script>
  919. <style lang="scss" scoped>
  920. ::v-deep .el-input__count {
  921. right: 70px;
  922. bottom: -36px;
  923. }
  924. .title {
  925. background-color: #f4f9fd;
  926. border: 1px solid #e4f0fc;
  927. padding-right: 10px;
  928. }
  929. .title .span {
  930. line-height: 36px;
  931. width: 100px;
  932. text-align: right;
  933. font-weight: 700;
  934. }
  935. .width_400 {
  936. width: 400px;
  937. position: relative;
  938. }
  939. .itemClass .el-cascader-menu .el-cascader-menu__wrap .el-scrollbar__view li.el-cascader-node {
  940. height: auto;
  941. max-width: 500px;
  942. .el-cascader-node__label {
  943. white-space: initial;
  944. overflow: initial;
  945. text-overflow: initial;
  946. }
  947. }
  948. .results {
  949. border-bottom: 1px solid #f1f1f1;
  950. text-align: center;
  951. }
  952. .results div {
  953. padding: 10px;
  954. }
  955. </style>