myCard.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <template>
  2. <view class="card_container">
  3. <view class="card_box">
  4. <!-- 标题区域 -->
  5. <view class="header_box" v-if="title">
  6. <view class="title_left">
  7. <view class="round" v-if="index">{{ index }}</view>
  8. <view class="orderId" :style="{ marginLeft: index ? '16rpx' : '' }">{{
  9. title
  10. }}</view>
  11. </view>
  12. <!-- 状态标签 -->
  13. <view class="status-tag" v-if="status">{{ status }}</view>
  14. <!-- 单选/多选控件 -->
  15. <view class="select-tag" v-if="showRadio" @click.stop>
  16. <!-- 单选模式:使用 u-radio-group(每个卡片独立,互斥) -->
  17. <u-radio-group
  18. v-if="selectionMode === 'single'"
  19. v-model="radioValueModel"
  20. placement="row"
  21. @change="handleRadioChange"
  22. >
  23. <u-radio
  24. :name="item.id"
  25. :iconSize="44"
  26. activeColor="#3c9cff"
  27. :labelSize="0"
  28. ></u-radio>
  29. </u-radio-group>
  30. <!-- 多选模式:使用独立 u-checkbox,由父组件控制选中状态 -->
  31. <u-checkbox
  32. v-else
  33. :name="item.id"
  34. :checked="isChecked"
  35. :iconSize="44"
  36. activeColor="#3c9cff"
  37. :labelSize="0"
  38. @change="handleCheckboxChange"
  39. ></u-checkbox>
  40. </view>
  41. </view>
  42. <!-- 内容区域(与原代码相同,省略...) -->
  43. <view class="item_box rx-bc" v-for="(_item, i) in columns" :key="i">
  44. <template v-for="val in _item">
  45. <view
  46. class="perce50"
  47. :class="[val.className, { 'full-width': val.isFullWidth }]"
  48. :style="val.style"
  49. :key="val.prop"
  50. v-if="!val.isNone"
  51. >
  52. <view class="item_one rx-sc" v-if="val.type == 'action'">
  53. <view class="lable">{{ val.label }}</view>
  54. <view class="text" style="flex-wrap: wrap">
  55. <template v-for="(btn, bI) in btnList">
  56. <u-button
  57. :plain="true"
  58. :hairline="true"
  59. size="mini"
  60. :type="btn.btnType"
  61. v-if="judge(btn)"
  62. :text="btn.name"
  63. @click="action(btn)"
  64. :key="bI"
  65. ></u-button>
  66. </template>
  67. </view>
  68. </view>
  69. <view class="item_one rx-sc kk" v-else>
  70. <view class="lable">{{ val.label }}</view>
  71. <view class="text" :class="val.valueClass" v-if="val.formatter">{{
  72. val.formatter(item) || ''
  73. }}</view>
  74. <view class="text" :class="val.valueClass" v-else-if="val.slot">
  75. <slot :name="val.slot"></slot>
  76. </view>
  77. <view class="text" :class="val.valueClass" v-else>{{
  78. item[val.prop] || ''
  79. }}</view>
  80. </view>
  81. </view>
  82. </template>
  83. </view>
  84. <view class="footer-link" v-if="showDetail" @click="goDetail">
  85. <text>查看详情</text>
  86. <u-icon name="arrow-right" size="24" color="#999999"></u-icon>
  87. </view>
  88. </view>
  89. </view>
  90. </template>
  91. <script>
  92. export default {
  93. props: {
  94. btnList: { type: Array, default: () => [] },
  95. item: { type: Object, default: () => ({}) },
  96. columns: { type: Array, default: () => [] },
  97. title: { type: String, default: '' },
  98. status: { type: String, default: '' },
  99. index: { type: [String, Number], default: '' },
  100. showDetail: { type: Boolean, default: true },
  101. showRadio: { type: Boolean, default: false },
  102. radioValue: { type: [String, Number], default: null },
  103. selectionMode: { type: String, default: 'single' }, // 'single' or 'multiple'
  104. checkboxValue: { type: Array, default: () => [] }, // 父组件传入的选中ID数组
  105. },
  106. computed: {
  107. judge() {
  108. return (item) => {
  109. if (item.judge) {
  110. let is = true;
  111. item.judge.forEach(({ key, value, authorities, fn }) => {
  112. if (authorities) {
  113. is = this.$isAuthorities(authorities);
  114. }
  115. if (value && !value.includes(this.item[key])) {
  116. is = false;
  117. }
  118. if (fn) {
  119. is = fn(this.item);
  120. }
  121. });
  122. return is;
  123. } else {
  124. return true;
  125. }
  126. };
  127. },
  128. // 单选模式下的绑定值
  129. radioValueModel: {
  130. get() {
  131. return this.radioValue;
  132. },
  133. set(val) {
  134. if (val === this.item.id) {
  135. this.$emit('radioChange', this.item);
  136. }
  137. },
  138. },
  139. // 多选模式下的选中状态(根据 checkboxValue 数组判断)
  140. isChecked() {
  141. return this.checkboxValue.includes(this.item.id);
  142. },
  143. },
  144. methods: {
  145. action(item) {
  146. if (item.type == 1) {
  147. uni.navigateTo({
  148. url: item.pageUrl + '?id=' + this.item.id + (item.query || ''),
  149. });
  150. } else {
  151. this.$emit(item.apiName);
  152. }
  153. },
  154. goDetail() {
  155. this.$emit('goDetail', this.item);
  156. },
  157. handleRadioChange(val) {
  158. if (val === this.item.id) {
  159. this.$emit('radioChange', this.item);
  160. }
  161. },
  162. // 多选:点击 checkbox 时,通知父组件变更
  163. handleCheckboxChange(e) {
  164. this.$emit('checkboxChange', {
  165. checked: e, // e 为 boolean,表示当前 checkbox 是否被选中
  166. item: this.item,
  167. });
  168. },
  169. },
  170. };
  171. </script>
  172. <style lang="scss" scoped>
  173. .card_container {
  174. padding: 16rpx 0;
  175. }
  176. .card_box {
  177. background: #ffffff;
  178. border-radius: 24rpx;
  179. padding: 24rpx;
  180. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
  181. position: relative;
  182. .rx-bc {
  183. display: flex;
  184. align-items: flex-start;
  185. flex-flow: row wrap;
  186. > view {
  187. margin-top: 24rpx;
  188. &:first-child,
  189. &:nth-child(2) {
  190. margin-top: 0;
  191. }
  192. }
  193. }
  194. .rx-sc {
  195. display: flex;
  196. align-items: flex-start;
  197. }
  198. .header_box {
  199. display: flex;
  200. justify-content: space-between;
  201. align-items: center;
  202. width: 100%;
  203. position: relative;
  204. background: linear-gradient(135deg, #eef5ff 0%, #f5f9ff 100%);
  205. padding: 20rpx 24rpx;
  206. border-radius: 20rpx 20rpx 0 0;
  207. margin: -24rpx -24rpx 24rpx -24rpx;
  208. width: calc(100% + 48rpx);
  209. box-sizing: border-box;
  210. .round {
  211. width: 44rpx;
  212. height: 44rpx;
  213. line-height: 44rpx;
  214. border-radius: 50%;
  215. background: $theme-color;
  216. color: #fff;
  217. text-align: center;
  218. font-size: 24rpx;
  219. font-weight: 600;
  220. flex-shrink: 0;
  221. }
  222. .orderId {
  223. color: #1f2b3c;
  224. font-size: 30rpx;
  225. font-weight: 600;
  226. white-space: nowrap;
  227. overflow: hidden;
  228. text-overflow: ellipsis;
  229. }
  230. .title_left {
  231. display: flex;
  232. align-items: center;
  233. flex: 1;
  234. padding-right: 120rpx;
  235. overflow: hidden;
  236. }
  237. .status-tag {
  238. background: #e3f2fd;
  239. color: #2196f3;
  240. font-size: 24rpx;
  241. padding: 6rpx 20rpx;
  242. border-radius: 30rpx;
  243. font-weight: 500;
  244. position: absolute;
  245. top: 50%;
  246. transform: translateY(-50%);
  247. right: 24rpx;
  248. z-index: 1;
  249. }
  250. .select-tag {
  251. position: absolute;
  252. top: 50%;
  253. transform: translateY(-50%);
  254. right: 24rpx;
  255. z-index: 2;
  256. background: transparent;
  257. /deep/ .u-radio-group,
  258. /deep/ .u-checkbox-group {
  259. .u-radio,
  260. .u-checkbox {
  261. padding: 0;
  262. margin-right: 0;
  263. }
  264. }
  265. }
  266. }
  267. .item_box {
  268. .text {
  269. display: flex;
  270. flex: 1;
  271. uni-button:after {
  272. border: none;
  273. }
  274. uni-button {
  275. width: 100rpx;
  276. margin-left: 10rpx;
  277. margin-right: 0;
  278. color: #fff !important;
  279. border: none;
  280. background: #157a2c;
  281. margin-top: 3px;
  282. }
  283. }
  284. .kk .text {
  285. overflow: hidden;
  286. text-overflow: ellipsis;
  287. white-space: nowrap;
  288. display: block;
  289. }
  290. .item_one {
  291. width: 100%;
  292. font-size: 28rpx;
  293. line-height: 44rpx;
  294. display: flex;
  295. align-items: flex-start;
  296. .lable {
  297. color: #8e9aae;
  298. flex-shrink: 0;
  299. margin-right: 16rpx;
  300. font-size: 26rpx;
  301. }
  302. .text {
  303. color: #1f2b3c;
  304. font-size: 28rpx;
  305. flex: 1;
  306. white-space: nowrap;
  307. overflow: hidden;
  308. text-overflow: ellipsis;
  309. min-width: 0;
  310. &.highlight {
  311. color: #4caf50;
  312. }
  313. }
  314. }
  315. .perce50 {
  316. width: 50%;
  317. box-sizing: border-box;
  318. padding-right: 20rpx;
  319. &:nth-child(2n) {
  320. padding-right: 0;
  321. padding-left: 20rpx;
  322. }
  323. }
  324. .perce100 {
  325. width: 100%;
  326. }
  327. .full-width {
  328. width: 100% !important;
  329. padding-left: 0 !important;
  330. padding-right: 0 !important;
  331. }
  332. }
  333. .footer-link {
  334. display: flex;
  335. justify-content: center;
  336. align-items: center;
  337. margin-top: 24rpx;
  338. padding-top: 20rpx;
  339. border-top: 2rpx solid #f0f2f5;
  340. color: #8e9aae;
  341. font-size: 28rpx;
  342. cursor: pointer;
  343. gap: 8rpx;
  344. &:active {
  345. opacity: 0.7;
  346. }
  347. }
  348. }
  349. </style>