settingPammManager.vue 13 KB

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