Smartline Technical Blog

website·Indexed 31 days ago

https://blog.smartline.cc

A technical blog featuring development guides and troubleshooting tips for frontend technologies, including Vue 2, Vue 3, Element UI, Element Plus, uni-app, Vite, ECharts, and Webpack.

Tokens
24.8K
Snippets
85
Records
114
Agent score
51%

What's inside Smartline Blog

  1. Install and configure network acceleration clients

    Follow these steps to set up the acceleration service on various platforms:

    1. Windows / Android / macOS: Download the client from the official website after logging in. If prompted about 'Unknown Sources' during installation, select 'Install anyway' or enable the necessary permissions in settings.
    2. Connection: Open the client, log in with your account, click 'Update Nodes' (更新节点), select a low-latency line, and click 'Quick Connect' (快速连接).
    3. iOS: Manually install Shadowrocket or Sing-box and import the subscription link to add nodes.
  2. Reduce ECharts bundle size for mini-programs via customization

    To stay within the strict code size limits of mini-programs, avoid importing the full ECharts library. Instead, use the official ECharts customization tool to generate a minimal echarts.min.js containing only the specific chart types (e.g., only line charts) required for the project.
  3. Optimize uni-app swiper performance for large datasets using a sliding window approach

    When rendering a large number of items (e.g., 100+) in a swiper component in WeChat Mini Programs, performance degrades and initial loading time increases. To solve this, instead of rendering the entire list, use a 'sliding window' technique with exactly three swiper-item elements acting as buffers (buckets). By setting circular to true and listening to the @change event, you can dynamically update the content of these three items to reflect the current, previous, and next items from the full dataset. This ensures smooth scrolling regardless of the total number of items.
    <template>
      <swiper
        class="swipercard"
        :circular="true"
        :autoplay="false"
        :current="swiperCurrentIndex"
        @change="eventHandle"
        :duration="300"
      >
        <block v-for="(item, index) in swiperList" :key="item.id">
          <swiper-item class="swiperItem">
            <view>{{item.name}}</view>
          </swiper-item>
        </block>
      </swiper>
    </template>
    
    <script>
    export default {
      data() {
        return {
          questionList: [ { id: 1, name: 'Question 1' }, ... ], // Full dataset
          swiperList: [], // The 3-item sliding window
          questionListIndex: 0, // Index of current item in full dataset
          swiperCurrentIndex: 0, // Current index within the 3-item swiper
        };
      },
      onLoad() {
        this.upDateSwiperList();
      },
      methods: {
        eventHandle(e) {
          let current = e.detail.current;
          
          // Detect forward slide
          if (this.swiperCurrentIndex - current == 2 || this.swiperCurrentIndex - current == -1) {
            this.questionListIndex = (this.questionListIndex + 1 == this.questionList.length) ? 0 : this.questionListIndex + 1;
            this.swiperCurrentIndex = (this.swiperCurrentIndex + 1 == 3) ? 0 : this.swiperCurrentIndex + 1;
            this.upDateSwiperList();
          }
          // Detect backward slide
          else if (this.swiperCurrentIndex - current == -2 || this.swiperCurrentIndex - current == 1) {
            this.questionListIndex = (this.questionListIndex - 1 == -1) ? this.questionList.length - 1 : this.questionListIndex - 1;
            this.swiperCurrentIndex = (this.swiperCurrentIndex - 1 == -1) ? 2 : this.swiperCurrentIndex - 1;
            this.upDateSwiperList();
          }
        },
        upDateSwiperList() {
          let displayList = [];
          // Current item
          displayList[this.swiperCurrentIndex] = this.questionList[this.questionListIndex];
          
          // Previous item
          let prevIdx = this.swiperCurrentIndex - 1 == -1 ? 2 : this.swiperCurrentIndex - 1;
          let prevDataIdx = this.questionListIndex - 1 == -1 ? this.questionList.length - 1 : this.questionListIndex - 1;
          displayList[prevIdx] = this.questionList[prevDataIdx];
    
          // Next item
          let nextIdx = this.swiperCurrentIndex + 1 == 3 ? 0 : this.swiperCurrentIndex + 1;
          let nextDataIdx = this.questionListIndex + 1 == this.questionList.length ? 0 : this.questionListIndex + 1;
          displayList[nextIdx] = this.questionList[nextDataIdx];
    
          this.swiperList = displayList;
        }
      }
    };
    </script>
  4. Implement remote search with lazy loading in el-tree-select

    To combine lazy loading (loading child nodes on demand) with remote searching (fetching filtered results from a server) in el-tree-select, you must configure both the lazy and remote properties. The load prop handles the lazy loading of child nodes, while the filter-method prop handles the remote search logic to update the data source.
    <template>
      <el-tree-select
        v-model="formDataModel.deptId"
        lazy
        :load="loadNode"
        filterable
        remote
        :filter-method="remoteMethod"
        :data="deptOptions"
        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
        :loading="treeloading"
        value-key="deptId"
      />
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const treeloading = ref(false);
    const deptOptions = ref([]);
    
    // Lazy load child nodes
    const loadNode = (node, resolve) => {
      findRootListByDeptIdList({
        parentId: node.data.deptId
      }).then((res) => {
        return resolve(res.data);
      });
    };
    
    // Remote search method
    const remoteMethod = (query) => {
      if (query) {
        treeloading.value = true;
        findRootListByDeptIdList({
          deptName: query
        }).then((res) => {
          deptOptions.value = res.data;
          treeloading.value = false;
        });
      }
    };
    </script>
  5. Validate forms generated with v-for loops in ElementUI

    When form elements are generated using a v-for loop, the prop attribute on el-form-item must be dynamically set to match the path of the data in the model object. Use a template literal to include the loop index in the prop path (e.g., :prop="'data.' + index + '.fieldName'"). This allows the validator to locate the specific array element and property being validated. You can also pass rules directly to the el-form-item using the :rules attribute if they are not defined in a global rules object.
    <template>
      <el-form :model="fullInfo" ref="form">
        <el-row v-for="(item, index) in beltData" :key="item.id">
          <el-col :span="12">
            <el-form-item
              :rules="rules.weightRule"
              :label="item.label"
              :prop="`data.${index}.muckWeight`"
              label-width="100px">
              <el-input v-model="fullInfo.data[index].muckWeight"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
    
        <el-form-item>
          <el-button type="primary" @click="submitForm">Submit</el-button>
        </el-form-item>
      </el-form>
    </template>
    
    <script>
    export default {
      data() {
        return {
          beltData: [{ id: 1, label: 'Ring 1' }],
          fullInfo: {
            data: [{ muckWeight: '' }]
          },
          rules: {
            weightRule: [
              { required: true, message: 'Field cannot be empty', trigger: 'change' },
              { pattern: '^[0-9]+$', message: 'Please enter a valid number' }
            ]
          }
        }
      },
      methods: {
        submitForm() {
          this.$refs.form.validate((valid) => {
            if (valid) alert('submit!')
          })
        }
      }
    }
    </script>
  6. Remove a file from Git remote while keeping it locally

    To remove a file (such as an .env file) from a remote Git repository that was accidentally committed, follow these steps:

    1. Add the file path to your .gitignore file in the project root to prevent it from being tracked again.
    2. Remove the file from the Git index (cache) without deleting the physical file from your local filesystem using the git rm --cached command.
    git rm --cached "filename"
  7. Implement standard form validation in ElementUI

    ElementUI uses async-validator for form validation. To implement standard validation, bind a rules object to the el-form component. The keys of the rules object must match the variable names bound to the form elements via v-model. Each rule is defined as an array of validation constraints. Use the prop attribute on el-form-item to specify which rule from the rules object applies to that specific field.
    <template>
      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px">
        <el-form-item label="Activity Name" prop="name">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
    
        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">Create</el-button>
        </el-form-item>
      </el-form>
    </template>
    
    <script>
    export default {
      data() {
        return {
          ruleForm: {
            name: ''
          },
          rules: {
            name: [
              { required: true, message: 'Please input activity name', trigger: 'blur' },
              { min: 3, max: 5, message: 'Length should be 3 to 5 characters', trigger: 'blur' }
            ]
          }
        }
      },
      methods: {
        submitForm(formName) {
          this.$refs[formName].validate((valid) => {
            if (valid) {
              alert('submit!')
            } else {
              return false
            }
          })
        }
      }
    }
    </script>
  8. Claim 60GB free monthly network acceleration

    Users can obtain a free monthly 60GB data plan (the 'Entry-level Pokéball' package) by registering an account and applying a monthly redemption code. This plan provides 200 Mbps bandwidth and supports up to 5 devices, allowing access to Google, Claude, and ChatGPT.
    Redemption Code: 吉利蛋
  9. Restore PDF bookmarks using FreePic2Pdf

    When converting a PDF to a dual-layer format via image merging, the original bookmarks are lost. Use FreePic2Pdf to transfer them from the source file to the new file:

    1. Extract Bookmarks: Open FreePic2Pdf, click Change PDF (bottom right), and select Extract Bookmarks from PDF. Select the original source PDF and a destination folder. This generates a .txt file containing the bookmark data.
    2. Apply Bookmarks: Select Apply Bookmarks to PDF. Select the newly merged/OCR-processed PDF and the folder containing the .txt bookmark file. Click Start.

    Note: Ensure the target PDF is not open in any other software during this process, otherwise the bookmark application will fail.

  10. Layer a background image over a background color in CSS

    To ensure a background image is displayed on top of a background color, the background color must be declared after the background image if using separate properties, or included within the background shorthand property. If background-color is declared before a background shorthand property, the shorthand property will overwrite the color.
    /* Option 1: Declare color AFTER the image */
    background: url('./images/banner11.png') no-repeat scroll left bottom/cover;
    background-color: #f5f5f5;
    
    /* Option 2: Use the shorthand property */
    background: #f5f5f5 url('./images/banner11.png') no-repeat scroll left bottom/cover;
  11. Deploy DeepSeek locally using LM Studio

    LM Studio provides a graphical user interface for downloading and running LLMs locally, avoiding the need for command-line tools like Ollama. To deploy DeepSeek:

    1. Download and install LM Studio from https://lmstudio.ai/ based on your operating system.
    2. Use the Model Search feature in the settings to search for "DeepSeek".
    3. Select and download a model compatible with your hardware (e.g., a 7B model is typically the performance limit for most modern laptops).
    4. In the chat interface, select the downloaded model from the top dropdown menu.
    5. Load the model using the default parameters or customize them in the configuration page.

    Key configuration options include:

    • Context Length: Increase this if you have long conversation histories.
    • GPU Offload (GPU 卸载): Adjust this to control the performance ratio allocated between the GPU and CPU.
  12. Prevent automatic PDF downloads on Android using pdfh5

    Android browsers often automatically download PDF files instead of previewing them. To implement a consistent PDF previewer across mobile platforms (iOS and Android) that avoids automatic downloads, use the pdfh5 library. This library renders the PDF within a specified HTML container using canvas.
    npm install pdfh5
    import Pdfh5 from "pdfh5";
    import "pdfh5/css/pdfh5.css";

    <!-- HTML Container --> <div id="pdfh5"></div>

    // Example implementation in a Vue/uniapp component
    onLoad() {
        this.$nextTick(() => {
            this.initPdf()
        })
    },
    methods: {
        initPdf() {
            this.filePreview = this.$route.query.url
            if (this.pdfh5) {
                // Destroy previous instance to avoid frequent creation
                this.pdfh5.destroy()
                this.pdfh5 = null
            }
            this.pdfh5 = new Pdfh5('#pdfh5', {
                pdfurl: this.filePreview,
                // Alternatively, use 'data' for file streams or base64 (converted via atob())
                // data: pdfFileStream 
            });
        }
    }