index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <template>
  2. <view>
  3. <!-- head -->
  4. <view class="head-area" :style="{ height: `${headHeight}rpx` }">
  5. <u-navbar
  6. :is-fixed="false"
  7. :border-bottom="false"
  8. :is-back="true"
  9. :custom-back="navBack"
  10. :title="isWeixin ? '' : option.navTitle"
  11. :back-icon-name="option.navBackIcon || 'arrow-leftward'"
  12. back-icon-color="#fff"
  13. back-icon-size="35"
  14. :background="{ background: 'transparent' }"
  15. title-color="#fff"
  16. >
  17. <view class="nav-title" v-if="isWeixin">{{ option.navTitle }}</view>
  18. <view class="nav-right" slot="right" v-if="option.manageBtn" @click="manageSwitch = !manageSwitch">
  19. {{ manageSwitch ? '完成' : '管理' }}
  20. </view>
  21. </u-navbar>
  22. <view class="search-item" v-if="option.searchShow">
  23. <u-search
  24. placeholder="搜索内容"
  25. v-model="searchValue"
  26. @search="onSearch(searchValue)"
  27. @clear="onSearch(searchValue)"
  28. shape="square"
  29. bg-color="#8EAAFF"
  30. placeholder-color="#5470C4"
  31. color="#fff"
  32. search-icon-color="#ffffffE5"
  33. :clearabled="true"
  34. :show-action="false"
  35. ></u-search>
  36. </view>
  37. </view>
  38. <!-- head seat -->
  39. <view :style="{ height: `${headHeight}rpx` }"></view>
  40. <!-- list -->
  41. <block v-if="data && data.length > 0">
  42. <view class="" v-for="(item, index) in data" :key="index">
  43. <crud-item
  44. :ref="`crud-item${index}`"
  45. :list="data"
  46. :node="item"
  47. :option="option"
  48. :nodeIndex="index"
  49. :checkList="checkList"
  50. :checkShow="manageSwitch"
  51. :random-num="randomNum"
  52. ></crud-item>
  53. </view>
  54. <view style="padding: 20rpx 0;"><slot name="loadmore"></slot></view>
  55. </block>
  56. <!-- foot -->
  57. <block v-if="manageSwitch">
  58. <view :style="[{ height: isFullSucreen ? '168rpx' : '100rpx' }]"></view>
  59. <view class="foot flex" v-if="manageSwitch" :class="[isFullSucreen ? 'foot-ipx' : '']">
  60. <checkbox-group @change="onCheckedAll">
  61. <label class="well-check flex flex-c">
  62. <checkbox
  63. color="#fff"
  64. style="transform: scale(0.7)"
  65. value="allSelect"
  66. :checked="checkedAll"
  67. class="curdCheck"
  68. ></checkbox>
  69. <text class="checkAll">全选 ({{ checkList.length }})</text>
  70. </label>
  71. </checkbox-group>
  72. <view class=""><view class="foot-del-btn" @click="onRemove">删除</view></view>
  73. </view>
  74. </block>
  75. <!-- form -->
  76. <u-popup
  77. v-model="createShow"
  78. safe-area-inset-bottom
  79. border-radius="14"
  80. mode="bottom"
  81. closeable
  82. @close="onCreateClose"
  83. :mask-close-able="false"
  84. >
  85. <view class="popup-head"><u-section :title="action" :right="false" line-color="#5f88ff"></u-section></view>
  86. <scroll-view scroll-y="true" style="height: 68vh;">
  87. <view class="form">
  88. <block v-if="formType == 'uviewForm'">
  89. <u-form :model="value" ref="uForm" :label-width="option.labelWidth || 160"><slot name="form"></slot></u-form>
  90. </block>
  91. <block v-else><slot name="form"></slot></block>
  92. </view>
  93. </scroll-view>
  94. <view class="form-btn">
  95. <u-button
  96. v-if="action != '查看'"
  97. @click="onSubmit"
  98. :loading="disabled"
  99. shape="circle"
  100. ripple
  101. :custom-style="{ width: '100%', background: 'linear-gradient(90deg, #69b0ff, #5f88ff)', color: '#fff' }"
  102. >
  103. 保存
  104. </u-button>
  105. </view>
  106. </u-popup>
  107. <!-- addBtn -->
  108. <movable-area class="movable-area">
  109. <movable-view class="movable-view" :x="moveX" :y="moveY" direction="all">
  110. <image src="@/static/images/crud/create.png" @click="onCreateOpen"></image>
  111. </movable-view>
  112. </movable-area>
  113. <!-- empty -->
  114. <slot name="empty">
  115. <block v-if="data.length == 0">
  116. <u-empty text="工作再忙,也要记得喝水~" margin-top="200" :src="require('@/static/images/crud/empty.png')"></u-empty>
  117. </block>
  118. </slot>
  119. </view>
  120. </template>
  121. <script>
  122. import crudItem from './components/crud-item/index'
  123. import { checkModel } from './util/tool.js'
  124. import validate from './util/validate.js'
  125. export default {
  126. name: 'rider-crud',
  127. components: { crudItem },
  128. props: {
  129. value: {}, // 表单
  130. data: Array, // 列表
  131. // 配置
  132. option: {
  133. type: Object,
  134. default: {
  135. column: []
  136. }
  137. },
  138. // 表单类型
  139. formType: {
  140. type: String,
  141. default: 'uviewForm'
  142. },
  143. // 返回事件
  144. navBack: {
  145. type: Function,
  146. default: null
  147. },
  148. rules: {}, // 校验规则
  149. readonly: {
  150. type: Boolean,
  151. default: false
  152. }
  153. },
  154. data() {
  155. return {
  156. isFullSucreen: checkModel(),
  157. moveX: 320, //添加按钮坐标
  158. moveY: 580,
  159. checkedAll: false, //全选
  160. checkList: [], //选中值
  161. createShow: false, //form弹窗
  162. disabled: false, // 操作按钮
  163. action: '新增',
  164. searchValue: '',
  165. isWeixin: false,
  166. manageSwitch: false,
  167. headHeight: 0,
  168. randomNum: Math.round(Math.random() * 99999) // 随机数,防止uni.on重复调用
  169. }
  170. },
  171. watch: {
  172. manageSwitch: {
  173. handler(newVal, oldVal) {
  174. this.checkList = []
  175. this.checkedAll = false
  176. },
  177. deep: true,
  178. immediate: true
  179. },
  180. 'option.searchShow': {
  181. handler(newVal, oldVal) {
  182. const that = this
  183. this.headHeight = newVal ? 192 : 98
  184. // #ifndef H5
  185. uni.getSystemInfo({
  186. success: function(res) {
  187. const statusBarHeight = Number(res.statusBarHeight) + Number(res.platform == 'ios' ? 44 : 48)
  188. that.headHeight = newVal ? statusBarHeight + 192 : statusBarHeight + 98
  189. }
  190. })
  191. // #endif
  192. },
  193. deep: true,
  194. immediate: true
  195. }
  196. },
  197. // 通信事件
  198. created() {
  199. let that = this
  200. uni.$on(`action${this.randomNum}`, ({ index }, i, list) => {
  201. that.onActionClick({ index }, i, list)
  202. })
  203. uni.$on(`checked${this.randomNum}`, (e, id) => {
  204. that.onChecked(e, id)
  205. })
  206. // #ifdef MP-WEIXIN
  207. this.isWeixin = true
  208. // #endif
  209. // #ifndef MP-WEIXIN
  210. this.isWeixin = false
  211. // #endif
  212. },
  213. methods: {
  214. setLazyLoad(func) {
  215. this.option.lazyLoad = func
  216. setTimeout(() => {
  217. this.data.forEach((d, i) => {
  218. this.$refs[`crud-item${i}`][0].setLazyLoad(func)
  219. })
  220. }, 100)
  221. },
  222. // 选择
  223. onChecked(e, id) {
  224. const i = e.detail.value[0]
  225. if (i) this.checkList.push(i)
  226. else this.checkList.splice(this.checkList.findIndex(e => e === id), 1)
  227. this.checkedAll = this.checkList.length === this.data.length
  228. },
  229. // 全选
  230. onCheckedAll() {
  231. if (!this.checkedAll) {
  232. this.checkedAll = true
  233. this.checkList = []
  234. this.data.map(e => {
  235. this.checkList.push(e.id)
  236. })
  237. } else {
  238. this.checkedAll = false
  239. this.checkList = []
  240. }
  241. },
  242. // 元素点击
  243. onClick(item, index) {
  244. const action = { name: '查看' }
  245. const data = JSON.parse(JSON.stringify(item))
  246. this.$emit('input', data)
  247. this.$emit('action-click', { index, data, actionIndex: -1, action, done: this.show })
  248. this.action = action.name
  249. },
  250. // 底部删除
  251. onRemove() {
  252. if (this.checkList.length === 0) {
  253. uni.showToast({
  254. title: '请选择至少一条数据',
  255. icon: 'none',
  256. duration: 2000
  257. })
  258. return
  259. }
  260. this.$emit('del', this.checkList.join(','))
  261. },
  262. // 打开弹窗
  263. onCreateOpen() {
  264. this.action = '新增'
  265. this.$emit('action-click', { action: { name: this.action }, done: this.show })
  266. },
  267. // 关闭弹窗
  268. onCreateClose() {
  269. this.hide()
  270. },
  271. // 添加保存
  272. onSubmit() {
  273. this.disabled = true
  274. const data = JSON.parse(JSON.stringify(this.value))
  275. if (this.formType == 'uviewForm') {
  276. // #ifdef MP
  277. validate(this.value, this.rules)
  278. .then(() => {
  279. this.$emit('submit', data, this.action, this.unloading, this.hide)
  280. })
  281. .catch(() => {
  282. this.unloading()
  283. })
  284. // #endif
  285. // #ifndef MP
  286. this.$refs.uForm.validate(valid => {
  287. if (valid) {
  288. this.$emit('submit', data, this.action, this.unloading, this.hide)
  289. } else {
  290. this.unloading()
  291. }
  292. })
  293. // #endif
  294. } else {
  295. validate(this.value, this.rules)
  296. .then(() => {
  297. debugger
  298. this.$emit('submit', data, this.action, this.unloading, this.hide)
  299. })
  300. .catch(() => {
  301. this.unloading()
  302. })
  303. }
  304. },
  305. // 左滑按钮点击
  306. onActionClick({ index }, i, list) {
  307. let action
  308. if (index == -1) action = { name: '查看' }
  309. else action = this.option.actions[index]
  310. const data = JSON.parse(JSON.stringify(list[i]))
  311. if (['新增', '编辑', '查看'].includes(action.name)) this.$emit('input', data)
  312. this.$emit('action-click', { index: i, data, actionIndex: index, action, done: this.show })
  313. // this.action = action.name
  314. this.$set(this, 'action', action.name)
  315. },
  316. //全选删除后回调
  317. allCheckHide(val) {
  318. if (this.checkedAll) this.manageSwitch = val
  319. this.checkList = []
  320. },
  321. // 搜索
  322. onSearch(val) {
  323. this.$emit('search', val)
  324. },
  325. show() {
  326. this.createShow = true
  327. setTimeout(() => {
  328. if (this.rules && this.$refs['uForm'] && typeof this.$refs['uForm'].setRules == 'function') {
  329. this.$refs['uForm'].setRules(this.rules)
  330. }
  331. }, 100)
  332. },
  333. hide() {
  334. this.createShow = false
  335. this.disabled = false
  336. this.$emit('input', {})
  337. this.$emit('update:readonly', false)
  338. },
  339. unloading() {
  340. this.disabled = false
  341. },
  342. hideChildren() {
  343. this.data.forEach((d, i) => {
  344. this.$refs[`crud-item${i}`][0].hide()
  345. })
  346. }
  347. }
  348. }
  349. </script>
  350. <style lang="scss" scoped>
  351. .flex {
  352. display: flex;
  353. align-items: center;
  354. justify-content: space-between;
  355. }
  356. .flex-one {
  357. flex: 1;
  358. }
  359. .head-area {
  360. position: fixed;
  361. top: 0;
  362. left: 0;
  363. right: 0;
  364. z-index: 999;
  365. width: 100%;
  366. background: url('/static/images/head_bg.png') no-repeat center bottom;
  367. background-size: 100% 100%;
  368. .nav-right {
  369. color: #fff;
  370. font-size: 30rpx;
  371. font-weight: normal;
  372. padding-right: 30rpx;
  373. }
  374. .nav-title {
  375. color: #fff;
  376. font-size: 32rpx;
  377. font-weight: normal;
  378. }
  379. .search-item {
  380. padding: 0 30rpx;
  381. }
  382. }
  383. .head-h {
  384. height: 200rpx;
  385. }
  386. /* 底部 */
  387. /* 适配 */
  388. .foot-ipx {
  389. bottom: 68rpx !important;
  390. }
  391. .foot-ipx::after {
  392. content: ' ';
  393. position: fixed;
  394. bottom: 0 !important;
  395. left: 0;
  396. right: 0;
  397. height: 68rpx !important;
  398. width: 100%;
  399. background: #fff;
  400. }
  401. .foot {
  402. position: fixed;
  403. left: 0;
  404. right: 0;
  405. bottom: 0;
  406. z-index: 99;
  407. width: 100%;
  408. padding: 0 26rpx;
  409. height: 100rpx;
  410. background: #fff;
  411. border-top: 2px solid #eee;
  412. &-del-btn {
  413. width: 160rpx;
  414. height: 64rpx;
  415. line-height: 64rpx;
  416. font-size: 26rpx;
  417. // color: #fff;
  418. text-align: center;
  419. border-radius: 34px;
  420. // background: linear-gradient(180deg, #5f88ff, #69b0ff);
  421. color: #f56c6c;
  422. background: #fef0f0;
  423. }
  424. }
  425. .form {
  426. padding: 0 30rpx 20rpx;
  427. }
  428. .popup-head {
  429. padding: 36rpx 30rpx;
  430. border-bottom: 2rpx dashed #efefef;
  431. }
  432. .form-btn {
  433. margin: 30rpx;
  434. min-height: 80rpx;
  435. }
  436. .movable-area {
  437. height: 100vh;
  438. width: 100%;
  439. top: 0;
  440. position: fixed;
  441. pointer-events: none;
  442. .movable-view {
  443. width: 96rpx;
  444. height: 96rpx;
  445. pointer-events: auto;
  446. image {
  447. width: 96rpx;
  448. height: 96rpx;
  449. }
  450. }
  451. }
  452. </style>