Dialog.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <template>
  2. <!-- 查看他人的分享时,保存文件到个人网盘 -->
  3. <a-modal
  4. title="保存文件到网盘"
  5. :visible="internalVisible"
  6. :confirm-loading="sureBtnLoading"
  7. @ok="handleDialogSure"
  8. @cancel="handleDialogClose"
  9. :afterClose="handleDialogClose"
  10. :maskClosable="false"
  11. >
  12. <div class="dialog-div-custom">
  13. <!-- 选择的目标路径 -->
  14. <div class="target-path">
  15. <span class="label">目标路径:</span>
  16. <a-input class="content" v-model:value="targetPath" readonly size="small"></a-input>
  17. </div>
  18. <!-- 文件目录树 -->
  19. <a-spin :spinning="loading">
  20. <a-tree
  21. :tree-data="fileTree"
  22. :field-names="{ children: 'children', title: 'label', key: 'id' }"
  23. highlight-current
  24. :expandedKeys="defaultExpandedKeys"
  25. @update:expandedKeys="(val) => (defaultExpandedKeys = val)"
  26. node-key="id"
  27. @select="handleNodeClick"
  28. :showLine="true"
  29. >
  30. <template #title="{ dataRef }">
  31. <span class="custom-tree-node">
  32. <span class="label">{{ dataRef.label }}</span>
  33. <a-button class="add-folder-btn" type="link" size="small" @click.stop="handleAddFolderBtnClick(dataRef)">
  34. 新建文件夹
  35. </a-button>
  36. </span>
  37. </template>
  38. </a-tree>
  39. </a-spin>
  40. </div>
  41. <template #footer>
  42. <a-button @click="handleDialogClose">取 消</a-button>
  43. <a-button type="primary" :loading="sureBtnLoading" @click="handleDialogSure"> 确 定 </a-button>
  44. </template>
  45. </a-modal>
  46. </template>
  47. <script setup>
  48. import { ref, getCurrentInstance, watch } from 'vue'
  49. import { getFoldTree, saveShareFile } from '@/api/myResource/file'
  50. import { message } from 'ant-design-vue'
  51. const { proxy } = getCurrentInstance()
  52. const props = defineProps({
  53. visible: {
  54. type: Boolean,
  55. default: false
  56. },
  57. userFileIds: {
  58. type: String,
  59. default: ''
  60. },
  61. shareBatchNum: {
  62. type: String,
  63. default: ''
  64. },
  65. callback: Function
  66. })
  67. const emit = defineEmits(['update:visible'])
  68. const internalVisible = ref(props.visible) // 使用 internalVisible 作为主要的响应式状态
  69. const targetPath = ref('/') // 目标路径
  70. const fileTree = ref([]) // 文件夹目录树
  71. const loading = ref(false) // 文件夹目录树 loading 状态
  72. const defaultExpandedKeys = ref([])
  73. const sureBtnLoading = ref(false) // 确定按钮 loading 状态
  74. watch(
  75. internalVisible, // 监听内部的 internalVisible ref
  76. (newVal, oldVal) => {
  77. if (newVal && !oldVal) {
  78. // 仅在从 false 变为 true 时调用,即对话框打开时
  79. handleDialogOpen()
  80. }
  81. }
  82. )
  83. // 如果父组件通过 prop 更新 visible,也需要同步到 internalVisible
  84. watch(
  85. () => props.visible,
  86. (newVal) => {
  87. internalVisible.value = newVal
  88. }
  89. )
  90. /**
  91. * 取消按钮点击事件 & 对话框关闭的回调
  92. */
  93. const handleDialogClose = () => {
  94. internalVisible.value = false
  95. emit('update:visible', false) // 如果父组件需要 .sync 修饰符或者 v-model:visible
  96. props.callback('cancel')
  97. }
  98. /**
  99. * 对话框打开的回调
  100. */
  101. const handleDialogOpen = () => {
  102. initFileTree()
  103. targetPath.value = '/' // 重置目标路径
  104. }
  105. /**
  106. * 初始化文件目录树
  107. */
  108. const initFileTree = async (id) => {
  109. loading.value = true
  110. try {
  111. const res = await getFoldTree()
  112. if (res.success) {
  113. fileTree.value = [res.data]
  114. defaultExpandedKeys.value = id ? [id] : fileTree.value.length > 0 ? [fileTree.value[0].id] : []
  115. } else {
  116. message.error(res.message)
  117. }
  118. } catch (error) {
  119. message.error('获取文件夹树失败')
  120. console.error(error)
  121. } finally {
  122. loading.value = false
  123. }
  124. }
  125. /**
  126. * 目录树节点点击回调函数
  127. * @param {Array} selectedKeys 当前选中的节点的 key
  128. * @param {object} e event 对象,包含 {selected: bool, selectedNodes, node, event}
  129. */
  130. const handleNodeClick = (selectedKeys, e) => {
  131. if (e.node && e.node.dataRef) {
  132. targetPath.value = e.node.dataRef.filePath ? e.node.dataRef.filePath : '/'
  133. }
  134. }
  135. /**
  136. * 新建文件夹按钮点击事件
  137. * @description 调用新建文件夹服务,并在弹窗确认回调事件中刷新文件夹树
  138. */
  139. const handleAddFolderBtnClick = async (data) => {
  140. try {
  141. await proxy.$openDialog.addFolder({
  142. filePath: data.filePath || '/'
  143. })
  144. initFileTree(data.id)
  145. } catch (error) {
  146. console.log('Add folder cancelled or failed', error)
  147. }
  148. }
  149. /**
  150. * 确定按钮点击事件
  151. * @description 调用保存分享文件接口
  152. */
  153. const handleDialogSure = async () => {
  154. sureBtnLoading.value = true
  155. try {
  156. const res = await saveShareFile({
  157. filePath: targetPath.value,
  158. userFileIds: props.userFileIds,
  159. shareBatchNum: props.shareBatchNum
  160. })
  161. if (res.success) {
  162. message.success('保存成功')
  163. internalVisible.value = false
  164. emit('update:visible', false)
  165. props.callback('confirm')
  166. } else {
  167. message.error(res.message)
  168. }
  169. } catch (error) {
  170. message.error('保存文件失败')
  171. console.error(error)
  172. } finally {
  173. sureBtnLoading.value = false
  174. }
  175. }
  176. // 暴露给模板或者父组件(如果需要ref访问)
  177. defineExpose({
  178. visible: internalVisible // 暴露 internalVisible
  179. })
  180. </script>
  181. <style lang="less" scoped>
  182. @import '@/style/myResource/varibles.less';
  183. @import '@/style/myResource/mixins.less';
  184. // 使用 :deep 替代 >>>
  185. :deep(.ant-modal) {
  186. .ant-modal-header {
  187. display: flex; // antd 默认就是 flex,可能不需要
  188. }
  189. .ant-modal-body {
  190. padding: 10px 30px;
  191. .dialog-div-custom {
  192. // 修改类名以避免与 antd 内部类名冲突
  193. height: 300px;
  194. overflow: auto;
  195. &::-webkit-scrollbar {
  196. width: 6px;
  197. }
  198. &::-webkit-scrollbar-thumb {
  199. background: #ccc; // 滚动条颜色
  200. border-radius: 3px;
  201. }
  202. &::-webkit-scrollbar-track {
  203. background: #f1f1f1; // 轨道颜色
  204. }
  205. .target-path {
  206. display: flex;
  207. align-items: center;
  208. margin-bottom: 10px; // 增加一些间距
  209. .label {
  210. width: 80px;
  211. flex-shrink: 0; // 防止被压缩
  212. }
  213. .content {
  214. flex: 1;
  215. }
  216. }
  217. .ant-tree {
  218. .ant-tree-treenode {
  219. width: 100%;
  220. // antd v4+ 使用 .ant-tree-treenode
  221. .ant-tree-node-content-wrapper {
  222. height: 34px;
  223. font-size: 16px;
  224. display: flex; // 确保自定义内容能正确布局
  225. align-items: center;
  226. }
  227. &:hover {
  228. .add-folder-btn {
  229. display: inline-block; // 或者 block,取决于布局需求
  230. }
  231. }
  232. .ant-tree-switcher .ant-tree-switcher-icon svg {
  233. font-size: 18px; // 调整展开/折叠图标大小
  234. }
  235. .custom-tree-node {
  236. flex: 1;
  237. display: flex;
  238. align-items: center;
  239. justify-content: space-between;
  240. font-size: 14px;
  241. padding-right: 8px;
  242. .add-folder-btn {
  243. color: #000000;
  244. display: none; // 默认隐藏
  245. margin-left: 8px; // 给按钮一些左边距
  246. }
  247. }
  248. }
  249. }
  250. }
  251. }
  252. }
  253. :deep(.ant-tree .ant-tree-treenode .custom-tree-node) {
  254. display: flex;
  255. justify-content: space-between;
  256. width: 100%;
  257. }
  258. :deep(.ant-tree .ant-tree-treenode:hover .custom-tree-node .add-folder-btn) {
  259. color: #29175b !important;
  260. display: inline-block !important;
  261. }
  262. :deep(.ant-tree .ant-tree-treenode .custom-tree-node .add-folder-btn) {
  263. color: #000000;
  264. display: none !important;
  265. margin-left: 8px;
  266. }
  267. :deep(.ant-tree) {
  268. .ant-tree-treenode,
  269. .ant-tree-node-content-wrapper,
  270. .ant-tree-title {
  271. width: 100%;
  272. }
  273. }
  274. </style>