settingPammManager.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Ib.PammManager.title1') + ' - ' + row.login">
  4. <template #right>
  5. <view class="header-btn" @click="backIndex">
  6. <cwg-icon name="icon_back" :size="18" />
  7. <text>{{ t('Ib.Settings.Title') }}</text>
  8. </view>
  9. </template>
  10. </cwg-header>
  11. <view class="main-content account-content">
  12. <view class="tips-text">{{ t('Ib.PammManager.tips3') }}</view>
  13. <view class="tab-group">
  14. <view class="tab-item" :class="{ active: subs === '1' }" @click="tableSearch('1')">
  15. {{ t('Ib.Settings.Hang') }}
  16. </view>
  17. <view class="tab-item" :class="{ active: subs === '2' }" @click="tableSearch('2')">
  18. {{ t('Ib.Settings.Undo') }}
  19. </view>
  20. </view>
  21. <!-- 穿梭框 (模拟 el-transfer) -->
  22. <view class="transfer-container">
  23. <!-- 左侧面板 (未选择) -->
  24. <view class="transfer-panel">
  25. <view class="transfer-header">
  26. <checkbox :checked="isAllLeftChecked" @click="toggleAllLeft" style="transform:scale(0.7)" />
  27. <text>{{ subs === '1' ? t('Ib.Settings.NotHang') : t('Ib.Settings.NotUndo') }}</text>
  28. <text class="count">{{ leftListData.length }}</text>
  29. </view>
  30. <view class="transfer-search">
  31. <uni-easyinput prefixIcon="search" v-model="searchLeft" :placeholder="t('placeholder.input')" />
  32. </view>
  33. <scroll-view scroll-y class="transfer-list">
  34. <checkbox-group @change="onLeftChange">
  35. <label class="transfer-item" v-for="item in filteredLeftList" :key="item.key">
  36. <checkbox :value="item.key" :checked="leftChecked.includes(item.key)" style="transform:scale(0.7)" />
  37. <text>{{ item.label }}</text>
  38. </label>
  39. </checkbox-group>
  40. <view v-if="filteredLeftList.length === 0" class="empty-text">{{ t('Documentary.tradingCenter.item143') }}
  41. </view>
  42. </scroll-view>
  43. </view>
  44. <!-- 中间操作按钮 -->
  45. <view class="transfer-actions">
  46. <button class="action-btn right" :disabled="leftChecked.length === 0" @click="moveToRight">
  47. <text>></text>
  48. </button>
  49. <button class="action-btn left" :disabled="rightChecked.length === 0" @click="moveToLeft">
  50. <text>
  51. << /text>
  52. </button>
  53. </view>
  54. <!-- 右侧面板 (已选择) -->
  55. <view class="transfer-panel">
  56. <view class="transfer-header">
  57. <checkbox :checked="isAllRightChecked" @click="toggleAllRight" style="transform:scale(0.7)" />
  58. <text>{{ subs === '1' ? t('Ib.Settings.HaveHang') : t('Ib.Settings.HaveUndo') }}</text>
  59. <text class="count">{{ rightListData.length }}</text>
  60. </view>
  61. <view class="transfer-search">
  62. <uni-easyinput prefixIcon="search" v-model="searchRight" :placeholder="t('placeholder.input')" />
  63. </view>
  64. <scroll-view scroll-y class="transfer-list">
  65. <checkbox-group @change="onRightChange">
  66. <label class="transfer-item" v-for="item in filteredRightList" :key="item.key">
  67. <checkbox :value="item.key" :checked="rightChecked.includes(item.key)" style="transform:scale(0.7)" />
  68. <text>{{ item.label }}</text>
  69. </label>
  70. </checkbox-group>
  71. <view v-if="filteredRightList.length === 0" class="empty-text">{{ t('Documentary.tradingCenter.item143') }}
  72. </view>
  73. </scroll-view>
  74. </view>
  75. </view>
  76. <view class="submit-bar">
  77. <button type="primary" class="submit-btn" @click="applySubmit">{{ t('Btn.Confirm') }}</button>
  78. </view>
  79. </view>
  80. <!-- 提交结果弹窗 -->
  81. <cwg-popup :visible="dialogCheck" :showClose="false" :showFooters="true" :confirmText="t('Btn.Confirm')"
  82. :cancelText="t('Btn.Cancel')" @confirm="closeDia" @close="closeDia">
  83. <view class="result-dialog">
  84. <view v-if="dialogVisible" class="icon-wrap">
  85. <cwg-icon name="icon_success" :size="50" color="#67C23A" />
  86. <view class="result-text">{{ t('ApplicationDialog.Des1') }}</view>
  87. </view>
  88. <view v-else class="icon-wrap">
  89. <cwg-icon name="icon_warning" :size="50" color="#E6A23C" />
  90. <view class="result-text">{{ RES }}</view>
  91. </view>
  92. </view>
  93. </cwg-popup>
  94. </cwg-page-wrapper>
  95. </template>
  96. <script setup lang="ts">
  97. import { ref, computed, onMounted } from 'vue'
  98. import { onLoad } from '@dcloudio/uni-app'
  99. import { useI18n } from 'vue-i18n'
  100. import { ibApi } from '@/service/ib'
  101. import Config from '@/config/index'
  102. const { t } = useI18n()
  103. const { Code } = Config
  104. const row = ref({ login: '', mamId: '' })
  105. const subs = ref('1')
  106. const transData1 = ref<any[]>([]) // 挂入的数据源
  107. const transValue1 = ref<string[]>([]) // 挂入的选中项
  108. const transData = ref<any[]>([]) // 撤销的数据源
  109. const transValue = ref<string[]>([]) // 撤销的选中项
  110. const flag = ref(false)
  111. const RES = ref('')
  112. const dialogCheck = ref(false)
  113. const dialogVisible = ref(false)
  114. // 穿梭框搜索和选择状态
  115. const searchLeft = ref('')
  116. const searchRight = ref('')
  117. const leftChecked = ref<string[]>([])
  118. const rightChecked = ref<string[]>([])
  119. // 根据 subs (挂入/撤销) 动态绑定当前的数据源
  120. const currentData = computed(() => subs.value === '1' ? transData1.value : transData.value)
  121. const currentValue = computed({
  122. get: () => subs.value === '1' ? transValue1.value : transValue.value,
  123. set: (val) => {
  124. if (subs.value === '1') transValue1.value = val
  125. else transValue.value = val
  126. }
  127. })
  128. // 左侧列表(全集 - 已选)
  129. const leftListData = computed(() => currentData.value.filter(item => !currentValue.value.includes(item.key)))
  130. // 右侧列表(已选)
  131. const rightListData = computed(() => currentData.value.filter(item => currentValue.value.includes(item.key)))
  132. // 搜索过滤
  133. const filteredLeftList = computed(() => leftListData.value.filter(item => item.label.toLowerCase().includes(searchLeft.value.toLowerCase())))
  134. const filteredRightList = computed(() => rightListData.value.filter(item => item.label.toLowerCase().includes(searchRight.value.toLowerCase())))
  135. const isAllLeftChecked = computed(() => filteredLeftList.value.length > 0 && leftChecked.value.length === filteredLeftList.value.length)
  136. const isAllRightChecked = computed(() => filteredRightList.value.length > 0 && rightChecked.value.length === filteredRightList.value.length)
  137. // 勾选事件
  138. const onLeftChange = (e: any) => { leftChecked.value = e.detail.value }
  139. const onRightChange = (e: any) => { rightChecked.value = e.detail.value }
  140. // 全选/反选
  141. const toggleAllLeft = () => {
  142. if (isAllLeftChecked.value) leftChecked.value = []
  143. else leftChecked.value = filteredLeftList.value.map(item => item.key)
  144. }
  145. const toggleAllRight = () => {
  146. if (isAllRightChecked.value) rightChecked.value = []
  147. else rightChecked.value = filteredRightList.value.map(item => item.key)
  148. }
  149. // 移动选中项
  150. const moveToRight = () => {
  151. currentValue.value = [...currentValue.value, ...leftChecked.value]
  152. leftChecked.value = []
  153. }
  154. const moveToLeft = () => {
  155. currentValue.value = currentValue.value.filter(key => !rightChecked.value.includes(key))
  156. rightChecked.value = []
  157. }
  158. // 切换 Tabs (挂入 / 撤销)
  159. const tableSearch = (val: string) => {
  160. subs.value = val
  161. searchLeft.value = ''
  162. searchRight.value = ''
  163. leftChecked.value = []
  164. rightChecked.value = []
  165. }
  166. // 获取挂入数据
  167. const getSubs = async () => {
  168. const res = await ibApi.MamSubsInfo({ mamId: Number(row.value.mamId), type: 1 })
  169. if (res.code === Code.StatusOK) {
  170. transData1.value = (res.data || []).map((item: string) => ({ key: item, label: item }))
  171. } else {
  172. uni.showToast({ title: res.msg, icon: 'none' })
  173. }
  174. }
  175. // 获取撤销数据
  176. const getSubs1 = async () => {
  177. const res = await ibApi.MamSubsInfo({ mamId: Number(row.value.mamId), type: 2 })
  178. if (res.code === Code.StatusOK) {
  179. transData.value = (res.data || []).map((item: string) => ({ key: item, label: item }))
  180. } else {
  181. uni.showToast({ title: res.msg, icon: 'none' })
  182. }
  183. }
  184. // 提交申请
  185. const applySubmit = async () => {
  186. if (flag.value) return
  187. flag.value = true
  188. const type = subs.value === '1' ? 1 : 2
  189. const selectedSubs = currentValue.value
  190. if (!selectedSubs || !selectedSubs.length) {
  191. flag.value = false
  192. uni.showToast({ title: t('placeholder.choose'), icon: 'none' })
  193. return
  194. }
  195. try {
  196. const res = await ibApi.MamSubsApply({
  197. mamId: Number(row.value.mamId),
  198. type,
  199. subs: selectedSubs
  200. })
  201. if (res.code === Code.StatusOK) {
  202. dialogCheck.value = true
  203. dialogVisible.value = true
  204. } else {
  205. RES.value = res.msg
  206. dialogCheck.value = true
  207. dialogVisible.value = false
  208. }
  209. } catch (error) {
  210. RES.value = t('Msg.Fail')
  211. dialogCheck.value = true
  212. dialogVisible.value = false
  213. } finally {
  214. flag.value = false
  215. }
  216. }
  217. // 关闭结果弹窗
  218. const closeDia = () => {
  219. dialogCheck.value = false
  220. dialogVisible.value = false
  221. // 刷新数据并清空选中
  222. if (subs.value === '1') getSubs()
  223. else getSubs1()
  224. currentValue.value = []
  225. }
  226. // 返回
  227. const backIndex = () => {
  228. uni.navigateBack({ delta: 1 })
  229. }
  230. onLoad((options: any) => {
  231. if (options) {
  232. row.value.login = options.login || ''
  233. row.value.mamId = options.id || ''
  234. }
  235. getSubs()
  236. getSubs1()
  237. })
  238. </script>
  239. <style lang="scss" scoped>
  240. @import "@/uni.scss";
  241. .header-btn {
  242. display: flex;
  243. align-items: center;
  244. font-size: px2rpx(14);
  245. color: var(--bs-heading-color);
  246. cursor: pointer;
  247. text {
  248. margin-left: px2rpx(5);
  249. }
  250. }
  251. .main-content {
  252. padding: px2rpx(20);
  253. background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
  254. border-radius: px2rpx(8);
  255. margin-top: px2rpx(15);
  256. min-height: calc(100vh - 100px);
  257. }
  258. .tips-text {
  259. margin-bottom: px2rpx(20);
  260. font-size: px2rpx(16);
  261. line-height: 1.7;
  262. font-weight: bold;
  263. }
  264. .tab-group {
  265. display: flex;
  266. margin-bottom: px2rpx(20);
  267. .tab-item {
  268. height: px2rpx(32);
  269. line-height: px2rpx(32);
  270. padding: 0 px2rpx(20);
  271. text-align: center;
  272. border: 1px solid #dcdfe6;
  273. background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
  274. font-size: px2rpx(14);
  275. cursor: pointer;
  276. &:first-child {
  277. border-radius: px2rpx(4) 0 0 px2rpx(4);
  278. }
  279. &:last-child {
  280. border-radius: 0 px2rpx(4) px2rpx(4) 0;
  281. border-left: none;
  282. }
  283. &.active {
  284. font-weight: bold;
  285. background-color: var(--color-error);
  286. color: var(--bs-emphasis-color);
  287. border-color: var(--color-error);
  288. }
  289. }
  290. }
  291. /* 穿梭框样式 */
  292. .transfer-container {
  293. display: flex;
  294. align-items: center;
  295. justify-content: space-between;
  296. margin-bottom: px2rpx(20);
  297. }
  298. .transfer-panel {
  299. flex: 1;
  300. border: 1px solid #ebeef5;
  301. border-radius: px2rpx(4);
  302. background: #fff;
  303. display: flex;
  304. flex-direction: column;
  305. height: px2rpx(350);
  306. }
  307. .transfer-header {
  308. height: px2rpx(40);
  309. line-height: px2rpx(40);
  310. background: #f5f7fa;
  311. margin: 0;
  312. padding: 0 px2rpx(15);
  313. border-bottom: 1px solid #ebeef5;
  314. box-sizing: border-box;
  315. color: #000;
  316. display: flex;
  317. align-items: center;
  318. font-size: px2rpx(14);
  319. .count {
  320. margin-left: auto;
  321. color: #909399;
  322. font-size: px2rpx(12);
  323. }
  324. }
  325. .transfer-search {
  326. padding: px2rpx(10);
  327. }
  328. .transfer-list {
  329. flex: 1;
  330. overflow: hidden;
  331. padding: px2rpx(10);
  332. }
  333. .transfer-item {
  334. display: flex;
  335. align-items: center;
  336. margin-bottom: px2rpx(10);
  337. font-size: px2rpx(14);
  338. color: #606266;
  339. }
  340. .empty-text {
  341. text-align: center;
  342. color: #909399;
  343. font-size: px2rpx(14);
  344. margin-top: px2rpx(50);
  345. }
  346. .transfer-actions {
  347. display: flex;
  348. flex-direction: column;
  349. justify-content: center;
  350. padding: 0 px2rpx(15);
  351. .action-btn {
  352. width: px2rpx(32);
  353. height: px2rpx(32);
  354. border-radius: px2rpx(4);
  355. background-color: var(--color-error);
  356. color: var(--bs-emphasis-color);
  357. display: flex;
  358. align-items: center;
  359. justify-content: center;
  360. margin-bottom: px2rpx(10);
  361. padding: 0;
  362. font-size: px2rpx(16);
  363. &[disabled] {
  364. background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
  365. border: 1px solid #ebeef5;
  366. color: #c0c4cc;
  367. cursor: not-allowed;
  368. }
  369. }
  370. }
  371. .submit-bar {
  372. margin-top: px2rpx(20);
  373. .submit-btn {
  374. width: 100%;
  375. }
  376. }
  377. .result-dialog {
  378. text-align: center;
  379. padding: px2rpx(20);
  380. .icon-wrap {
  381. margin-bottom: px2rpx(20);
  382. }
  383. .result-text {
  384. font-size: px2rpx(16);
  385. font-weight: bold;
  386. margin-top: px2rpx(10);
  387. line-height: 1.5;
  388. }
  389. .dialog-footer {
  390. display: flex;
  391. justify-content: center;
  392. gap: px2rpx(15);
  393. margin-top: px2rpx(30);
  394. button {
  395. min-width: px2rpx(100);
  396. margin: 0;
  397. }
  398. }
  399. }
  400. </style>