Function is introduced

Feature list

The function is not much, after all, is casual practice hand, at present to a local version, do not need background can use.

Main functions:

  • Draw logic
  • The prize list
  • The winners
  • Prize quantity control
  • User Operation Tips
  • Background music control

Interface to preview

Draw the page

The prize list

The winners

Prepare for the code

There is nothing to talk about, the front end of the siege lion is a new technology, and the editor I used is VSCode.

Vite creates the project

If you don’t know what a Vite is? Go to www.vitejs.dev/for more information.

Package management tool to use YARN, if using NPM friends do not copy oh ~~

yarn create @vitejs/app
Copy the code

After typing on the command line, there will be an interactive operation where you enter your own project name. The template uses vue3+ts.

ESLint and Prettier configurations

Install dependencies

Related packages are:

#Here in order to let you see a little bit more clearly, so separate installation

yarn add -D prettier
yarn add -D eslint
yarn add -D eslint-plugin-vue
yarn add -D eslint-plugin-prettier
yarn add -D eslint-config-prettier
yarn add -D @typescript-eslint/eslint-plugin
yarn add -D @typescript-eslint/parser
Copy the code

ESLint configuration

Create the.eslintrc.js file in the project root directory and configure it as follows (rules can be modified according to your preferences) :

module.exports = {
  parser: 'vue-eslint-parser'.parserOptions: {
    parser: '@typescript-eslint/parser'.ecmaVersion: 2020.sourceType: 'module'
  },
  extends: [
    'plugin:vue/vue3-recommended'.'plugin:@typescript-eslint/recommended'.'plugin:prettier/recommended'.'prettier'].rules: {
    'no-unused-vars': 'off'.'@typescript-eslint/no-unused-vars': 'off'.'@typescript-eslint/explicit-module-boundary-types': 'off'}}Copy the code

Prettier configuration

Create the file prettier.config.js in the root directory of the project and configure it as follows (adjust it according to your coding habits) :

// What is the meaning of the word
module.exports = {
  printWidth: 80.tabWidth: 2.useTabs: false.vueIndentScriptAndStyle: false.singleQuote: true.quoteProps: 'as-needed'.trailingComma: 'none'.endOfLine: 'auto'.semi: false
}
Copy the code

Editor configuration (optional)

Since I often switch between MacOS and Windows, I need to configure the editor to keep it uniform. Before configuration, I need to install the EditorConfig for VSCode plug-in in the VSCode plug-in store.

After installation, create a. Editorconfig file in the root directory of the project and configure it as follows:

Editor configuration

root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
# insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false
Copy the code

Element Plus configuration

I use the viet-plugin-IMP plugin. The official documentation uses the viet-plugin-style-import plugin. Here you can choose.

Method 1: Refer to the official documentation: Element+ Quick Start

Method 2:

yarn add element-plus vite-plugin-imp
Copy the code

After installation, open the root directory vite.config.ts file to configure:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vitePluginImp from 'vite-plugin-imp'
import path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      '@ /': `${path.resolve(__dirname, 'src')}/ `}},plugins: [
    vue(),
    vitePluginImp({
      libList: [{libName: 'element-plus'.style: (name) = > {
            return `element-plus/lib/theme-chalk/${name}.css`}}]})})Copy the code

If there is an error with path, install @types/node:

yarn add -D @types/node
Copy the code

Vue Router and Vuex

The function of this project is relatively simple, so I don’t need to configure it. If you want to know more about it, you can go to the official website and have a look. Or you can use VUE-CLI to create a vuE3 project and have a look at the code.

Ready to go. Open the code

Template section

<template>
  <div>
    <! -- Audio related -->
    <audio ref="music" preload="auto" loop :src="musicFile" />
    <audio ref="bgm" preload="auto" loop :src="bgmFile" />

    <! -- Background decoration image -->
    <div class="heng-fu">
      <el-image :src="hengFuImg" />
    </div>
    <div class="deng-long-left">
      <el-image :src="dengLongImg" />
    </div>
    <div class="deng-long-right">
      <el-image :src="dengLongImg" />
    </div>
    <div class="niu-left">
      <el-image :src="niuLeftImg" />
    </div>
    <div class="niu-right">
      <el-image :src="niuRightImg" />
    </div>

    <! -- Button related -->
    <div class="music-btn">
      <input
        type="image"
        :src="musicImg"
        title="Music switch"
        class="btn-music"
        @click="musicOpen = ! musicOpen"
      />
    </div>
    <el-tooltip content="Lucky draw" placement="top" effect="light">
      <input
        type="image"
        :src="jinLiImg"
        class="btn-jin-li"
        @click="lotteryBtnClick"
      />
    </el-tooltip>
    <el-tooltip content="The prize" placement="top" effect="light">
      <input
        type="image"
        :src="hongBaoImg"
        class="btn-hong-bao"
        @click="prizeDrawer = true"
      />
    </el-tooltip>
    <el-tooltip content="List of winners" placement="top" effect="light">
      <input
        type="image"
        :src="huaDuoImg"
        class="btn-hua-duo"
        @click="recordDrawer = true"
      />
    </el-tooltip>

    <! -- Middle avatar related -->
    <div class="avatar">
      <el-avatar
        :size="200"
        :src="avatarUrl"
        shape="square"
        class="avatar-border"
        @click="retryLottery"
      />
      <div class="name-label">{{ currentName }}</div>
    </div>

    <! -- Prize list sidebar -->
    <el-drawer v-model="prizeDrawer" title="List of prizes" size="400">
      <div style="padding: 0 20px 20px 20px">
        <el-select v-model="currentPrizeTitle" placeholder="Please select the prize to draw.">
          <el-option
            v-for="prize in prizes"
            :key="prize.type"
            :label="prize.title"
            :value="prize.title"
            :disabled="isPrizeUnavailable(prize.title)"
          >
            <span style="float: left">{{ prize.title }}</span>
            <span style="float: right; color: #8492a6; font-size: 13px">{{
              prize.text
            }}</span>
          </el-option>
        </el-select>
      </div>
      <el-space direction="vertical">
        <el-card v-for="prize in prizes" :key="prize.type" class="prize-card">
          <el-image
            style="width: 100px; height: 100px"
            :src="prize.img"
            fit="contain"
          />
          <div style="margin-left: 20px">
            <h4>{{prize. Text}} / {{prize. Count}}</h4>
            <h5>{{ prize.title }}</h5>
          </div>
        </el-card>
      </el-space>
    </el-drawer>

    <! -- Winning list sidebar -->
    <el-drawer v-model="recordDrawer" title="List of winners" size="400">
      <el-empty v-if="lotteryRecords.length <= 0" description="No record at present" />
      <el-space v-else direction="vertical" :size="0">
        <div
          v-for="(record, index) in lotteryRecords"
          :key="index"
          class="lottery-record"
        >
          <div class="lottery-record-content">
            <el-avatar :size="50" :src="record.avatar" />
            <div style="margin-left: 20px">
              <span style="font-weight: bold">{{ record.name }}</span>In the smoking<span style="font-weight: bold">{{ record.prize }}</span>
            </div>
          </div>
          <el-divider style="margin: 10px 0" />
        </div>
      </el-space>
    </el-drawer>
  </div>
</template>
Copy the code

Style part

<style>
body {
  margin: 0;
}

#app {
  width: 100vw;
  height: 100vh;
  background-color: #f39f86;
  background-image: linear-gradient(315deg.#f39f86 0%.#f9d976 74%);
}

.heng-fu {
  position: absolute;
  top: 24px;
  left: 50%;
  transform: translateX(-50%);
}

.deng-long-left {
  position: absolute;
  left: 30px;
  top: 0;
}

.deng-long-right {
  position: absolute;
  right: 30px;
  top: 0;
}

.niu-left {
  position: absolute;
  bottom: 0;
  left: 30px;
}

.niu-right {
  position: absolute;
  bottom: 0;
  right: 30px;
}

.btn-jin-li {
  position: absolute;
  bottom: 55px;
  left: calc(50% - 250px);
  width: 130px;
  height: 60px;
}

.btn-hong-bao {
  position: absolute;
  bottom: 30px;
  left: 50%;
  width: 130px;
  height: 133px;
  transform: translateX(-50%);
}

.btn-hua-duo {
  position: absolute;
  bottom: 0;
  left: calc(50% + 120px);
  width: 85px;
  height: 130px;
}

.music-btn {
  position: absolute;
  right: 15px;
  top: 15px;
}

.btn-music {
  width: 40px;
  height: 40px;
}

.avatar {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
}

.avatar-border {
  padding: 5px;
  background: white;
}

.name-label {
  margin-top: 10px;
  font-size: 30px;
  color: #fff;
  font-weight: bold;
  text-align: center;
}

input:focus {
  outline: none;
}

.prize-card {
  margin: 0 20px;
  width: 360px;
}

.el-card__body {
  padding: 0;
  height: 100px;
  display: flex;
  flex-direction: row;
}

.el-drawer {
  width: 400px;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

.lottery-record {
  margin: 0 20px;
  width: 360px;
}

.lottery-record-content {
  display: flex;
  flex-direction: row;
  align-items: center;
}
</style>
Copy the code

Script part

import { computed, defineComponent, ref, watch } from 'vue'
import { ElMessage, ElNotification, ElMessageBox } from 'element-plus'
import personJsonData from './assets/json/person.json'
import { prizes } from './models/prize'
import { res } from './composables/res'
import {
  musicOpen,
  switchLotteryEffect,
  music,
  bgm
} from './composables/music-control'

export default defineComponent({
  name: 'App'.setup() {
    /** Whether to open the prize sidebar */
    const prizeDrawer = ref(false)
    /** Whether the list of winners sidebar is open */
    const recordDrawer = ref(false)
    /** Whether the lottery is over */
    const lotteryFinish = ref(false)
    // List of all personnel
    const personList = ref<Person[]>(personJsonData)
    // Current personnel profile picture
    const avatarUrl = ref(personList.value[0].avatar)
    // The name of the current person
    const currentName = ref(personList.value[0].name)

    const availablePersons = computed(() = > {
      // Set the current list of people who can win awards, remove awards already
      const personToRemove = lotteryRecords.value.map((item) = > item.name)
      const filterArr = personList.value.filter(
        (item) = > personToRemove.indexOf(item.name) < 0
      )
      return filterArr
    })

    // Record of winning the lottery
    const lotteryRecords = ref<LotteryRecord[]>([])

    // Monitor the winning record, handle the prize switch and whether the lottery is over state
    watch(
      lotteryRecords,
      () = > {
        if (lotteryRecords.value.length > 0) {
          // Check whether the prize is exhausted and automatically switch the prize
          const isCurrentPrizeUnavailable = isPrizeUnavailable(
            currentPrizeTitle.value
          )
          if (isCurrentPrizeUnavailable) {
            const prePrize = currentPrizeTitle.value
            if (currentPrizeIndex.value > 0) {
              currentPrizeIndex.value--
              ElNotification({
                title: 'Message alert'.message: ` prize"${prePrize}Start drawing the prize${ prizes[currentPrizeIndex.value].title }】 `.position: 'top-left'.type: 'info'})}else {
              lotteryFinish.value = true
              ElNotification({
                title: 'Message alert'.message: 'Prizes are all drawn. The drawing is over.'.position: 'top-left'.type: 'warning'})}}}}, {deep: true})// Set a random maximum index of the number of people that can be drawn
    const maxIndex = computed(() = > availablePersons.value.length - 1)

    // Whether a lottery is in progress
    const lotteryRunning = ref(false)
    watch(lotteryRunning, () = > {
      switchLotteryEffect(lotteryRunning.value)
    })

    // Lottery timer
    let timer: NodeJS.Timeout

    / / draw
    const lottery = () = > {
      lotteryRunning.value = true
      const index = Math.round(Math.random() * maxIndex.value)
      avatarUrl.value = availablePersons.value[index].avatar
      currentName.value = availablePersons.value[index].name
      timer = setTimeout(lottery, 50)}// Stop the lottery
    const stopLottery = () = > {
      lotteryRunning.value = false
      clearTimeout(timer)

      const record: LotteryRecord = {
        avatar: avatarUrl.value,
        name: currentName.value,
        prize: currentPrizeTitle.value,
        prizeIndex: currentPrizeIndex.value
      }
      lotteryRecords.value.unshift(record)
    }

    // Check whether a lottery is in progress
    watch(lotteryRunning, () = > {
      if (lotteryRunning.value) {
        ElMessage.success({
          message: 'drawing${currentPrize.value.text} / ${currentPrize.value.title}`.type: 'success'
        })

        lottery()
      } else {
        stopLottery()
      }
    })

    /** * Lucky draw button click */
    const lotteryBtnClick = () = > {
      if (lotteryFinish.value) {
        ElNotification({
          title: 'Message alert'.message: 'Prizes are all drawn. The drawing is over.'.position: 'top-left'.type: 'warning'})}else{ lotteryRunning.value = ! lotteryRunning.value } }/** * click to re-draw */
    const retryLottery = () = > {
      console.log('retryLottery')
      if (lotteryRecords.value.length <= 0) return

      ElMessageBox.confirm('This operation will re-draw the prize, do you want to continue? '.'tip', {
        confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'
      })
        .then(() = > {
          const recordToRemove = lotteryRecords.value[0]
          if(recordToRemove.prizeIndex ! == currentPrizeIndex.value) { currentPrizeIndex.value = recordToRemove.prizeIndex } lotteryRecords.value.splice(0.1)
          ElMessage.success({
            message: 'Operation successful, please proceed with lucky draw'.type: 'success'
          })
        })
        .catch(() = > {
          console.log('cancel')})}/** Index */ of the array corresponding to the current prize
    const currentPrizeIndex = ref(prizes.length - 1)

    /** listen on the index of the current prize */
    watch(
      currentPrizeIndex,
      () = > (currentPrizeTitle.value = prizes[currentPrizeIndex.value].title)
    )

    /** The name of the current prize */
    const currentPrizeTitle = ref(prizes[currentPrizeIndex.value].title)

    /** Current prize draw */
    const currentPrize = computed(() = > {
      const filterArr = prizes.filter(
        (item) = > item.title === currentPrizeTitle.value
      )
      return filterArr.length > 0 ? filterArr[0] : <Prize>{}
    })

    /** Determine whether the prize is full */
    const isPrizeUnavailable = (prizeTitle: string) = > {
      const filterPrizes = prizes.filter((item) = > item.title === prizeTitle)
      if (filterPrizes.length <= 0) return false

      const prize = filterPrizes[0]
      const filterRecords = lotteryRecords.value.filter(
        (item) = > item.prize === prizeTitle
      )
      return prize.count <= filterRecords.length
    }

    return {
      ...res,
      avatarUrl,
      currentName,
      lotteryBtnClick,
      retryLottery,
      prizeDrawer,
      prizes,
      currentPrizeTitle,
      isPrizeUnavailable,
      lotteryRecords,
      recordDrawer,
      musicOpen,
      music,
      bgm
    }
  }
})
Copy the code

The front-end source

Making address Gitee

Back-end management system +API interface

After all, it took two hours to write a supporting back-end part using Django+Django Rest Framework.

API Interface

Management System Interface

Here is no more to say, interested friends can directly look at the code ~

The back-end source

Gitee

But do good, ask not the future