1
jhzh
2026-01-14 fa821ce7a7755dd0e13897cefc57071d4596431b
src/page/newUser/about.vue
@@ -11,68 +11,98 @@
    <div class="hezi" v-show="type == '1'">
      <img src="@/assets/img/yyzz.png" />
      <h6>{{ $t("jy321") }}</h6>
      <p>{{ $t("jy320") }}:V1.1.2</p>
      <p>{{ $t("jy320") }}: V1.1.2</p>
    </div>
    <div v-show="type != '1'" class="hezi" style="line-height: 0.5rem;">
      <div v-if="type == 2" v-html="userInfo.companyInfo"></div>
      <div v-if="type == 2">
        <div v-if="generating" class="loading-container">
          <div class="loading-spinner"></div>
          <p class="loading-text">{{ $t('jy526') }}</p>
        </div>
        <div v-else-if="!hasAgreementPdf" style="text-align: center; padding: 20px;">
          <!-- <button @click="handleGeneratePdf" class="btn-generate" :disabled="generating">
            生成用户合同
          </button> -->
        </div>
        <div v-else class="agreement-container">
          <ImageViewer :imageUrls="imageUrls" v-if="imageUrls && imageUrls.length > 0" />
          <div class="signature-section" v-if="showSignature && !isSigned">
            <h4>{{ $t('jy527') }}</h4>
            <vue-signature-pad
              ref="mySignaturePad"
              class="signature-canvas"
              :options="signatureOptions"
            ></vue-signature-pad>
            <div class="signature-btns">
              <button @click="clearSignature" class="btn-clear" :disabled="submittingSignature">{{ $t('jy528') }}</button>
              <button @click="confirmSignature" class="btn-confirm" :disabled="submittingSignature">{{ $t('jy529') }}</button>
            </div>
          </div>
          <div v-else-if="!isSigned" class="signature-action">
            <button @click="showSignature = true" class="btn-signature">{{ $t('jy529') }}</button>
          </div>
          <div v-if="isSigned" class="signature-tip">
            <p>{{ $t('jy530') }}</p>
          </div>
        </div>
      </div>
      <div v-else-if="type == 3" v-html="userInfo.certImg1"></div>
      <div v-else-if="type == 4" v-html="userInfo.certImg2"></div>
      <div v-else-if="type == 5" v-html="userInfo.tradeAgreeText"></div>
      <div v-else-if="type == 6">{{ $t("jy319") }}</div>
      <div v-else-if="type == 7">
        <div v-if="generatingContract" class="loading-container">
          <div class="loading-spinner"></div>
          <p class="loading-text">{{ $t('jy526') }}</p>
        </div>
        <div v-else-if="!hasContractPdf" style="text-align: center; padding: 20px;">
          <!-- <button @click="handleGenerateContractPdf" class="btn-generate" :disabled="generatingContract">
            生成用户合同
          </button> -->
        </div>
        <div v-else class="agreement-container">
          <ImageViewer :imageUrls="contractImageUrls" v-if="contractImageUrls && contractImageUrls.length > 0" />
          <div class="signature-section" v-if="showContractSignature && !isContractSigned">
            <h4>{{ $t('jy527') }}</h4>
            <vue-signature-pad
              ref="contractSignaturePad"
              class="signature-canvas"
              :options="signatureOptions"
            ></vue-signature-pad>
            <div class="signature-btns">
              <button @click="clearContractSignature" class="btn-clear" :disabled="submittingContractSignature">{{ $t('jy528') }}</button>
              <button @click="confirmContractSignature" class="btn-confirm" :disabled="submittingContractSignature">{{ $t('jy529') }}</button>
            </div>
          </div>
          <div v-else-if="!isContractSigned" class="signature-action">
            <button @click="showContractSignature = true" class="btn-signature">{{ $t('jy529') }}</button>
          </div>
          <div v-if="isContractSigned" class="signature-tip">
            <p>{{ $t('jy530') }}</p>
          </div>
        </div>
      </div>
      <div v-else></div>
     <!-- <img src="@/assets/img/yyzz.png" style="width: 100%;" /> -->
    </div>
   <div>
       <!-- 原始图片,点击可放大 -->
       <img
         src="@/assets/img/yyzz.png"
         style="width: 100%; cursor: pointer;"
         @click="showModal = true"
       />
<!-- <div class="signature-area">
      <h4>请完成电子签名</h4>
      <vue-signature-pad
        ref="mySignaturePad"
        class="signature-canvas"
        :options="signatureOptions"
      ></vue-signature-pad>
      <div class="signature-btns">
        <button @click="clearSignature" class="btn-clear">清空签名</button>
        <button @click="confirmSignature" class="btn-confirm" :disabled="!isAgree || !hasSignature">确认签名</button>
      </div>
    </div>
    <div class="signature-preview" v-if="signatureImg">
      <h4>签名预览</h4>
      <img :src="signatureImg" alt="电子签名" />
    </div> -->
       <!-- 模态框,用于显示放大的图片 -->
       <div
         v-if="showModal"
         class="modal"
         @click.self="showModal = false"
       >
         <div class="modal-content">
           <img
             :src="require('@/assets/img/yyzz.png')"
             class="enlarged-image"
           />
         </div>
       </div>
     </div>
  </div>
</template>
<script>
import * as api from "@/axios/api";
import VueSignaturePad from 'vue-signature-pad'
import PdfViewerModern from '@/components/PdfViewerModern.vue'
import ImageViewer from '@/components/ImageViewer.vue'
import axios from '@/axios/index'
import APIUrl from '@/axios/api.url'
import { Toast } from 'mint-ui'
export default {
  name: "about",
  components: {
    VueSignaturePad
    VueSignaturePad,
    PdfViewerModern,
    ImageViewer
  },
  created() {
    var that = this;
@@ -84,7 +114,7 @@
          this.title = this.$t("jy318");
          break;
        case "2":
          this.title = this.$t("jy317");
          this.title = this.$t("jy546");
          break;
        case "3":
          this.title = this.$t("jy316");
@@ -98,6 +128,9 @@
        case "6":
          this.title = this.$t("jy315");
          break;
        case "7":
          this.title = this.$t("jy547");
          break;
        default:
          break;
      }
@@ -109,75 +142,513 @@
      title: this.$t("jy314"),
      type: 0,
      userInfo: {},
     showModal: false,
     signatureImg: '',
     isAgree: false,
     hasSignature: false,
     signatureOptions: {
      penColor: '#000000',
      penWidth: 2,
     },
    signatureInstance: null
      signatureImg: '',
      hasSignature: false,
      signatureOptions: {
        penColor: '#000000',
        penWidth: 2,
      },
      signatureInstance: null,
      hasAgreementPdf: false,
      generating: false,
      pdfUrl: '',
      imageUrls: [],
      showSignature: false,
      currentUser: null,
      hasContractPdf: false,
      generatingContract: false,
      contractPdfUrl: '',
      contractImageUrls: [],
      showContractSignature: false,
      contractSignatureInstance: null,
      submittingSignature: false,
      submittingContractSignature: false
    };
  },
  mounted() {
    this.getUserInfo();
    // vue-signature-pad@2.x 的实例在 this.$refs.xxx.signaturePad 里
    this.signatureInstance = this.$refs.mySignaturePad.signaturePad
    // 监听签名变化,更新 hasSignature 状态
    this.$refs.mySignaturePad.$on('signatureChanged', (isEmpty) => {
      this.hasSignature = !isEmpty
    })
  computed: {
    isSigned() {
      // 判断是否已签名:检查isSignedContract字段是否不为空
      return this.currentUser && this.currentUser.isSignedContract && this.currentUser.isSignedContract.trim() !== '';
    },
    isContractSigned() {
      // 判断保密协议是否已签名:检查isSignedAgreement字段是否等于'Y'
      return this.currentUser && this.currentUser.isSignedAgreement === 'Y';
    },
    pdfIframeSrc() {
      if (!this.pdfUrl) return '';
      const token = typeof window !== 'undefined' && window.localStorage ? window.localStorage.getItem('USERTOKEN') || '' : '';
      return this.pdfUrl + '?token=' + token;
    }
  },
  async mounted() {
    if(this.type != 3) {
    await this.getUserInfo();
    }else{
      let data = await api.getInfoSite();
      if (data.status === 0) {
        this.userInfo = data.data;
      }
    }
    if (this.type == 2) {
      this.checkAgreementPdf();
    } else if (this.type == 7) {
      this.checkContractPdf();
    }
  },
  watch: {
    showSignature(newVal) {
      if (newVal) {
        // 当显示签名区域时,初始化签名组件
        this.$nextTick(() => {
          this.initSignaturePad();
        });
      }
    },
    showContractSignature(newVal) {
      if (newVal) {
        // 当显示合同签名区域时,初始化签名组件
        this.$nextTick(() => {
          this.initContractSignaturePad();
        });
      }
    }
  },
  methods: {
    isAuthenticated() {
      // 判断是否已实名认证:isActive === 1 (审核中) 或 isActive === 2 (已通过)
      const userInfo = this.currentUser || this.$store.state.userInfo;
      return userInfo && (userInfo.isActive === 1 || userInfo.isActive === 2);
    },
    async getUserInfo() {
      // 获取用户信息
      let data = await api.getInfoSite();
      if (data.status === 0) {
        this.userInfo = data.data;
      } else {
      }
      // 获取当前登录用户信息
      let userData = await api.getUserInfo();
      if (userData.status === 0) {
        this.currentUser = userData.data;
        this.$store.state.userInfo = userData.data;
      }
    },
    // 清空签名(调用真实实例的方法)
    clearSignature() {
      if (this.signatureInstance) {
        this.signatureInstance.clear()
        this.signatureImg = ''
        this.hasSignature = false
      } else {
        alert('签名组件未初始化!')
      }
    },
    // 确认签名(调用真实实例的方法)
    confirmSignature() {
      if (!this.isAgree) {
        alert('请先阅读并同意用户协议!')
        return
      }
      if (this.signatureInstance) {
        // 检查是否有签名
        if (!this.signatureInstance.isEmpty()) {
          // 导出 Base64 图片
          this.signatureImg = this.signatureInstance.toDataURL('image/png')
          this.hasSignature = true
          this.submitAgreement()
        } else {
          alert('请先完成电子签名!')
    initSignaturePad() {
      this.$nextTick(() => {
        if (this.$refs.mySignaturePad) {
          this.signatureInstance = this.$refs.mySignaturePad.signaturePad;
        }
      } else {
        alert('签名组件未初始化!')
      });
    },
    clearSignature() {
      this.$nextTick(() => {
        if (!this.signatureInstance && this.$refs.mySignaturePad) {
          this.signatureInstance = this.$refs.mySignaturePad.signaturePad;
        }
        if (this.signatureInstance) {
          this.signatureInstance.clear();
          this.signatureImg = '';
          this.hasSignature = false;
        }
      });
    },
    confirmSignature() {
      // 防止重复点击
      if (this.submittingSignature) {
        return;
      }
      this.$nextTick(() => {
        if (!this.signatureInstance && this.$refs.mySignaturePad) {
          this.signatureInstance = this.$refs.mySignaturePad.signaturePad;
        }
        if (this.signatureInstance) {
          if (!this.signatureInstance.isEmpty()) {
            this.signatureImg = this.signatureInstance.toDataURL('image/png');
            this.hasSignature = true;
            this.submitAgreement();
          } else {
          this.$message.error(this.$t('jy554'));
          }
        } else {
          this.$message.error(this.$t('jy555'));
        }
      });
    },
    async submitAgreement() {
      // 防止重复提交
      if (this.submittingSignature) {
        return;
      }
      this.submittingSignature = true;
      try {
        const blob = await this.dataURLtoBlob(this.signatureImg);
        const formData = new FormData();
        formData.append('signature', blob, 'signature.png');
        const config = {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        };
        if (window.localStorage.getItem("USERTOKEN")) {
          config.headers["USERTOKEN"] = window.localStorage.getItem("USERTOKEN");
        }
        const result = await axios.post(APIUrl.baseURL + '/user/saveAgreementSignature.do', formData, config);
        if (result.data.status === 0) {
          this.$message.success(this.$t('jy556'));
          this.showSignature = false;
          // 重新获取用户信息,更新签名状态
          let userData = await api.getUserInfo();
          if (userData.status === 0) {
            this.currentUser = userData.data;
            this.$store.state.userInfo = userData.data;
          }
          // 延迟跳转,让用户看到成功提示
          setTimeout(() => {
            this.$router.push('/User');
          }, 1500);
        } else {
          this.$message.error(result.data.msg || this.$t('jy557'));
          this.submittingSignature = false;
        }
      } catch (error) {
        console.error('提交签名失败:', error);
        this.$message.error(this.$t('jy557'));
        this.submittingSignature = false;
      }
    },
    // 提交协议
    submitAgreement() {
      const submitData = {
        userId: '当前用户ID',
        isAgree: this.isAgree,
        signature: this.signatureImg,
        agreeTime: new Date().getTime()
    dataURLtoBlob(dataurl) {
      const arr = dataurl.split(',');
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      console.log('提交数据:', submitData)
      alert('签名成功,协议已提交!')
      return new Blob([u8arr], { type: mime });
    },
    async checkAgreementPdf() {
      try {
        // type == 2 是线上合同
        if (this.type == 2) {
          const APIUrl = require('@/axios/api.url').default;
          // 如果已签名(isSignedContract === 'Y'),调用viewAgreementPdf接口获取已签名的图片
          if (this.currentUser && this.currentUser.isSignedContract === 'Y') {
            this.generating = true;
            let result = await api.viewAgreementPdf();
            console.log('viewAgreementPdf返回结果:', result);
            if (result.status === 0) {
              this.hasAgreementPdf = true;
              // result.data 现在是图片数组
              if (Array.isArray(result.data) && result.data.length > 0) {
                this.imageUrls = result.data.map(url => {
                  if (!url.startsWith('http')) {
                    return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
                  }
                  return url;
                });
                console.log('处理后的图片URLs:', this.imageUrls);
              } else {
                console.warn('未返回图片数据或数据为空');
                this.hasAgreementPdf = false;
              }
            } else {
              console.error('接口返回错误:', result.msg);
              this.hasAgreementPdf = false;
            }
            this.generating = false;
            return;
          }
          // 如果未签名(isSignedContract为空),调用generateAgreementPdf接口生成并渲染图片
          this.generating = true;
          let result = await api.generateAgreementPdf();
          if (result.status === 0) {
            this.hasAgreementPdf = true;
            // result.data 现在是图片数组
            if (Array.isArray(result.data) && result.data.length > 0) {
              this.imageUrls = result.data.map(url => {
                if (!url.startsWith('http')) {
                  return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
                }
                return url;
              });
            } else {
              this.hasAgreementPdf = false;
            }
          } else {
            this.hasAgreementPdf = false;
          }
          this.generating = false;
          return;
        }
        // 其他type的处理逻辑
        // 如果已签名,直接使用已签名的PDF
        if (this.isSigned && this.currentUser && this.currentUser.isSignedContract) {
          this.hasAgreementPdf = true;
          let url = this.currentUser.isSignedContract;
          if (!url.startsWith('http')) {
            url = APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
          }
          this.pdfUrl = url;
          return;
        }
        // 未签名,生成新的PDF
        this.generating = true;
        let result = await api.generateAgreementPdf();
        if (result.status === 0) {
          this.hasAgreementPdf = true;
          // result.data 现在是图片数组
          if (Array.isArray(result.data) && result.data.length > 0) {
            this.imageUrls = result.data.map(url => {
              if (!url.startsWith('http')) {
                return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
              }
              return url;
            });
          } else {
            this.hasAgreementPdf = false;
          }
        } else {
          this.hasAgreementPdf = false;
        }
      } catch (e) {
        this.hasAgreementPdf = false;
      } finally {
        this.generating = false;
      }
    },
    async handleGeneratePdf() {
      this.generating = true;
      try {
        let result = await api.generateAgreementPdf();
        if (result.status === 0) {
          this.hasAgreementPdf = true;
          // result.data 现在是图片数组
          if (Array.isArray(result.data) && result.data.length > 0) {
            this.imageUrls = result.data.map(url => {
              if (!url.startsWith('http')) {
                return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
              }
              return url;
            });
            this.$message.success(this.$t('jy558'));
          } else {
            this.$message.error(this.$t('jy559'));
          }
        } else {
          this.$message.error(result.msg || this.$t('jy560'));
        }
      } catch (e) {
        this.$message.error(this.$t('jy560') + ': ' + e.message);
      } finally {
        this.generating = false;
      }
    },
    initContractSignaturePad() {
      this.$nextTick(() => {
        if (this.$refs.contractSignaturePad) {
          this.contractSignatureInstance = this.$refs.contractSignaturePad.signaturePad;
        }
      });
    },
    clearContractSignature() {
      this.$nextTick(() => {
        if (!this.contractSignatureInstance && this.$refs.contractSignaturePad) {
          this.contractSignatureInstance = this.$refs.contractSignaturePad.signaturePad;
        }
        if (this.contractSignatureInstance) {
          this.contractSignatureInstance.clear();
        }
      });
    },
    confirmContractSignature() {
      // 防止重复点击
      if (this.submittingContractSignature) {
        return;
      }
      this.$nextTick(() => {
        if (!this.contractSignatureInstance && this.$refs.contractSignaturePad) {
          this.contractSignatureInstance = this.$refs.contractSignaturePad.signaturePad;
        }
        if (this.contractSignatureInstance) {
          if (!this.contractSignatureInstance.isEmpty()) {
            const signatureImg = this.contractSignatureInstance.toDataURL('image/png');
            this.submitContractAgreement(signatureImg);
          } else {
            this.$message.error(this.$t('jy554'));
          }
        } else {
          this.$message.error(this.$t('jy555'));
        }
      });
    },
    async submitContractAgreement(signatureImg) {
      // 防止重复提交
      if (this.submittingContractSignature) {
        return;
      }
      this.submittingContractSignature = true;
      try {
        const blob = await this.dataURLtoBlob(signatureImg);
        const formData = new FormData();
        formData.append('signature', blob, 'signature.png');
        const config = {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        };
        if (window.localStorage.getItem("USERTOKEN")) {
          config.headers["USERTOKEN"] = window.localStorage.getItem("USERTOKEN");
        }
        const result = await axios.post(APIUrl.baseURL + '/user/saveAgreementContractSignature.do', formData, config);
        if (result.data.status === 0) {
          this.$message.success(this.$t('jy556'));
          this.showContractSignature = false;
          // 重新获取用户信息,更新签名状态
          let userData = await api.getUserInfo();
          if (userData.status === 0) {
            this.currentUser = userData.data;
            this.$store.state.userInfo = userData.data;
          }
          // 延迟跳转,让用户看到成功提示
          setTimeout(() => {
            this.$router.push('/User');
          }, 1500);
        } else {
          this.$message.error(result.data.msg || this.$t('jy557'));
          this.submittingContractSignature = false;
        }
      } catch (error) {
        console.error('提交合同签名失败:', error);
        this.$message.error(this.$t('jy557'));
        this.submittingContractSignature = false;
      }
    },
    async checkContractPdf() {
      try {
        // type == 7 是保密协议
        if (this.type == 7) {
          const APIUrl = require('@/axios/api.url').default;
          // 判断是否已签名:isSignedAgreement === 'Y'
          const isSigned = this.currentUser && this.currentUser.isSignedAgreement === 'Y';
          if (isSigned) {
            // 如果已签名,调用viewAgreementContractPdf接口获取已签名的图片(从数据库查询)
            this.generatingContract = true;
            let result = await api.viewAgreementContractPdf();
            console.log('viewAgreementContractPdf返回结果:', result);
            if (result.status === 0) {
              this.hasContractPdf = true;
              // result.data 现在是图片数组
              if (Array.isArray(result.data) && result.data.length > 0) {
                this.contractImageUrls = result.data.map(url => {
                  if (!url.startsWith('http')) {
                    return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
                  }
                  return url;
                });
                console.log('处理后的图片URLs:', this.contractImageUrls);
              } else {
                console.warn('未返回图片数据或数据为空');
                this.hasContractPdf = false;
              }
            } else {
              console.error('接口返回错误:', result.msg);
              this.hasContractPdf = false;
            }
            this.generatingContract = false;
            return;
          }
          // 如果未签名(isSignedAgreement为空),调用generateAgreementContractPdf接口生成并渲染图片
          this.generatingContract = true;
          let result = await api.generateAgreementContractPdf();
          if (result.status === 0) {
            this.hasContractPdf = true;
            // result.data 现在是图片数组
            if (Array.isArray(result.data) && result.data.length > 0) {
              this.contractImageUrls = result.data.map(url => {
                if (!url.startsWith('http')) {
                  return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
                }
                return url;
              });
            } else {
              this.hasContractPdf = false;
            }
          } else {
            this.hasContractPdf = false;
          }
          this.generatingContract = false;
          return;
        }
      } catch (e) {
        console.error('checkContractPdf错误:', e);
        this.hasContractPdf = false;
      } finally {
        this.generatingContract = false;
      }
    },
    async handleGenerateContractPdf() {
      this.generatingContract = true;
      try {
        let result = await api.generateAgreementContractPdf();
        if (result.status === 0) {
          this.hasContractPdf = true;
          // result.data 现在是图片数组
          if (Array.isArray(result.data) && result.data.length > 0) {
            this.contractImageUrls = result.data.map(url => {
              if (!url.startsWith('http')) {
                return APIUrl.baseURL + (url.startsWith('/') ? url : '/' + url);
              }
              return url;
            });
            this.$message.success(this.$t('jy558'));
          } else {
            this.$message.error(this.$t('jy559'));
          }
        } else {
          this.$message.error(result.msg || this.$t('jy560'));
        }
      } catch (e) {
        this.$message.error(this.$t('jy560') + ': ' + e.message);
      } finally {
        this.generatingContract = false;
      }
    },
    async handleGenerateContractPdfOld() {
      this.generatingContract = true;
      try {
        let result = await api.generateAgreementContractPdf();
        if (result.status === 0) {
          this.hasContractPdf = true;
          let url = result.data || '/user/viewAgreementContractPdf.do';
          if (!url.startsWith('http')) {
            url = APIUrl.baseURL + url;
          }
          this.contractPdfUrl = url;
          this.$message.success(this.$t('jy558'));
        } else {
          this.$message.error(result.msg || this.$t('jy560'));
        }
      } catch (e) {
        this.$message.error(this.$t('jy560') + ': ' + e.message);
      } finally {
        this.generatingContract = false;
      }
    }
  }
};
@@ -270,6 +741,9 @@
.bijnm {
  background: #fff;
  min-height: 100vh;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}
.headf {
@@ -311,14 +785,128 @@
}
.hezi {
  width: 9.2115rem;
  width: 100%;
  max-width: 100%;
  padding: 0 20px;
  border-bottom: 0.0267rem solid #e0e0e0;
  margin: 0 auto;
  margin-top: 1.1748rem;
  padding-bottom: 0.534rem;
  box-sizing: border-box;
}
.hezi img {
  width: 5rem;
}
.btn-generate, .btn-view {
  padding: 10px 20px;
  background: linear-gradient(-55deg, rgb(241, 22, 20), rgb(240, 40, 37));
  color: #fff;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
}
.btn-generate:disabled {
  background: #ccc;
  cursor: not-allowed;
}
.agreement-container {
  padding: 0;
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  height: calc(100vh - 1.1748rem - 100px);
  max-height: calc(100vh - 1.1748rem - 100px);
  margin-top: -20px;
}
.signature-section {
  margin-top: 20px;
  padding: 20px;
  background: #f9f9f9;
  border-radius: 5px;
}
.signature-section h4 {
  margin-bottom: 15px;
  font-size: 18px;
  font-weight: bold;
}
.signature-canvas {
  width: 100%;
  height: 300px;
  border: 2px dashed #ccc;
  margin: 15px 0;
  background: #fff;
  border-radius: 5px;
}
.signature-btns {
  display: flex;
  gap: 10px;
  margin-top: 10px;
}
.btn-signature {
  padding: 10px 20px;
  background: linear-gradient(-55deg, rgb(241, 22, 20), rgb(240, 40, 37));
  color: #fff;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
  width: 100%;
  margin-top: 20px;
}
.signature-tip {
  margin-top: 20px;
  padding: 15px;
  background: #e8f5e9;
  border-radius: 5px;
  text-align: center;
}
.signature-tip p {
  margin: 0;
  color: #4caf50;
  font-size: 16px;
  font-weight: bold;
}
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  min-height: 300px;
}
.loading-spinner {
  width: 50px;
  height: 50px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid rgb(241, 22, 20);
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 20px;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
.loading-text {
  color: #666;
  font-size: 16px;
  margin-top: 10px;
}
</style>