华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

809 lines
27 KiB

  1. <template>
  2. <div class="flex flex1">
  3. <div class="flex1 flex">
  4. <el-input v-if="type === 'input'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  5. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" @blur="onCommonHandleSaveRecord"
  6. :placeholder="getPlaceholder()" v-model="inputValue"
  7. @input="onInputChange" @change="onInputChange" />
  8. <el-input v-else-if="type === 'textarea'" :maxlength="item.maxlength || 50" :disabled="getDisabled()"
  9. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" type="textarea" show-word-limit resize="none" @blur="onCommonHandleSaveRecord"
  10. :rows="item.rows || 3" :placeholder="getPlaceholder()"
  11. v-model="inputValue" @input="onInputChange" @change="onInputChange" />
  12. <DecimalInput v-else-if="type === 'inputNumber'" @blur="onCommonHandleSaveRecord" :maxlength="item.maxlength || 10"
  13. class="flex1" :disabled="getDisabled()" :controls="item.controls || false" :min="item.min || 0"
  14. :prepend = "item.prepend"
  15. :decimalDigits="item.precision" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  16. :placeholder="getPlaceholder()" v-model="inputValue"
  17. @input="onInputChange" @change="onInputChange" />
  18. <el-select v-else-if="type === 'select'" class="flex1" :multiple="item.multiple"
  19. :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')" v-model="inputValue" :disabled="getDisabled()"
  20. :placeholder="getPlaceholder()" @change="onInputChange">
  21. <el-option v-for="op in item.options" :key="op.value" :label="op.label" :value="op.value">
  22. </el-option>
  23. </el-select>
  24. <el-date-picker v-else-if="type === 'dateTime'" type="datetime" class="flex1" :class="getFillTypeStyle() + (orangeBg ? ' orange-bg' : '')"
  25. v-model="inputValue" :disabled="getDisabled()" format="yyyy/MM/DD HH:mm:ss"
  26. value-format="yyyy/MM/DD HH:mm:ss"
  27. :placeholder="getPlaceholder()" @change="onCommonHandleSaveRecord">
  28. </el-date-picker>
  29. <div class="clickable" :class="getFillTypeStyle() + (getDisabled()?' disabled':'') + (orangeBg ? ' orange-bg' : '')" v-else-if = "item.type ==='clickable'" @click="handleClickable(item,$event)">
  30. <span v-if="value">{{ value }}</span>
  31. <span v-else class="default-placeholder-text">{{getPlaceholder()}}</span>
  32. </div>
  33. </div>
  34. <!-- qc才能操作 -->
  35. <div class="handle-row" v-if="isShowHandle()">
  36. <el-checkbox class="ml-5"></el-checkbox>
  37. <div @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
  38. <Question class="handle-icon" :class="getQuestionColor()" />
  39. </div>
  40. <img v-if="getIsShowCopyIcon()" @click="onCopy" src="@/assets/images/copy-icon.svg" class="handle-icon"
  41. alt="" />
  42. <img src="@/assets/images/record-icon.svg" class="handle-icon" alt="" />
  43. </div>
  44. <!-- 修改记录模态框 -->
  45. <div
  46. v-if="showModal && modificationRecords.length > 0"
  47. ref="modalRef"
  48. :class="['modification-modal', {'show': showModal}]"
  49. @mouseenter="onModalEnter"
  50. @mouseleave="onModalLeave"
  51. >
  52. <div class="modal-content">
  53. <h4>修改记录</h4>
  54. <div class="records-list">
  55. <div
  56. v-for="(record, index) in modificationRecords"
  57. :key="index"
  58. class="record-item"
  59. >
  60. <p><strong>时间:</strong> {{ record.timestamp }}</p>
  61. <p><strong>旧值:</strong> {{ record.oldValue }}</p>
  62. <p><strong>新值:</strong> {{ record.newValue }}</p>
  63. <hr v-if="index < modificationRecords.length - 1">
  64. </div>
  65. <div v-if="!modificationRecords || modificationRecords.length === 0" class="no-records">
  66. 暂无修改记录
  67. </div>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. </template>
  73. <script>
  74. import Question from "./icons/Question.vue";
  75. import DecimalInput from "./DecimalInput.vue";
  76. export default {
  77. components: {
  78. Question,
  79. DecimalInput
  80. },
  81. props: {
  82. type: {//form类型 input/select等
  83. type: String,
  84. default: "input"
  85. },
  86. item: {
  87. type: Object,
  88. default: () => {
  89. return {
  90. placeholder: "",
  91. maxlength: 30,
  92. label: "",
  93. disabled: false,
  94. }
  95. }
  96. },
  97. // v-model 值
  98. value: {
  99. type: [String, Number, Array],
  100. default: ''
  101. },
  102. // 错误状态
  103. error: {
  104. type: Boolean,
  105. default: false
  106. },
  107. // 橙色背景状态
  108. orangeBg: {
  109. type: Boolean,
  110. default: false
  111. },
  112. },
  113. data() {
  114. return {
  115. inputValue: this.value,
  116. oldValue: this.value, // 记录上一次的值
  117. showModal: false, // 控制模态框显示
  118. modificationRecords: [], // 存储修改记录
  119. modalTimer: null, // 用于延迟隐藏模态框
  120. isHoveringModal: false, // 是否悬停在模态框上
  121. isHoveringMain: false, // 是否悬停在主元素上(这个实际上不需要,因为我们有事件处理)
  122. db: null, // IndexedDB 实例
  123. }
  124. },
  125. watch: {
  126. value(newVal) {
  127. this.inputValue = newVal;
  128. console.log(newVal,"newVal")
  129. }
  130. },
  131. filters: {
  132. },
  133. methods: {
  134. getFillTypeStyle(type) {
  135. const {fillType} = this.item;
  136. const typeObj = {
  137. actFill: "orange-border",//实际填写的边框颜色
  138. green: "green-border",
  139. preFill: "blue-border",//预填写的边框颜色
  140. }
  141. // 如果有错误状态,返回红色边框样式,覆盖原有的边框颜色
  142. if (this.error) {
  143. return "error-border";
  144. }
  145. return typeObj[fillType] || ""
  146. },
  147. //获取question图标颜色
  148. getQuestionColor() {
  149. //gray 灰色 green 绿色 orange 橙色
  150. return "green"
  151. },
  152. // 统一处理输入变化
  153. onInputChange(val) {
  154. const value = val !== undefined ? val : this.inputValue;
  155. this.$emit('input', value);
  156. this.$emit('change', value);
  157. // 根据输入值判断是否显示错误状态
  158. const isEmpty = this.isValueEmpty(value);
  159. if (this.error && !isEmpty) {
  160. this.$emit('update:error', false);
  161. } else if (!this.error && isEmpty) {
  162. this.$emit('update:error', true);
  163. }
  164. },
  165. async onCommonHandleSaveRecord(val){
  166. const isEmpty = this.isValueEmpty(this.inputValue);
  167. if (this.error && !isEmpty) {
  168. this.$emit('update:error', false);
  169. } else if (!this.error && isEmpty) {
  170. this.$emit('update:error', true);
  171. }
  172. const {templateStatus} = this.$store.state.template;
  173. // 检查值是否发生变化
  174. if (this.inputValue !== this.oldValue && templateStatus === "actFill") {
  175. // 值发生了变化,需要弹出密码输入框
  176. try {
  177. // const passwordResult = await this.$prompt('请输入密码以确认修改', '密码验证', {
  178. // confirmButtonText: '确定',
  179. // cancelButtonText: '取消',
  180. // inputType: 'password',
  181. // inputPattern: /.+/,
  182. // inputErrorMessage: '请输入密码',
  183. // zIndex: 10000,
  184. // });
  185. // 用户输入密码并点击确定,保存修改
  186. this.oldValue = this.inputValue; // 更新旧值
  187. this.$emit("blur", val);
  188. this.$emit('input', this.inputValue);
  189. this.$emit("change", val);
  190. // 调用后端接口记录修改记录
  191. await this.saveModificationRecord();
  192. } catch {
  193. // 用户点击取消,还原数据
  194. this.inputValue = this.oldValue;
  195. this.$emit('input', this.inputValue); // 触发 v-model 更新
  196. this.$emit("blur", this.oldValue);
  197. this.$emit("change", this.oldValue);
  198. }
  199. } else {
  200. // 值没有变化,正常触发 blur和change 事件
  201. this.$emit("blur", val)
  202. // this.$emit('input', val);
  203. this.$emit("change", val)
  204. }
  205. },
  206. // 通用的值变化处理方法
  207. async handleValueChange(val, componentType = '') {
  208. const oldValue = this.oldValue; // 保存旧值
  209. this.$emit('input', val);
  210. this.$emit('change', val);
  211. // 根据输入值判断是否显示错误状态
  212. const isEmpty = this.isValueEmpty(val);
  213. if (this.error && !isEmpty) {
  214. this.$emit('update:error', false);
  215. } else if (!this.error && isEmpty) {
  216. this.$emit('update:error', true);
  217. }
  218. // 值发生改变,记录修改
  219. const { templateStatus } = this.$store.state.template;
  220. if (val !== oldValue && templateStatus === "actFill") {
  221. // 值发生了变化,记录修改
  222. try {
  223. this.oldValue = val; // 更新旧值
  224. // 调用后端接口记录修改记录
  225. await this.saveModificationRecord();
  226. } catch (error) {
  227. const componentName = componentType || '组件';
  228. console.error(`记录${componentName}修改失败:`, error);
  229. }
  230. }
  231. },
  232. // 判断值是否为空
  233. isValueEmpty(value) {
  234. if (value === null || value === undefined || value === '') {
  235. return true;
  236. }
  237. if (typeof value === 'string' && value.trim() === '') {
  238. return true;
  239. }
  240. if (Array.isArray(value) && value.length === 0) {
  241. return true;
  242. }
  243. return false;
  244. },
  245. handleClickable(item,event){
  246. if(item.fillType !== 'actFill'){
  247. return
  248. }
  249. this.$emit("clickable",item)
  250. },
  251. //判断是否显示复制按钮
  252. getIsShowCopyIcon() {
  253. const { copyFrom } = this.item;
  254. const { templateStatus } = this.$store.state.template;
  255. return copyFrom && templateStatus === "actFill";
  256. },
  257. //判断是否显示操作按钮
  258. isShowHandle() {
  259. const { fillType } = this.item;
  260. const { templateStatus } = this.$store.state.template;
  261. //只有当模板状态是qc和实际填报时,才显示操作按钮
  262. return (templateStatus === "qc" || templateStatus === "actFill") && fillType === "actFill";
  263. },
  264. //判断是否禁用
  265. getDisabled() {
  266. const { item } = this;
  267. const { fillType } = item;
  268. if (item.hasOwnProperty("disabled")) {
  269. return item.disabled
  270. } else {
  271. const { templateStatus } = this.$store.state.template;
  272. if (fillType === "actFill") {//当模板状态是实际填写时,只有当fillType是actFill时才能填写
  273. return templateStatus !== "actFill"
  274. } else if (fillType === "preFill") {//当模板状态是预填写时,只有当fillType是preFill才能填写
  275. return templateStatus !== "preFill"
  276. } else {
  277. return true
  278. }
  279. }
  280. },
  281. getPlaceholder() {
  282. const { placeholder,label } = this.item;
  283. const {type} = this;
  284. if(this.getDisabled()){
  285. return ""
  286. }
  287. if(type === "clickable"){
  288. return "请选择"
  289. }
  290. let prex = "请输入";
  291. if(type === "select" || type === "dateTime"){
  292. prex = "请选择"
  293. }
  294. return placeholder ? placeholder : (prex + label)
  295. },
  296. onCopy() {
  297. this.$emit("copy")
  298. },
  299. // 记录数据修改
  300. // 鼠标进入主容器
  301. // 初始化 IndexedDB
  302. initDB() {
  303. return new Promise((resolve, reject) => {
  304. const request = indexedDB.open('ModificationRecordsDB', 1);
  305. request.onerror = (event) => {
  306. console.error('IndexedDB error:', event.target.error);
  307. reject(event.target.error);
  308. };
  309. request.onsuccess = (event) => {
  310. this.db = event.target.result;
  311. resolve(this.db);
  312. };
  313. request.onupgradeneeded = (event) => {
  314. const db = event.target.result;
  315. if (!db.objectStoreNames.contains('modificationRecords')) {
  316. const objectStore = db.createObjectStore('modificationRecords', { keyPath: 'id' });
  317. objectStore.createIndex('fieldId', 'fieldId', { unique: false });
  318. objectStore.createIndex('timestamp', 'timestamp', { unique: false });
  319. }
  320. };
  321. });
  322. },
  323. // 生成唯一字段ID (id + key值)
  324. getFieldId() {
  325. const templateId = 'template_123'; // 这里是写死的id
  326. const fieldKey = this.item.key || this.item.prop || this.item.label || 'default_key';
  327. // 考虑到CustomTable组件可能有重复的key值,我们需要额外标识
  328. // 如果在表格中,可能需要添加行索引等信息
  329. const tableRowIndex = this.item.rowIndex !== undefined ? `_${this.item.rowIndex}` : '';
  330. return `${templateId}_${fieldKey}${tableRowIndex}`;
  331. },
  332. // 获取 IndexedDB 对象存储实例
  333. async getObjectStore(storeName = 'modificationRecords', mode = 'readonly') {
  334. if (!this.db) {
  335. await this.initDB();
  336. }
  337. const transaction = this.db.transaction([storeName], mode);
  338. return transaction.objectStore(storeName);
  339. },
  340. // 保存单条修改记录到 IndexedDB
  341. async saveRecordToDB(record) {
  342. const objectStore = await this.getObjectStore('modificationRecords', 'readwrite');
  343. const fieldId = this.getFieldId();
  344. const newRecord = {
  345. id: `${fieldId}_${Date.now()}`, // 使用时间戳确保唯一性
  346. fieldId: fieldId,
  347. oldValue: record.oldValue,
  348. newValue: record.newValue,
  349. timestamp: new Date().toLocaleString(),
  350. };
  351. return new Promise((resolve, reject) => {
  352. const request = objectStore.add(newRecord);
  353. request.onsuccess = () => {
  354. resolve(request.result);
  355. };
  356. request.onerror = (event) => {
  357. reject(event.target.error);
  358. };
  359. });
  360. },
  361. // 从 IndexedDB 获取修改记录
  362. async getRecordsFromDB() {
  363. if (!this.db) {
  364. await this.initDB();
  365. }
  366. const transaction = this.db.transaction(['modificationRecords'], 'readonly');
  367. const objectStore = transaction.objectStore('modificationRecords');
  368. const fieldIdIndex = objectStore.index('fieldId');
  369. const fieldId = this.getFieldId();
  370. return new Promise((resolve, reject) => {
  371. const request = fieldIdIndex.getAll(IDBKeyRange.only(fieldId));
  372. request.onsuccess = (event) => {
  373. // 按时间戳排序,最新的在前
  374. const records = event.target.result.sort((a, b) => {
  375. return new Date(b.timestamp) - new Date(a.timestamp);
  376. });
  377. resolve(records);
  378. };
  379. request.onerror = (event) => {
  380. reject(event.target.error);
  381. };
  382. });
  383. },
  384. // 同步后端修改记录到 IndexedDB
  385. async syncRecordsToDB(backendRecords) {
  386. // 清空当前字段的记录
  387. await this.clearFieldRecords();
  388. // 批量添加后端记录到 IndexedDB
  389. const objectStore = await this.getObjectStore('modificationRecords', 'readwrite');
  390. const fieldId = this.getFieldId();
  391. return new Promise((resolve, reject) => {
  392. let completed = 0;
  393. const total = backendRecords.length;
  394. if (total === 0) {
  395. resolve();
  396. return;
  397. }
  398. backendRecords.forEach((record) => {
  399. // 标准化记录格式以匹配 IndexedDB 存储格式
  400. const newRecord = {
  401. id: `${fieldId}_${Date.parse(record.timestamp) || Date.now()}`, // 使用时间戳或当前时间戳作为 ID
  402. fieldId: fieldId,
  403. oldValue: record.oldValue,
  404. newValue: record.newValue,
  405. timestamp: record.timestamp || new Date().toLocaleString(),
  406. password: record.password ? '***' : '' // 不直接存储密码
  407. };
  408. const request = objectStore.add(newRecord);
  409. request.onsuccess = () => {
  410. completed++;
  411. if (completed === total) {
  412. resolve();
  413. }
  414. };
  415. request.onerror = (event) => {
  416. console.error('同步单条记录失败:', event.target.error);
  417. completed++;
  418. if (completed === total) {
  419. resolve(); // 即使有错误也继续,避免单条记录失败影响整体同步
  420. }
  421. };
  422. });
  423. });
  424. },
  425. // 清空当前字段的记录
  426. async clearFieldRecords() {
  427. if (!this.db) {
  428. await this.initDB();
  429. }
  430. const transaction = this.db.transaction(['modificationRecords'], 'readwrite');
  431. const objectStore = transaction.objectStore('modificationRecords');
  432. const fieldIdIndex = objectStore.index('fieldId');
  433. const fieldId = this.getFieldId();
  434. return new Promise((resolve, reject) => {
  435. const request = fieldIdIndex.openCursor(IDBKeyRange.only(fieldId));
  436. request.onsuccess = (event) => {
  437. const cursor = event.target.result;
  438. if (cursor) {
  439. cursor.delete(); // 删除匹配的记录
  440. cursor.continue();
  441. } else {
  442. // 所有匹配的记录已删除
  443. resolve();
  444. }
  445. };
  446. request.onerror = (event) => {
  447. reject(event.target.error);
  448. };
  449. });
  450. },
  451. // 鼠标进入主容器
  452. async onMouseEnter(event) {
  453. clearTimeout(this.modalTimer);
  454. // 从 IndexedDB 加载修改记录
  455. try {
  456. const records = await this.getRecordsFromDB();
  457. this.modificationRecords = records;
  458. } catch (error) {
  459. console.error('获取修改记录失败:', error);
  460. this.modificationRecords = [];
  461. }
  462. // 先计算模态框位置,避免闪烁
  463. this.showModal = true;
  464. this.$nextTick(() => {
  465. if (this.$refs.modalRef) {
  466. const elementRect = event.target.getBoundingClientRect();
  467. const modalEl = this.$refs.modalRef;
  468. // 设置模态框位置在元素右侧
  469. modalEl.style.left = elementRect.right + 5 + 'px'; // 5px间距
  470. modalEl.style.top = elementRect.top + 'px';
  471. }
  472. });
  473. },
  474. // 鼠标离开主容器
  475. onMouseLeave() {
  476. // 延迟隐藏模态框,让用户有机会移动到模态框上
  477. this.modalTimer = setTimeout(() => {
  478. if (!this.isHoveringModal) {
  479. this.showModal = false;
  480. }
  481. }, 300);
  482. },
  483. // 鼠标进入模态框
  484. onModalEnter() {
  485. this.isHoveringModal = true;
  486. clearTimeout(this.modalTimer);
  487. },
  488. // 鼠标离开模态框
  489. onModalLeave() {
  490. this.isHoveringModal = false;
  491. this.modalTimer = setTimeout(() => {
  492. this.showModal = false;
  493. }, 300);
  494. },
  495. // 记录数据修改
  496. async saveModificationRecord() {
  497. // 添加修改记录到本地存储
  498. const record = {
  499. oldValue: this.oldValue,
  500. newValue: this.inputValue,
  501. timestamp: new Date().toLocaleString(), // 格式化时间
  502. };
  503. // 保存到 IndexedDB
  504. try {
  505. await this.saveRecordToDB(record);
  506. // 保存成功后更新本地记录(可选,取决于是否需要立即显示)
  507. // const records = await this.getRecordsFromDB();
  508. // this.modificationRecords = records;
  509. } catch (error) {
  510. console.error('保存修改记录失败:', error);
  511. }
  512. // 发送事件告知父组件值已修改
  513. this.$emit('modification-recorded', {
  514. field: this.item.label || '',
  515. oldValue: this.oldValue,
  516. newValue: this.inputValue,
  517. });
  518. }
  519. },
  520. }
  521. </script>
  522. <style lang="scss">
  523. .flex {
  524. display: flex;
  525. align-items: center;
  526. }
  527. .flex1 {
  528. flex: 1;
  529. }
  530. .handle-row {
  531. margin-left: 10px;
  532. display: flex;
  533. align-items: center;
  534. cursor: pointer;
  535. }
  536. .w-100 {
  537. width: 100%;
  538. }
  539. .handle-icon {
  540. width: 18px;
  541. height: 18px;
  542. margin-left: 5px;
  543. }
  544. .ml-5 {
  545. margin-left: 5px;
  546. }
  547. .orange {
  548. color: #f9c588;
  549. }
  550. .green {
  551. color: green;
  552. }
  553. .gray {
  554. color: #b2b2b2;
  555. }
  556. .orange-border {
  557. .el-input-group__prepend,input,
  558. textarea {
  559. border-color: #f9c588;
  560. &:focus {
  561. border-color: #f9c588;
  562. }
  563. &:hover {
  564. border-color: #f9c588;
  565. }
  566. &:disabled {
  567. border-color: #f9c588 !important;
  568. }
  569. }
  570. }
  571. .green-border {
  572. .el-input-group__prepend,input,
  573. textarea {
  574. border-color: green;
  575. &:focus {
  576. border-color: green;
  577. }
  578. &:hover {
  579. border-color: green;
  580. }
  581. &:disabled {
  582. border-color: green !important;
  583. }
  584. }
  585. }
  586. .blue-border {
  587. .el-input-group__prepend,input,
  588. textarea {
  589. border-color: #4ea2ff;
  590. &:focus {
  591. border-color: #4ea2ff;
  592. }
  593. &:hover {
  594. border-color: #4ea2ff;
  595. }
  596. &:disabled {
  597. border-color: #4ea2ff !important;
  598. }
  599. }
  600. }
  601. .error-border {
  602. .el-input-group__prepend,input,
  603. textarea,
  604. .el-select,
  605. .clickable,
  606. .el-date-editor {
  607. border-color: #f56c6c;
  608. &:focus {
  609. border-color: #f56c6c;
  610. }
  611. &:hover {
  612. border-color: #f56c6c;
  613. }
  614. }
  615. // 为 el-select 和 el-date-picker 添加错误边框样式
  616. .el-select .el-input__inner,
  617. .el-date-editor .el-input__inner {
  618. border-color: #f56c6c;
  619. }
  620. // 处理 DecimalInput 组件的错误边框样式
  621. :deep(.el-input-number) {
  622. .el-input__inner {
  623. border-color: #f56c6c;
  624. }
  625. }
  626. // 为点击式表单项添加错误边框样式
  627. .clickable {
  628. border-color: #f56c6c;
  629. }
  630. }
  631. .orange-bg {
  632. background-color: #FFF1F1 !important; // 橙色背景,透明度适中
  633. input, textarea, .el-input__inner, .el-textarea__inner {
  634. background-color: #FFF1F1 !important;
  635. }
  636. }
  637. .modification-modal {
  638. position: fixed;
  639. z-index: 9999;
  640. background-color: rgba(0, 0, 0, 0.7);
  641. border-radius: 4px;
  642. padding: 10px;
  643. color: white;
  644. max-height: 300px;
  645. min-width: 250px;
  646. overflow: hidden;
  647. pointer-events: auto;
  648. opacity: 0;
  649. transform: scale(0.9);
  650. transition: opacity 0.2s ease, transform 0.2s ease;
  651. }
  652. .modification-modal.show {
  653. opacity: 1;
  654. transform: scale(1);
  655. }
  656. .modification-modal .modal-content {
  657. max-height: 280px;
  658. overflow-y: auto;
  659. padding-right: 5px;
  660. }
  661. .modification-modal .modal-content h4 {
  662. margin: 0 0 10px 0;
  663. font-size: 14px;
  664. border-bottom: 1px solid #ccc;
  665. padding-bottom: 5px;
  666. }
  667. .modification-modal .records-list {
  668. font-size: 12px;
  669. }
  670. .modification-modal .record-item p {
  671. margin: 5px 0;
  672. word-break: break-all;
  673. }
  674. .modification-modal .record-item hr {
  675. border: 0;
  676. border-top: 1px solid #555;
  677. margin: 8px 0;
  678. }
  679. .modification-modal .no-records {
  680. text-align: center;
  681. color: #aaa;
  682. font-style: italic;
  683. }
  684. .clickable{
  685. cursor: pointer;
  686. width: auto;
  687. // margin-left: 10px;
  688. min-width: 100px;
  689. height: 28px;
  690. border-radius: 4px;
  691. border:1px solid #4ea2ff;
  692. display: flex;
  693. align-items: center;
  694. padding:0 15px;
  695. font-size: 14px;
  696. font-weight: normal;
  697. color: #606266;
  698. flex:1;
  699. &.disabled{
  700. cursor: not-allowed;
  701. color: #c0c4cc;
  702. background-color: #f5f7fa;
  703. }
  704. &.error-border{
  705. border-color: #f56c6c !important;
  706. }
  707. }
  708. </style>