index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <div class="page">
  3. <el-card shadow="nover">
  4. <h2>远程配置</h2>
  5. <div class="container">
  6. <div class="selectContainer">
  7. <article style="margin-right: 20px">
  8. <span class="label">产品:</span>
  9. <el-select v-model="product" filterable @change="handleChange">
  10. <el-option v-for="item in productOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
  11. </el-select>
  12. </article>
  13. <article>
  14. <span class="label">配置格式:</span>
  15. <el-select v-model="currentConfig.configFormat" filterable>
  16. <el-option v-for="item in configTypeOptions" :key="item.key" :label="item.key" :value="item.key"> </el-option>
  17. </el-select>
  18. </article>
  19. </div>
  20. <div class="editorTitle">
  21. <div class="leftLabel">
  22. 配置模板
  23. <span style="color: #999; font-size: 12px; font-weight: normal; display: inline-block; margin-left: 10px; margin-right: 50px">当前文件大小{{ size }}KB(上限64KB)</span>
  24. <span style="color: #999; font-size: 14px; font-weight: normal" v-if="currentConfig.gmtCreate">提交于{{ currentConfig.gmtCreate }}</span>
  25. </div>
  26. <div class="rightLabel">
  27. <el-icon size="18" class="icon">
  28. <question-filled />
  29. </el-icon>
  30. <span v-if="onlineOpen">远程配置已经打开</span>
  31. <span v-if="!onlineOpen">远程配置已经关闭</span>
  32. <el-switch v-model="onlineOpen" active-color="#13ce66" inactive-color="#ddd"></el-switch>
  33. </div>
  34. </div>
  35. <!-- <div style="border: 1px solid #ccc; border-top: none">-->
  36. <div style="border-top: none">
  37. <codeEditor class="params flex1" ref="mirrorRef" :mode="currentConfig.configFormat" :onchange="onchange" :readOnly="true" :content="currentConfig.configContent" :cursorBlinkRate="-1"></codeEditor>
  38. </div>
  39. <div class="btnContainer">
  40. <div v-if="!onlineOpen">
  41. <el-tooltip class="box-item" effect="dark" placement="top" content="请先开启远程配置">
  42. <div style="margin-right: 20px"><el-button type="primary" disabled>编辑</el-button></div>
  43. </el-tooltip>
  44. </div>
  45. <div v-else style="margin-right: 20px"><el-button type="primary" @click="edit">编辑</el-button></div>
  46. <div v-if="canSave"><el-button type="primary" @click="saveDialogVisible = true">保存</el-button></div>
  47. <div v-else><el-button type="primary" @click="updateDialogVisible = true">批量更新</el-button></div>
  48. </div>
  49. <h2>配置版本记录</h2>
  50. <el-table :data="tableData.data" style="width: 100%" row-key="id" default-expand-all :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="tableData.loading">
  51. <el-table-column prop="configNumber" label="编号" width="200"> </el-table-column>
  52. <el-table-column prop="gmtCreate" label="版本更新时间"></el-table-column>
  53. <el-table-column label="操作" width="200">
  54. <template #default="scope">
  55. <el-button size="small" type="text" @click="look(scope.row, scope.$index)">查看</el-button>
  56. </template>
  57. </el-table-column>
  58. </el-table>
  59. </div>
  60. </el-card>
  61. <el-dialog v-model="updateDialogVisible" width="40%">
  62. <div class="saveDialogContainer">
  63. <div class="icon">
  64. <el-icon color="#E6A23C" size="26">
  65. <warning-filled />
  66. </el-icon>
  67. </div>
  68. <div class="string">
  69. <h2>是否确认对该产品下的所有设备进行批量远程配置更新</h2>
  70. <p>注:该产品下的所有设备将自动更新该配置文件.设备端需订阅远程配置的topic</p>
  71. <p>指定产品:{{ selectProduct }}</p>
  72. <p>设备范围:所有设备</p>
  73. </div>
  74. </div>
  75. <template #footer>
  76. <span class="dialog-footer">
  77. <el-button @click="updateDialogHandle" type="primary">确认更新</el-button>
  78. <el-button @click="updateDialogVisible = false">取消</el-button>
  79. </span>
  80. </template>
  81. </el-dialog>
  82. <el-dialog v-model="saveDialogVisible" width="40%">
  83. <div class="saveDialogContainer">
  84. <div class="icon">
  85. <el-icon color="#E6A23C" size="26">
  86. <warning-filled />
  87. </el-icon>
  88. </div>
  89. <div class="string">
  90. <h3>是否保存当前配置信息? 保存后可以手动将配置批量更新到该产品下的所有设备,设备也可以主动获取配置</h3>
  91. </div>
  92. </div>
  93. <template #footer>
  94. <span class="dialog-footer">
  95. <el-button @click="saveHandle" type="primary">确认</el-button>
  96. <el-button @click="saveDialogVisible = false">取消</el-button>
  97. </span>
  98. </template>
  99. </el-dialog>
  100. <el-dialog v-model="lookDialogVisible" width="40%">
  101. <div class="saveDialogContainer" v-if="lookDialogVisible">
  102. <codeEditor class="params flex1" ref="mirrorRefDialog" mode="" :onchange="onchange" :readOnly="true" :content="tableData.data[lookIndex!].content" :cursorBlinkRate="-1"></codeEditor>
  103. </div>
  104. <template #footer>
  105. <span class="dialog-footer">
  106. <el-button @click="lookSureDialogVisible = true" type="primary">恢复至此版本</el-button>
  107. <el-button @click="lookDialogVisible = false">取消</el-button>
  108. </span>
  109. </template>
  110. </el-dialog>
  111. <el-dialog v-model="lookSureDialogVisible" width="40%">
  112. <div class="saveDialogContainer">
  113. <div class="icon">
  114. <el-icon color="#E6A23C" size="26">
  115. <warning-filled />
  116. </el-icon>
  117. </div>
  118. <div class="string">
  119. <h3>恢复版本</h3>
  120. <p>是否确认恢复到{{ tableData.data[lookIndex!].time }}的版本</p>
  121. </div>
  122. </div>
  123. <template #footer>
  124. <span class="dialog-footer">
  125. <el-button @click="sureRestore" type="primary">确认</el-button>
  126. <el-button @click="lookSureDialogVisible = false">取消</el-button>
  127. </span>
  128. </template>
  129. </el-dialog>
  130. </div>
  131. </template>
  132. <script lang="ts">
  133. import { reactive, onMounted, ref, defineComponent, watch, nextTick } from 'vue'
  134. import { ElMessage } from 'element-plus'
  135. import { QuestionFilled, WarningFilled } from '@element-plus/icons-vue'
  136. import api from '/@/api/system'
  137. import codeEditor from '/@/components/codeEditor/index.vue'
  138. export default defineComponent({
  139. name: 'online',
  140. components: { QuestionFilled, WarningFilled, codeEditor },
  141. setup() {
  142. const product = ref()
  143. const productOptions = ref<{ value: string; label: string; status: string }[]>([])
  144. const canSave = ref(false)
  145. const onlineOpen = ref(false)
  146. const mirrorRef = ref<any>() // 编辑器
  147. const mirrorRefDialog = ref<any>()
  148. const configTypeOptions = [{ key: 'application/json' }, { key: 'text/yaml' }]
  149. const selectProduct = ref(); // 选中产品名称
  150. const currentConfig = ref({
  151. configContent: '',
  152. configFormat: 'application/json',
  153. configNumber: '',
  154. gmtCreate: '',
  155. id: '',
  156. scope: '',
  157. })
  158. // 页面加载时
  159. onMounted(() => {
  160. api.remoteconf.getProductList({ status: '1', name: '' }).then((res: any) => {
  161. if (res.product) {
  162. productOptions.value = res.product.map((item: any) => {
  163. return {
  164. value: item.key,
  165. label: item.name,
  166. status: item.status,
  167. }
  168. })
  169. if (productOptions.value.length > 0) {
  170. product.value = productOptions.value[0].value
  171. selectProduct.value = productOptions.value[0].label
  172. }
  173. }
  174. })
  175. })
  176. const edit = () => {
  177. mirrorRef.value.setOption('readOnly', false)
  178. // 显示光标
  179. mirrorRef.value.setOption('cursorBlinkRate', 530);
  180. canSave.value = true
  181. }
  182. const saveDialogVisible = ref(false)
  183. const updateDialogVisible = ref(false)
  184. watch(
  185. () => onlineOpen.value,
  186. () => {
  187. //初始化 然后由false变为 true 不用管
  188. // 这里是 由false变为true
  189. if (!onlineOpen.value) {
  190. canSave.value = false
  191. mirrorRef.value.setOption('readOnly', true)
  192. // 隐藏光标
  193. mirrorRef.value.setOption('cursorBlinkRate', -1);
  194. }
  195. }
  196. )
  197. watch(() => product.value, () => {
  198. //product.value 就是那个key options里面转化了
  199. currentConfig.value.gmtCreate = '';
  200. mirrorRef.value.setValue('');
  201. tableData.data = [];
  202. updateTableData()
  203. })
  204. // 选择下拉框替换批量更新产品名称
  205. const handleChange = (value) => {
  206. selectProduct.value = productOptions.value.find((item) => item.value === value).label;
  207. }
  208. const updateTableData = () => {
  209. api.remoteconf.queryThingConfig({ pageNum: 1, PageSize: 50, productKey: product.value })
  210. .then((res: any) => {
  211. if (res.remoteconf) {
  212. // tableData.data = res.remoteconf.slice(1)
  213. tableData.data = res.remoteconf
  214. // 重塑编号
  215. for (let i = 0; i < tableData.data.length; i++) {
  216. if ((i + 1) < 10) {
  217. tableData.data[i].configNumber = '0' + (i + 1);
  218. } else {
  219. tableData.data[i].configNumber = i + 1;
  220. }
  221. }
  222. currentConfig.value = res.remoteconf[0]
  223. mirrorRef.value.setValue(currentConfig.value.configContent)
  224. }
  225. }).catch((error: any) => {
  226. ElMessage.error(error)
  227. })
  228. }
  229. const size = ref('0')
  230. const saveHandle = () => {
  231. saveDialogVisible.value = false
  232. // let now = new Date()
  233. // let year: number | string = now.getFullYear()
  234. // let month: number | string = now.getMonth() + 1 // getMonth() 返回的月份从 0 开始,所以需要 +1
  235. // let day: number | string = now.getDate()
  236. // let hour: number | string = now.getHours()
  237. // let minute: number | string = now.getMinutes()
  238. // let second: number | string = now.getSeconds()
  239. // // 如果月、日、小时、分钟或秒的值小于 10,前面补 0
  240. // if (month < 10) month = '0' + month
  241. // if (day < 10) day = '0' + day
  242. // if (hour < 10) hour = '0' + hour
  243. // if (minute < 10) minute = '0' + minute
  244. // if (second < 10) second = '0' + second
  245. // updateTime.value = year + '/' + month + '/' + day + ' ' + hour + ':' + minute + ':' + second
  246. api.remoteconf.saveThisConfig({
  247. scope: 'product',
  248. configName: '',
  249. configFormat: currentConfig.value.configFormat,
  250. configContent: mirrorRef.value.getValue(),
  251. configSize: size.value,
  252. status: productOptions.value.find((item: any) => item.value == product.value)?.status!,
  253. productKey: product.value,
  254. }).then((res: any) => {
  255. ElMessage.success('保存成功')
  256. onlineOpen.value = false
  257. updateTableData()
  258. }).catch((err: any) => {
  259. ElMessage.error(err)
  260. })
  261. }
  262. const onchange = (e: any, b: any) => {
  263. var blob = new Blob([e.getValue()], { type: 'text/plain' })
  264. size.value = (blob.size / 1024).toFixed(2)
  265. }
  266. const tableData = reactive({
  267. data: <any[]>[],
  268. loading: false,
  269. })
  270. const lookIndex = ref<null | number>(0)
  271. const lookDialogVisible = ref(false)
  272. const lookSureDialogVisible = ref(false)
  273. const look = (item: any, index: number) => {
  274. lookDialogVisible.value = true
  275. lookIndex.value = index
  276. setTimeout(() => {
  277. mirrorRefDialog.value.setMode(tableData.data[lookIndex.value!].configFormat)
  278. mirrorRefDialog.value.setValue(tableData.data[lookIndex.value!].configContent)
  279. }, 0)
  280. }
  281. const sureRestore = () => {
  282. lookDialogVisible.value = false
  283. lookSureDialogVisible.value = false
  284. currentConfig.value.configFormat = tableData.data[lookIndex.value!].configFormat
  285. mirrorRef.value.setValue(tableData.data[lookIndex.value!].configContent)
  286. nextTick(() => {
  287. saveHandle()
  288. })
  289. }
  290. const updateDialogHandle = () => {
  291. updateDialogVisible.value = false
  292. saveHandle()
  293. }
  294. return {
  295. selectProduct,
  296. productOptions,
  297. product,
  298. canSave,
  299. edit,
  300. onlineOpen,
  301. updateDialogVisible,
  302. saveDialogVisible,
  303. mirrorRef,
  304. size,
  305. saveHandle,
  306. onchange,
  307. tableData,
  308. look,
  309. lookIndex,
  310. lookDialogVisible,
  311. lookSureDialogVisible,
  312. sureRestore,
  313. mirrorRefDialog,
  314. configTypeOptions,
  315. updateDialogHandle,
  316. currentConfig,
  317. handleChange,
  318. }
  319. },
  320. })
  321. </script>
  322. <style scoped lang="scss">
  323. .selectContainer {
  324. display: flex;
  325. }
  326. .label {
  327. display: inline-block;
  328. padding-right: 12px;
  329. }
  330. h2 {
  331. margin-bottom: 10px;
  332. }
  333. article {
  334. margin-bottom: 10px;
  335. }
  336. .container :deep(.ql-toolbar.ql-snow) {
  337. display: none;
  338. }
  339. .editorTitle {
  340. width: 100%;
  341. //border: 1px solid #ccc;
  342. padding: 10px;
  343. height: 50px //background-color: #eee;
  344. }
  345. .leftLabel {
  346. float: left;
  347. height: 32px;
  348. line-height: 32px;
  349. font-weight: 600;
  350. }
  351. .rightLabel {
  352. float: right;
  353. span {
  354. display: inline-block;
  355. margin: 0 10px;
  356. }
  357. .icon {
  358. float: left;
  359. margin-top: 6px;
  360. }
  361. }
  362. .btnContainer {
  363. width: 200px;
  364. margin: 10px auto;
  365. display: flex;
  366. }
  367. .saveDialogContainer {
  368. display: flex;
  369. h2 {
  370. color: #333;
  371. }
  372. p {
  373. color: #999;
  374. margin: 8px 0;
  375. }
  376. .icon {
  377. margin: 2px 10px;
  378. }
  379. }
  380. </style>