OCPPOperations.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <template>
  2. <div class="card-container">
  3. <div class="card-content">
  4. <div class="ocpp-content">
  5. <div class="section-title">Selected Stations</div>
  6. <div class="flex-item">
  7. <span style="padding-right: 15px;">Site: </span>
  8. <el-select
  9. class="flex1"
  10. filterable
  11. remote
  12. v-model="listQuery.pageVo.sitePk"
  13. :remote-method="filterSite"
  14. :loading="siteLoading"
  15. placeholder="Select with search"
  16. @change="changeSite">
  17. <el-option
  18. v-for="(item, index) in siteList"
  19. :key="index"
  20. :label="item.siteName"
  21. :value="item.sitePk"/>
  22. </el-select>
  23. <span class="flex1"></span>
  24. </div>
  25. <div class="flex-item" style="padding: 20px 0; justify-content: flex-end;">
  26. <el-button
  27. type="primary"
  28. @click="selectNone">
  29. Select None
  30. </el-button>
  31. <el-button
  32. style="margin-left: 15px;"
  33. type="primary"
  34. @click="selectAll">
  35. Select All
  36. </el-button>
  37. </div>
  38. <el-table
  39. style="width: 100%;min-height: 50vh;"
  40. v-loading="listLoading"
  41. :data="stationList">
  42. <el-table-column
  43. :label="item.label"
  44. align="center"
  45. class-name="fixed-width"
  46. v-for="(item, index) in columns"
  47. :key="index"
  48. :width="item.width">
  49. <template slot-scope="{row, $index}">
  50. <el-checkbox
  51. v-if="item.action"
  52. v-model="row[item.key]"
  53. @change="checked => changeRow($index, checked)"/>
  54. <span v-else>{{ row[item.key] }}</span>
  55. </template>
  56. </el-table-column>
  57. </el-table>
  58. <div class="right">
  59. <pagination
  60. v-show="total > 0"
  61. :total="total"
  62. :page.sync="listQuery.pageNo"
  63. :limit.sync="listQuery.pageSize"
  64. @pagination="handlePageChange" />
  65. </div>
  66. </div>
  67. <div class="ocpp-content">
  68. <el-form
  69. :model="operationForm"
  70. :rules="rule"
  71. ref="operationForm"
  72. label-width="200px"
  73. label-position="left">
  74. <div class="section-title">OCPP Operation</div>
  75. <el-form-item
  76. label="Operation:"
  77. prop="operation">
  78. <el-select
  79. class="flex-item"
  80. v-model="operationForm.operation"
  81. @change="changeOperation">
  82. <el-option
  83. v-for="(item, index) in operationList"
  84. :key="index"
  85. :label="item.name"
  86. :value="index"
  87. v-if="item.api"/>
  88. </el-select>
  89. </el-form-item>
  90. <div class="section-title" style="padding-top: 20px;" v-loading="paramLoading">Parameters</div>
  91. <el-form-item
  92. label="Filter Type:"
  93. prop="filterType"
  94. v-if="paramsOption.filterTypeOption.length > 0">
  95. <el-select
  96. v-model="operationForm.filterType"
  97. class="flex-item"
  98. @change="changeFilterType">
  99. <el-option
  100. v-for="(item, index) in paramsOption.filterTypeOption"
  101. :key="index"
  102. :label="item.name"
  103. :value="item.value"/>
  104. </el-select>
  105. </el-form-item>
  106. <el-form-item
  107. label="Charging Profile ID:"
  108. prop="chargingProfilePk"
  109. v-if="paramsOption.needChargingProfile">
  110. <el-select
  111. v-model="operationForm.chargingProfilePk"
  112. class="flex-item">
  113. <el-option
  114. v-for="(item, index) in paramsOption.chargingProfileOption"
  115. :key="index"
  116. :label="item.value + ' (' + item.name + ')'"
  117. :value="item.value"/>
  118. </el-select>
  119. </el-form-item>
  120. <el-form-item
  121. label="Connector ID:"
  122. prop="connectorId"
  123. v-if="paramsOption.needConnector">
  124. <el-select
  125. v-model="operationForm.connectorId"
  126. class="flex-item">
  127. <el-option
  128. v-for="(item, index) in paramsOption.connectorIds"
  129. :key="index"
  130. :label="item"
  131. :value="item"/>
  132. </el-select>
  133. </el-form-item>
  134. <el-form-item
  135. label="Transaction ID:"
  136. prop="transactionId"
  137. v-if="paramsOption.needActConnector">
  138. <el-select
  139. v-model="operationForm.transactionId"
  140. class="flex-item">
  141. <el-option
  142. v-for="(item, index) in paramsOption.connectorActIds"
  143. :key="index"
  144. :label="item"
  145. :value="item"/>
  146. </el-select>
  147. </el-form-item>
  148. <el-form-item
  149. label="Mobile Number:"
  150. prop="idTagInfo"
  151. v-if="paramsOption.idTagAll.length > 0">
  152. <el-select
  153. v-model="operationForm.idTagInfo"
  154. filterable
  155. remote
  156. :remote-method="filterMobile"
  157. class="flex-item"
  158. :loading="tagLoading">
  159. <el-option
  160. v-for="(item, index) in paramsOption.idTags"
  161. :key="index"
  162. :label="item.name"
  163. :value="item.value"/>
  164. </el-select>
  165. </el-form-item>
  166. <el-form-item
  167. label="Availability Type:"
  168. prop="availType"
  169. v-if="paramsOption.availTypeOption.length > 0">
  170. <el-select
  171. v-model="operationForm.availType"
  172. class="flex-item"
  173. placeholder="Select">
  174. <el-option
  175. v-for="(item, index) in paramsOption.availTypeOption"
  176. :key="index"
  177. :label="item.name"
  178. :value="item.value"/>
  179. </el-select>
  180. </el-form-item>
  181. <el-form-item
  182. label="Charging Profile Purpose:"
  183. prop="chargingProfilePurpose"
  184. v-if="paramsOption.needChargingProfilePurpose">
  185. <el-select
  186. v-model="operationForm.chargingProfilePurpose"
  187. class="flex-item">
  188. <el-option
  189. v-for="(item, index) in paramsOption.chargingPurposeOption"
  190. :key="index"
  191. :label="item.name"
  192. :value="item.value"/>
  193. </el-select>
  194. </el-form-item>
  195. <el-form-item
  196. label="Stack Level (integer):"
  197. v-if="paramsOption.needChargingProfilePurpose">
  198. <el-input
  199. v-model="operationForm.stackLevel"
  200. class="flex-item"/>
  201. </el-form-item>
  202. <el-form-item
  203. label="Key Type:"
  204. prop="keyType"
  205. v-if="paramsOption.keyTypeOption.length > 0">
  206. <el-select
  207. v-model="operationForm.keyType"
  208. class="flex-item"
  209. placeholder="Select"
  210. @change="changeKeyType">
  211. <el-option
  212. v-for="(item, index) in paramsOption.keyTypeOption"
  213. :key="index"
  214. :label="item.name"
  215. :value="item.value"/>
  216. </el-select>
  217. </el-form-item>
  218. <div
  219. class="multi-select"
  220. v-if="paramsOption.confKeyOption.length > 0 && paramsOption.selectMultConfig">
  221. <span class="note-span">Info: If none selected, the charge point returns a list of all configuration settings.</span>
  222. <el-form-item
  223. label="Configuration Key:">
  224. <el-select
  225. v-model="operationForm.confKeyList"
  226. class="flex-item"
  227. style="align-items: flex-start;"
  228. placeholder="Select multiple"
  229. :multiple="true">
  230. <el-option
  231. v-for="(item, index) in paramsOption.confKeyOption"
  232. :key="index"
  233. :label="item.name"
  234. :value="item.value"/>
  235. </el-select>
  236. </el-form-item>
  237. </div>
  238. <el-form-item
  239. label="Configuration Key:"
  240. prop="confKey"
  241. v-else-if="paramsOption.confKeyOption.length > 0 && operationForm.keyType == 'PREDEFINED'">
  242. <el-select
  243. v-model="operationForm.confKey"
  244. class="flex-item"
  245. placeholder="Select">
  246. <el-option
  247. v-for="(item, index) in paramsOption.confKeyOption"
  248. :key="index"
  249. :label="item.name"
  250. :value="item.value"/>
  251. </el-select>
  252. </el-form-item>
  253. <el-form-item
  254. label="Custom Configuration Key:"
  255. prop="customConfKey"
  256. label-width="205px"
  257. v-if="paramsOption.keyTypeOption.length > 0 && operationForm.keyType == 'CUSTOM'">
  258. <el-input
  259. v-model="operationForm.customConfKey"
  260. class="flex-item"/>
  261. </el-form-item>
  262. <el-form-item
  263. label="Value:"
  264. v-if="paramsOption.keyTypeOption.length > 0">
  265. <el-input
  266. v-model="operationForm.value"
  267. class="flex-item"/>
  268. </el-form-item>
  269. <el-form-item
  270. label="Reset Type:"
  271. prop="resetType"
  272. v-if="paramsOption.resetTypeOption.length > 0">
  273. <el-select
  274. class="flex-item"
  275. v-model="operationForm.resetType">
  276. <el-option
  277. v-for="(item, index) in paramsOption.resetTypeOption"
  278. :key="index"
  279. :label="item.name"
  280. :value="item.value"/>
  281. </el-select>
  282. </el-form-item>
  283. <template v-if="operationInfo.value == 'UF'">
  284. <el-form-item
  285. label="Location(directory URI):"
  286. prop="location">
  287. <el-input
  288. v-model="operationForm.location"
  289. class="flex-item"/>
  290. </el-form-item>
  291. <el-form-item
  292. label="Retries(integer):">
  293. <el-input
  294. v-model="operationForm.retries"
  295. class="flex-item"/>
  296. </el-form-item>
  297. <el-form-item
  298. label="Retry Interval(integer):">
  299. <el-input
  300. v-model="operationForm.retryInterval"
  301. class="flex-item"/>
  302. </el-form-item>
  303. <el-form-item
  304. label="Retrieve Date/Time:"
  305. prop="retrieve">
  306. <el-date-picker
  307. v-model="operationForm.retrieve"
  308. type="datetime"
  309. format="yyyy-MM-dd HH:mm"
  310. value-format="yyyy-MM-dd HH:mm"
  311. default-time="12:00:00"
  312. class="flex-item"/>
  313. </el-form-item>
  314. </template>
  315. <div class="perform">
  316. <el-button
  317. style="margin-left: 15px;"
  318. type="primary"
  319. native-type="submit"
  320. :loading="btnLoading"
  321. @click="perform">
  322. Perform
  323. </el-button>
  324. </div>
  325. </el-form>
  326. </div>
  327. </div>
  328. </div>
  329. </template>
  330. <script>
  331. import Pagination from '@/components/Pagination'
  332. import site from '../../http/api/site'
  333. import ocpp from '../../http/api/ocpp'
  334. import util from './operationUtil'
  335. const NOT_SPECIFIC = 'Not for a specific connector'
  336. export default {
  337. data() {
  338. return {
  339. tagLoading: false,
  340. listLoading: false,
  341. siteLoading: false,
  342. paramLoading: false,
  343. btnLoading: false,
  344. siteList: [],
  345. siteKeyword: '',
  346. stationList: [],
  347. total: 0,
  348. listQuery: {
  349. pageNo: 1,
  350. pageSize: 10,
  351. pageVo: {
  352. sitePk: '',
  353. stationId: ''
  354. }
  355. },
  356. operationInfo: {},
  357. operationList: ocpp.getOperationList(),
  358. operationForm: {
  359. value: '',
  360. confKey: '',
  361. keyType: '',
  362. resetType: '',
  363. operation: '',
  364. availType: '',
  365. idTagInfo: '',
  366. filterType: '',
  367. location: "",
  368. retrieve: "",
  369. retries: "",
  370. retryInterval: "",
  371. confKeyList: [],
  372. stationIds: [],
  373. stackLevel: '',
  374. connectorId: '',
  375. transactionId: '',
  376. customConfKey: '',
  377. chargingProfilePk: '',
  378. chargingProfilePurpose: undefined
  379. },
  380. paramsOption: {
  381. idTags: [],
  382. idTagAll: [],
  383. connectorIds: [],
  384. connectorActIds: [],
  385. needConnector: false,
  386. needActConnector: false,
  387. selectMultConfig: false,
  388. confKeyOption: [],
  389. keyTypeOption: [],
  390. availTypeOption: [],
  391. resetTypeOption: [],
  392. filterTypeOption: [],
  393. needCustomCoinfig: false,
  394. needChargingProfile: false,
  395. chargingProfileOption: [],
  396. chargingPurposeOption: [],
  397. needChargingProfilePurpose: false
  398. },
  399. rule: {
  400. idTagInfo: {
  401. required: true,
  402. trigger: 'change',
  403. message: 'Please select ID tag'
  404. },
  405. operation: {
  406. required: true,
  407. trigger: 'change',
  408. message: 'Please select operation'
  409. },
  410. confKey: {
  411. required: true,
  412. trigger: 'change',
  413. message: 'Please select configuration key'
  414. },
  415. keyType: {
  416. required: true,
  417. trigger: 'change',
  418. message: 'Please select key type'
  419. },
  420. resetType: {
  421. required: true,
  422. trigger: 'change',
  423. message: 'Please select reset type'
  424. },
  425. availType: {
  426. required: true,
  427. trigger: 'change',
  428. message: 'Please select availability type'
  429. },
  430. filterType: {
  431. required: true,
  432. trigger: 'change',
  433. message: 'Please select filter type'
  434. },
  435. connectorId: {
  436. required: true,
  437. trigger: 'change',
  438. message: 'Please select a connector ID'
  439. },
  440. confKeyList: {
  441. required: true,
  442. trigger: 'change',
  443. message: 'Please select at last least 1 config key'
  444. },
  445. transactionId: {
  446. required: true,
  447. trigger: 'change',
  448. message: 'Please select a active transaction ID'
  449. },
  450. customConfKey: {
  451. required: true,
  452. trigger: 'blur',
  453. message: 'Please input custom configuration key'
  454. },
  455. chargingProfilePk: {
  456. required: true,
  457. trigger: 'change',
  458. message: 'Please select Charging Profile ID'
  459. },
  460. chargingProfilePurpose: {
  461. required: true,
  462. trigger: 'change',
  463. message: 'Please select Charging Profile Purpose'
  464. },
  465. location: {
  466. required: true,
  467. trigger: 'blur',
  468. message: 'Please input location'
  469. },
  470. retrieve: {
  471. required: true,
  472. trigger: 'change',
  473. message: 'Please select retrieve date time'
  474. }
  475. },
  476. columns: [{
  477. key: 'siteName',
  478. label: 'Site Name'
  479. }, {
  480. key: 'stationId',
  481. label: 'Station ID'
  482. }, {
  483. key: 'lastHeartbeat',
  484. label: 'Last Heartbeat'
  485. }, {
  486. key: 'checked',
  487. label: 'Select',
  488. action: true,
  489. width: 70
  490. }],
  491. searchTag: ""
  492. }
  493. },
  494. components: { Pagination },
  495. created() {
  496. this.getAllSite();
  497. },
  498. methods: {
  499. filterSite(word) {
  500. this.siteKeyword = word;
  501. this.siteLoading = true;
  502. this.getAllSite();
  503. },
  504. getAllSite() {
  505. site.getAllSiteList({siteName: this.siteKeyword}).then(res => {
  506. var list = []
  507. if (this.siteKeyword == '') {
  508. list = [{siteName: 'All', sitePk: ''}]
  509. }
  510. if (res.data && res.data.length > 0) {
  511. this.siteList = list.concat(res.data)
  512. } else {
  513. this.siteList = []
  514. }
  515. this.siteLoading = false;
  516. this.changeSite();
  517. }).catch(err => {
  518. this.siteLoading = false;
  519. });
  520. },
  521. async changeSite() {
  522. this.listLoading = true;
  523. const res = await ocpp.getStationPages(this.listQuery);
  524. if (res.success) {
  525. this.total = res.total;
  526. this.stationList = res.data;
  527. }
  528. this.listLoading = false;
  529. },
  530. async changeOperation(index) {
  531. this.paramLoading = true;
  532. this.operationForm = {
  533. operation: this.operationForm.operation,
  534. value: '',
  535. confKey: '',
  536. keyType: '',
  537. resetType: '',
  538. availType: '',
  539. idTagInfo: '',
  540. filterType: '',
  541. location: "",
  542. retrieve: "",
  543. retries: "",
  544. retryInterval: "",
  545. confKeyList: [],
  546. stationIds: [],
  547. stackLevel: '',
  548. connectorId: '',
  549. transactionId: '',
  550. customConfKey: '',
  551. chargingProfilePk: '',
  552. chargingProfilePurpose: undefined
  553. }
  554. this.operationInfo = this.operationList[index];
  555. this.selectNone();
  556. this.paramsOption = await util.getOperationParams(this.operationInfo, /*this.getCheckedStation()*/);
  557. //console.log('-----', paramsOption);
  558. this.paramLoading = false;
  559. this.$refs['operationForm'].clearValidate()
  560. },
  561. async changeRow(index, checked) {
  562. if (checked && this.operationInfo.singleStation) {
  563. this.selectNone();
  564. this.stationList[index].checked = true;
  565. }
  566. this.operationForm.stationIds = this.getCheckedStation();
  567. if (this.paramsOption.needConnector) {
  568. this.operationForm.connectorId = '';
  569. let list = await util.getConnectorIds(this.operationForm.stationIds);
  570. if (this.operationInfo.value == 'RSTART') {
  571. list.unshift(NOT_SPECIFIC)
  572. }
  573. this.paramsOption.connectorIds = list
  574. }
  575. if (this.paramsOption.needActConnector) {
  576. this.operationForm.transactionId = '';
  577. let list = await util.getActiveConnectorIds(this.operationForm.stationIds);
  578. this.paramsOption.connectorActIds = list;
  579. }
  580. },
  581. changeKeyType() {
  582. if (this.operationForm.keyType == 'PREDEFINED') {
  583. this.operationForm.confKey = '';
  584. } else {
  585. this.operationForm.customConfKey = '';
  586. }
  587. this.$refs['operationForm'].clearValidate()
  588. },
  589. changeFilterType() {
  590. if (this.operationForm.filterType == 'ChargingProfileId') {
  591. this.operationForm.connectorId = '';
  592. this.paramsOption.needConnector = false;
  593. this.paramsOption.needChargingProfile = true;
  594. this.operationForm.chargingProfilePurpose = undefined;
  595. this.paramsOption.needChargingProfilePurpose = false;
  596. } else {
  597. this.paramsOption.needConnector = true;
  598. this.operationForm.chargingProfilePk = '';
  599. this.paramsOption.needChargingProfile = false;
  600. this.paramsOption.needChargingProfilePurpose = true;
  601. if (this.paramsOption.connectorIds.length == 0) {
  602. this.changeRow();
  603. }
  604. }
  605. this.$refs['operationForm'].clearValidate()
  606. },
  607. getCheckedStation() {
  608. let list = [];
  609. this.stationList.forEach(item => {
  610. if (item.checked)
  611. list.push(item.stationId)
  612. })
  613. return list;
  614. },
  615. selectNone() {
  616. this.stationList.forEach(item => {
  617. item.checked = false;
  618. })
  619. this.paramsOption.connectorIds = [];
  620. this.operationForm.connectorId = '';
  621. this.operationForm.stationIds = [];
  622. },
  623. selectAll() {
  624. if (!this.operationInfo.singleStation) {
  625. this.stationList.forEach(item => {
  626. item.checked = true;
  627. })
  628. } else {
  629. this.stationList[0].checked = true;
  630. }
  631. this.changeRow();
  632. },
  633. handlePageChange() {
  634. this.changeSite()
  635. },
  636. perform() {
  637. this.$forceUpdate();
  638. this.$refs['operationForm'].validate(result => {
  639. if (result) {
  640. //this.addStation();
  641. if (this.operationForm.stationIds.length == 0) {
  642. this.$message({
  643. type: 'error',
  644. message: 'Please select at least 1 charge point'
  645. })
  646. } else if (this.operationInfo.api) {
  647. this.btnLoading = true;
  648. const params = Object.assign({}, this.operationForm)
  649. //对参数做特殊处理
  650. if (params.connectorId === NOT_SPECIFIC) {
  651. params.connectorId = ''
  652. }
  653. if (params.idTagInfo !== '') {
  654. params.idTagInfo = JSON.parse(params.idTagInfo)
  655. }
  656. ocpp.sendPerform(this.operationInfo.api, params).then(res => {
  657. this.btnLoading = false;
  658. if (res.data.taskId) {
  659. this.$router.push({path: '/ocpp-operations/result/' + res.data.taskId});
  660. }
  661. }).catch(err => {
  662. this.btnLoading = false;
  663. this.$message({
  664. type: 'error',
  665. message: err
  666. })
  667. });
  668. }
  669. }
  670. })
  671. },
  672. filterMobile(word) {
  673. if (word) {
  674. this.searchTag = word;
  675. setTimeout(() => {
  676. this.searchMobile(word)
  677. }, 300)
  678. } else {
  679. this.paramsOption.idTags = []
  680. this.paramsOption.idTags.push(...this.paramsOption.idTagAll)
  681. }
  682. },
  683. searchMobile(word) {
  684. console.log(this.searchTag, word);
  685. if (this.searchTag === word) {
  686. this.tagLoading = true;
  687. util.filterMobile(word, tags => {
  688. this.paramsOption.idTags = tags;
  689. this.tagLoading = false;
  690. })
  691. }
  692. }
  693. }
  694. }
  695. </script>
  696. <style scoped="scoped" lang='scss'>
  697. @import '../../styles/variables.scss';
  698. .card-container {
  699. width: 100%;
  700. padding: 20px 40px;
  701. min-height: $mainAppMinHeight;
  702. background-color: #F0F5FC;
  703. }
  704. .card-content {
  705. display: flex;
  706. overflow-x: auto;
  707. padding: 15px 5px;
  708. border-radius: 6px;
  709. flex-direction: row;
  710. flex-wrap: wrap-reverse;
  711. background-color: white;
  712. }
  713. .ocpp-content {
  714. flex: 1;
  715. min-width: 520px;
  716. padding: 0 35px 0 15px;
  717. }
  718. .section-title {
  719. color: #333;
  720. margin-top: 20px;
  721. margin-bottom: 30px;
  722. font-size: 16px;
  723. user-select: none;
  724. line-height: 24px;
  725. font-weight: 500;
  726. white-space: nowrap;
  727. font-family: sans-serif;
  728. text-transform: uppercase;
  729. }
  730. .perform {
  731. text-align: center;
  732. padding: 20px 80px 30px 0;
  733. }
  734. .multi-select {
  735. position: relative;
  736. min-height: 200px;
  737. }
  738. .note-span {
  739. top: 40px;
  740. left: 0px;
  741. width: 180px;
  742. color: #333333;
  743. padding: 6px 10px;
  744. font-size: 12px;
  745. border-radius: 3px;
  746. position: absolute;
  747. background-color: #56D905;
  748. }
  749. .flex-item {
  750. width: 100%;
  751. max-width: 320px;
  752. }
  753. @media screen and (max-width: 500px) {
  754. .card-container {
  755. padding: 0px;
  756. }
  757. .card-content {
  758. padding: 10px;
  759. }
  760. .ocpp-content {
  761. flex: 1;
  762. min-width: 320px;
  763. padding-right: 20px;
  764. }
  765. .perform {
  766. padding: 20px 0;
  767. }
  768. }
  769. </style>