settingPammManager.vue 13 KB

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