Header.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. <template>
  2. <div id="dashboard-header" class="wrap d-flex align-items-center">
  3. <draggable v-model="options" tag="ul" class="d-flex flex-fill flex-wrap list-unstyled m-0" handle=".handle">
  4. <li
  5. class="item"
  6. v-for="item in options"
  7. :key="item.id"
  8. :class="{ active: current.id === item.id }"
  9. @click.stop.prevent="$emit('select', item)">
  10. <icon type="move" class="handle" />
  11. <span>{{ item.name }}</span>
  12. </li>
  13. </draggable>
  14. <data-range v-if="(isAdminMode || isDomainMode) && $appConfig.isPrivate && !$store.getters.isSysCE" :dataRangeParams="dataRangeParams" @updateDataRange="updateDataRange" />
  15. <a-button @click="handleRefresh" type="link" class="action-btn">
  16. <icon type="refresh" />
  17. </a-button>
  18. <a-dropdown v-if="showActions.length" :trigger="['click']" slot="tabBarExtraContent" placement="bottomRight">
  19. <a class="ant-dropdown-link font-weight-bold pl-2 pr-2 h-100 d-block action-btn" @click="e => e.preventDefault()">
  20. <icon type="more" style="font-size: 18px;" />
  21. </a>
  22. <a-menu slot="overlay" @click="handleActionClick">
  23. <a-menu-item key="handleCreate" v-if="showActions.includes('create')"><a-icon type="plus" />{{$t('dashboard.text_103')}}</a-menu-item>
  24. <a-menu-item key="handleEdit" v-if="showActions.includes('edit')"><a-icon type="edit" />{{$t('dashboard.text_104')}}</a-menu-item>
  25. <a-menu-item key="handleDownload" v-if="showActions.includes('export')"><a-icon type="download" />{{$t('dashboard.text_105')}}</a-menu-item>
  26. <a-menu-item key="handleImport" v-if="showActions.includes('import')"><a-icon type="file" />{{$t('dashboard.text_106')}}</a-menu-item>
  27. <a-menu-item key="handleCopy" v-if="showActions.includes('clone')"><a-icon type="copy" />{{$t('dashboard.text_107')}}</a-menu-item>
  28. <a-menu-item key="handleShare" v-if="isAdminRole && isDefaultOption && showActions.includes('share')"><a-icon type="share-alt" />{{$t('common_104', [''])}}</a-menu-item>
  29. <a-menu-item key="handleDelete" v-if="showActions.includes('reset')"><a-icon type="delete" />{{deleteText}}</a-menu-item>
  30. </a-menu>
  31. </a-dropdown>
  32. </div>
  33. </template>
  34. <script>
  35. import * as R from 'ramda'
  36. import { mapGetters } from 'vuex'
  37. import { Base64 } from 'js-base64'
  38. import draggable from 'vuedraggable'
  39. import { download, uuid } from '@/utils/utils'
  40. import WindowsMixin from '@/mixins/windows'
  41. import DataRange from './DataRange.vue'
  42. export default {
  43. name: 'DashboardHeader',
  44. components: {
  45. draggable,
  46. DataRange,
  47. },
  48. mixins: [WindowsMixin],
  49. props: {
  50. tabs: {
  51. type: Array,
  52. required: true,
  53. },
  54. current: {
  55. type: Object,
  56. required: true,
  57. },
  58. // 当前卡片配置
  59. data: {
  60. type: [Array, Object],
  61. required: true,
  62. },
  63. // 检查options配置是否创建过
  64. checkOptionsCreated: {
  65. type: Function,
  66. required: true,
  67. },
  68. // 创建初始化配置
  69. initOptions: {
  70. type: Function,
  71. required: true,
  72. },
  73. // 是否为默认面板
  74. isDefaultOption: Boolean,
  75. dataRangeParams: {
  76. type: Object,
  77. },
  78. },
  79. data () {
  80. return {
  81. isPrivate: process.env.VUE_APP_IS_PRIVATE,
  82. }
  83. },
  84. computed: {
  85. ...mapGetters(['scope', 'userInfo', 'isAdminMode', 'isDomainMode']),
  86. options: {
  87. get () {
  88. return this.tabs
  89. },
  90. set (value) {
  91. this.$emit('update-options', value)
  92. },
  93. },
  94. // 是否只有一个标签
  95. isSingle () {
  96. return this.tabs.length === 1
  97. },
  98. deleteText () {
  99. if (this.isDefaultOption) {
  100. return this.$t('common.reset')
  101. } else {
  102. return this.$t('dashboard.text_108')
  103. }
  104. },
  105. showActions () {
  106. return (['create', 'edit', 'export', 'import', 'share', 'reset', 'clone']).filter(key => {
  107. return !this.$isScopedPolicyMenuHidden(`dashboard_hidden_actions.${key}`)
  108. })
  109. },
  110. isAdminRole () {
  111. const { projects = [] } = this.userInfo
  112. const systemProj = projects.find(o => o.name === 'system')
  113. return !!systemProj
  114. },
  115. },
  116. beforeDestroy () {
  117. this.pm = null
  118. },
  119. created () {
  120. this.pm = new this.$Manager('parameters', 'v1')
  121. },
  122. methods: {
  123. updateDataRange (params) {
  124. this.$emit('updateDataRange', params)
  125. },
  126. handleActionClick ({ key }) {
  127. if (this[key]) this[key]()
  128. },
  129. handleCreate () {
  130. this.$router.push({ name: 'DashboardEdit' })
  131. },
  132. handleEdit () {
  133. this.$router.push({ name: 'DashboardEdit', query: { id: this.current.id } })
  134. },
  135. handleDownload () {
  136. const name = `${this.$t(`policyScopeLabel.${this.scope}`)}_${this.current.name}.ocdb`
  137. const data = this.genExportData()
  138. download(data, name)
  139. },
  140. handleImport () {
  141. this.createDialog('DashboardImport', {
  142. title: this.$t('dashboard.text_106'),
  143. options: this.options,
  144. genName: name => this.genName(name),
  145. checkOptionsCreated: () => this.checkOptionsCreated(),
  146. initOptions: () => this.initOptions(),
  147. updateOptions: options => this.$emit('update-options', options),
  148. selectOption: item => {
  149. this.$emit('select', item)
  150. },
  151. })
  152. },
  153. handleDelete () {
  154. this.createDialog('CommonDialog', {
  155. header: this.deleteText,
  156. body: () => {
  157. let number
  158. if (R.is(Array, this.data)) {
  159. number = this.data.length
  160. } else {
  161. number = Object.keys(this.data).length
  162. }
  163. const data = [{
  164. ...this.current,
  165. number,
  166. }]
  167. return [
  168. <dialog-selected-tips count={1} action={this.deleteText} name={this.$t('dashboard.text_109')} />,
  169. <dialog-table
  170. vxeGridProps={{ showOverflow: 'title' }}
  171. data={ data }
  172. columns={
  173. [
  174. {
  175. field: 'name',
  176. title: this.$t('dashboard.text_110'),
  177. },
  178. {
  179. field: 'number',
  180. title: this.$t('dashboard.text_111'),
  181. },
  182. ]
  183. } />,
  184. ]
  185. },
  186. ok: async () => {
  187. try {
  188. const newOptions = [...this.options]
  189. const index = R.findIndex(R.propEq('id', this.current.id))(newOptions)
  190. if (index !== -1) {
  191. newOptions.splice(index, 1)
  192. }
  193. if (newOptions.length) {
  194. await this.$emit('update-options', newOptions)
  195. }
  196. await this.pm.delete({
  197. id: this.current.id,
  198. })
  199. // 使用删除后的列表首项,避免 update-options 尚未反映到 tabs 时仍用旧的 options[0]
  200. const next = newOptions.length ? newOptions[0] : this.options[0]
  201. this.$emit('select', next)
  202. } catch (error) {
  203. throw error
  204. }
  205. },
  206. })
  207. },
  208. async handleCopy () {
  209. try {
  210. // 检查是否已经创建了dashboard配置
  211. const optionsCreated = await this.checkOptionsCreated()
  212. if (!optionsCreated) {
  213. await this.initOptions()
  214. }
  215. // 更新options
  216. const newOptions = [...this.options]
  217. const id = `dashboard-${this.scope}-panel-${uuid(16)}`
  218. const item = {
  219. name: this.genName(this.current.name),
  220. id,
  221. }
  222. newOptions.push(item)
  223. await this.$emit('update-options', newOptions)
  224. // 创建panel数据配置
  225. const panelData = {}
  226. if (R.is(Array, this.data)) {
  227. for (let i = 0, len = this.data.length; i < len; i++) {
  228. panelData[`dashboard-item-${uuid(32)}`] = this.data[i]
  229. }
  230. } else {
  231. for (const key in this.data) {
  232. panelData[`dashboard-item-${uuid(32)}`] = this.data[key]
  233. }
  234. }
  235. await this.pm.create({
  236. data: {
  237. name: id,
  238. value: panelData,
  239. },
  240. })
  241. this.$emit('select', item)
  242. } catch (error) {
  243. throw error
  244. }
  245. },
  246. // 生成面板名称,如果已存在默认+1
  247. genName (name) {
  248. const existNames = this.options.map(item => item.name)
  249. let num = 1
  250. let newName = name
  251. while (existNames.includes(newName)) {
  252. newName = `${newName}-${num++}`
  253. }
  254. return newName
  255. },
  256. // 生成导出及克隆的数据
  257. genExportData () {
  258. const ret = {
  259. scope: this.scope,
  260. name: this.current.name,
  261. items: [],
  262. }
  263. R.forEachObjIndexed((value, key) => {
  264. ret.items.push(value)
  265. }, this.data)
  266. const data = Base64.encode(JSON.stringify(ret))
  267. return data
  268. },
  269. handleRefresh () {
  270. this.$emit('refresh')
  271. },
  272. handleShare () {
  273. this.createDialog('CommonDialog', {
  274. header: this.$t('common_104', ['']),
  275. body: () => {
  276. return [
  277. <a-alert class="mb-2" type="warning">
  278. <div slot="message">{ this.$t('dashbaord.panel_shared_tip') }</div>
  279. </a-alert>,
  280. <dialog-selected-tips count={1} action={this.$t('common_104', [''])} name={this.$t('dashboard.text_109')} />,
  281. ]
  282. },
  283. ok: async () => {
  284. await this.$store.dispatch('widgetSetting/putFetchWidgetSettingValue', {
  285. [`dashboard-${this.scope}`]: this.data,
  286. })
  287. this.$message.success(this.$t('compute.text_423'))
  288. },
  289. })
  290. },
  291. },
  292. }
  293. </script>
  294. <style lang="less" scoped>
  295. @import '~@/styles/less/theme';
  296. .wrap {
  297. border-bottom: 1px solid #EDEDED;
  298. }
  299. .item {
  300. padding: 12px 16px;
  301. margin: 0 32px 0 0;
  302. cursor: pointer;
  303. position: relative;
  304. color: rgba(0, 0, 0, 0.45);
  305. &:last-child {
  306. margin-right: 0;
  307. }
  308. &.active {
  309. border-bottom: 1px solid @primary-color;
  310. color: @primary-color;
  311. font-weight: bold;
  312. }
  313. &:hover {
  314. color: @primary-color;
  315. .handle {
  316. visibility: visible;
  317. }
  318. }
  319. .handle {
  320. cursor: move;
  321. visibility: hidden;
  322. position: absolute;
  323. left: 0;
  324. top: 4px;
  325. color: rgba(0, 0, 0, 0.65);
  326. }
  327. }
  328. .action-btn {
  329. color: rgba(0, 0, 0, 0.65);
  330. &:hover {
  331. color: #40a9ff;
  332. }
  333. }
  334. </style>