settingPammManager.vue 13 KB

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