Compare commits

..

2 Commits

Author SHA1 Message Date
刘隽霖 0bb10f2828 Initial commit
2 months ago
刘隽霖 bc2786d736 Initial commit
2 months ago

@ -0,0 +1,35 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 80, // 每行宽度至多80字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' // 换行符号不限制win mac 不一致)
}
],
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index'] // vue组件名称多单词组成忽略index.vue
}
],
'vue/no-setup-props-destructure': ['off'], // 关闭 props 解构的校验
// 💡 添加未定义变量错误提示create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'error'
}
}

30
.gitignore vendored

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

@ -0,0 +1,40 @@
{
"hash": "60808396",
"configHash": "59b8cfd4",
"lockfileHash": "dc70f975",
"browserHash": "f37eb18a",
"optimized": {
"vue": {
"src": "../../node_modules/.pnpm/vue@3.5.11/node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "b734d491",
"needsInterop": false
},
"pinia": {
"src": "../../node_modules/.pnpm/pinia@2.2.4_vue@3.5.11/node_modules/pinia/dist/pinia.mjs",
"file": "pinia.js",
"fileHash": "9cec28a2",
"needsInterop": false
},
"pinia-plugin-persistedstate": {
"src": "../../node_modules/.pnpm/pinia-plugin-persistedstate@4.1.1_pinia@2.2.4_vue@3.5.11__rollup@4.24.0/node_modules/pinia-plugin-persistedstate/dist/index.js",
"file": "pinia-plugin-persistedstate.js",
"fileHash": "e1a46a27",
"needsInterop": false
},
"vue-router": {
"src": "../../node_modules/.pnpm/vue-router@4.4.5_vue@3.5.11/node_modules/vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "97ce4aa0",
"needsInterop": false
}
},
"chunks": {
"chunk-BPR5FKHA": {
"file": "chunk-BPR5FKHA.js"
},
"chunk-UGVUZKZS": {
"file": "chunk-UGVUZKZS.js"
}
}
}

@ -0,0 +1,162 @@
// node_modules/.pnpm/@vue+devtools-api@6.6.4/node_modules/@vue/devtools-api/lib/esm/env.js
function getDevtoolsGlobalHook() {
return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
}
function getTarget() {
return typeof navigator !== "undefined" && typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : {};
}
var isProxyAvailable = typeof Proxy === "function";
// node_modules/.pnpm/@vue+devtools-api@6.6.4/node_modules/@vue/devtools-api/lib/esm/const.js
var HOOK_SETUP = "devtools-plugin:setup";
var HOOK_PLUGIN_SETTINGS_SET = "plugin:settings:set";
// node_modules/.pnpm/@vue+devtools-api@6.6.4/node_modules/@vue/devtools-api/lib/esm/time.js
var supported;
var perf;
function isPerformanceSupported() {
var _a;
if (supported !== void 0) {
return supported;
}
if (typeof window !== "undefined" && window.performance) {
supported = true;
perf = window.performance;
} else if (typeof globalThis !== "undefined" && ((_a = globalThis.perf_hooks) === null || _a === void 0 ? void 0 : _a.performance)) {
supported = true;
perf = globalThis.perf_hooks.performance;
} else {
supported = false;
}
return supported;
}
function now() {
return isPerformanceSupported() ? perf.now() : Date.now();
}
// node_modules/.pnpm/@vue+devtools-api@6.6.4/node_modules/@vue/devtools-api/lib/esm/proxy.js
var ApiProxy = class {
constructor(plugin, hook) {
this.target = null;
this.targetQueue = [];
this.onQueue = [];
this.plugin = plugin;
this.hook = hook;
const defaultSettings = {};
if (plugin.settings) {
for (const id in plugin.settings) {
const item = plugin.settings[id];
defaultSettings[id] = item.defaultValue;
}
}
const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
let currentSettings = Object.assign({}, defaultSettings);
try {
const raw = localStorage.getItem(localSettingsSaveId);
const data = JSON.parse(raw);
Object.assign(currentSettings, data);
} catch (e) {
}
this.fallbacks = {
getSettings() {
return currentSettings;
},
setSettings(value) {
try {
localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
} catch (e) {
}
currentSettings = value;
},
now() {
return now();
}
};
if (hook) {
hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
if (pluginId === this.plugin.id) {
this.fallbacks.setSettings(value);
}
});
}
this.proxiedOn = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target.on[prop];
} else {
return (...args) => {
this.onQueue.push({
method: prop,
args
});
};
}
}
});
this.proxiedTarget = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target[prop];
} else if (prop === "on") {
return this.proxiedOn;
} else if (Object.keys(this.fallbacks).includes(prop)) {
return (...args) => {
this.targetQueue.push({
method: prop,
args,
resolve: () => {
}
});
return this.fallbacks[prop](...args);
};
} else {
return (...args) => {
return new Promise((resolve) => {
this.targetQueue.push({
method: prop,
args,
resolve
});
});
};
}
}
});
}
async setRealTarget(target) {
this.target = target;
for (const item of this.onQueue) {
this.target.on[item.method](...item.args);
}
for (const item of this.targetQueue) {
item.resolve(await this.target[item.method](...item.args));
}
}
};
// node_modules/.pnpm/@vue+devtools-api@6.6.4/node_modules/@vue/devtools-api/lib/esm/index.js
function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
const descriptor = pluginDescriptor;
const target = getTarget();
const hook = getDevtoolsGlobalHook();
const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
} else {
const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
list.push({
pluginDescriptor: descriptor,
setupFn,
proxy
});
if (proxy) {
setupFn(proxy.proxiedTarget);
}
}
}
export {
setupDevtoolsPlugin
};
//# sourceMappingURL=chunk-BPR5FKHA.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
{
"type": "module"
}

@ -0,0 +1,239 @@
// node_modules/.pnpm/destr@2.0.3/node_modules/destr/dist/index.mjs
var suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
var suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
var JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
function jsonParseTransform(key, value) {
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
warnKeyDropped(key);
return;
}
return value;
}
function warnKeyDropped(key) {
console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
}
function destr(value, options = {}) {
if (typeof value !== "string") {
return value;
}
const _value = value.trim();
if (
// eslint-disable-next-line unicorn/prefer-at
value[0] === '"' && value.endsWith('"') && !value.includes("\\")
) {
return _value.slice(1, -1);
}
if (_value.length <= 9) {
const _lval = _value.toLowerCase();
if (_lval === "true") {
return true;
}
if (_lval === "false") {
return false;
}
if (_lval === "undefined") {
return void 0;
}
if (_lval === "null") {
return null;
}
if (_lval === "nan") {
return Number.NaN;
}
if (_lval === "infinity") {
return Number.POSITIVE_INFINITY;
}
if (_lval === "-infinity") {
return Number.NEGATIVE_INFINITY;
}
}
if (!JsonSigRx.test(value)) {
if (options.strict) {
throw new SyntaxError("[destr] Invalid JSON");
}
return value;
}
try {
if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
if (options.strict) {
throw new Error("[destr] Possible prototype pollution");
}
return JSON.parse(value, jsonParseTransform);
}
return JSON.parse(value);
} catch (error) {
if (options.strict) {
throw error;
}
return value;
}
}
// node_modules/.pnpm/deep-pick-omit@1.2.1/node_modules/deep-pick-omit/dist/index.mjs
function get(obj, path) {
if (obj == null)
return void 0;
let value = obj;
for (let i = 0; i < path.length; i++) {
if (value == null || value[path[i]] == null)
return void 0;
value = value[path[i]];
}
return value;
}
function set(obj, value, path) {
if (path.length === 0)
return value;
const idx = path[0];
if (path.length > 1) {
value = set(
typeof obj !== "object" || obj === null || !Object.prototype.hasOwnProperty.call(obj, idx) ? Number.isInteger(Number(path[1])) ? [] : {} : obj[idx],
value,
Array.prototype.slice.call(path, 1)
);
}
if (Number.isInteger(Number(idx)) && Array.isArray(obj))
return obj.slice()[idx];
return Object.assign({}, obj, { [idx]: value });
}
function unset(obj, path) {
if (obj == null || path.length === 0)
return obj;
if (path.length === 1) {
if (obj == null)
return obj;
if (Number.isInteger(path[0]) && Array.isArray(obj))
return Array.prototype.slice.call(obj, 0).splice(path[0], 1);
const result = {};
for (const p in obj)
result[p] = obj[p];
delete result[path[0]];
return result;
}
if (obj[path[0]] == null) {
if (Number.isInteger(path[0]) && Array.isArray(obj))
return Array.prototype.concat.call([], obj);
const result = {};
for (const p in obj)
result[p] = obj[p];
return result;
}
return set(
obj,
unset(
obj[path[0]],
Array.prototype.slice.call(path, 1)
),
[path[0]]
);
}
function deepPickUnsafe(obj, paths) {
return paths.map((p) => p.split(".")).map((p) => [p, get(obj, p)]).filter((t) => t[1] !== void 0).reduce((acc, cur) => set(acc, cur[1], cur[0]), {});
}
function deepOmitUnsafe(obj, paths) {
return paths.map((p) => p.split(".")).reduce((acc, cur) => unset(acc, cur), obj);
}
// node_modules/.pnpm/pinia-plugin-persistedstate@4.1.1_pinia@2.2.4_vue@3.5.11__rollup@4.24.0/node_modules/pinia-plugin-persistedstate/dist/index.js
function hydrateStore(store, {
storage,
serializer,
key,
debug,
pick,
omit,
beforeHydrate,
afterHydrate
}, context, runHooks = true) {
try {
if (runHooks)
beforeHydrate == null ? void 0 : beforeHydrate(context);
const fromStorage = storage.getItem(key);
if (fromStorage) {
const deserialized = serializer.deserialize(fromStorage);
const picked = pick ? deepPickUnsafe(deserialized, pick) : deserialized;
const omitted = omit ? deepOmitUnsafe(picked, omit) : picked;
store.$patch(omitted);
}
if (runHooks)
afterHydrate == null ? void 0 : afterHydrate(context);
} catch (error) {
if (debug)
console.error("[pinia-plugin-persistedstate]", error);
}
}
function persistState(state, {
storage,
serializer,
key,
debug,
pick,
omit
}) {
try {
const picked = pick ? deepPickUnsafe(state, pick) : state;
const omitted = omit ? deepOmitUnsafe(picked, omit) : picked;
const toStorage = serializer.serialize(omitted);
storage.setItem(key, toStorage);
} catch (error) {
if (debug)
console.error("[pinia-plugin-persistedstate]", error);
}
}
function createPersistence(context, optionsParser, auto) {
const { pinia, store, options: { persist = auto } } = context;
if (!persist)
return;
if (!(store.$id in pinia.state.value)) {
const originalStore = pinia._s.get(store.$id.replace("__hot:", ""));
if (originalStore)
Promise.resolve().then(() => originalStore.$persist());
return;
}
const persistenceOptions = Array.isArray(persist) ? persist : persist === true ? [{}] : [persist];
const persistences = persistenceOptions.map(optionsParser);
store.$hydrate = ({ runHooks = true } = {}) => {
persistences.forEach((p) => {
hydrateStore(store, p, context, runHooks);
});
};
store.$persist = () => {
persistences.forEach((p) => {
persistState(store.$state, p);
});
};
persistences.forEach((p) => {
hydrateStore(store, p, context);
store.$subscribe(
(_mutation, state) => persistState(state, p),
{ detached: true }
);
});
}
function createPersistedState(options = {}) {
return function(context) {
createPersistence(
context,
(p) => ({
key: (options.key ? options.key : (x) => x)(p.key ?? context.store.$id),
debug: p.debug ?? options.debug ?? false,
serializer: p.serializer ?? options.serializer ?? {
serialize: (data) => JSON.stringify(data),
deserialize: (data) => destr(data)
},
storage: p.storage ?? options.storage ?? window.localStorage,
beforeHydrate: p.beforeHydrate,
afterHydrate: p.afterHydrate,
pick: p.pick,
omit: p.omit
}),
options.auto ?? false
);
};
}
var src_default = createPersistedState();
export {
createPersistedState,
src_default as default
};
//# sourceMappingURL=pinia-plugin-persistedstate.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,343 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-UGVUZKZS.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

@ -1,2 +1,35 @@
# quick-roll
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

@ -0,0 +1,44 @@
{
"name": "quick-roll",
"version": "0.0.0",
"private": true,
"type": "module",
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
]
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/",
"prepare": "husky install",
"lint-staged": "lint-staged",
"test": "echo \"Error: no test specified\" && exit 0"
},
"dependencies": {
"axios": "^1.7.7",
"element-plus": "^2.8.4",
"mockjs": "^1.1.0",
"pinia": "^2.1.7",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"husky": "^8.0.0",
"lint-staged": "^15.2.10",
"pinia-plugin-persistedstate": "^4.1.1",
"prettier": "^3.2.5",
"sass": "^1.79.4",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.3.1"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,9 @@
<script setup></script>
<template>
<div>
<router-view></router-view>
</div>
</template>
<style scoped></style>

@ -0,0 +1,135 @@
<template>
<div class="scrolling-number">
<div
v-for="(item, index) in numbers"
:key="index"
class="number"
:class="{
active: currentNumber === index,
exiting: previousNumber === index
}"
>
{{ item }}
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import { defineEmits, defineProps } from 'vue'
const props = defineProps({
duration: {
type: Number,
default: 100 //
},
isScrolling: {
type: Boolean,
default: true //
},
stopNumber: {
type: Number,
default: 0
}
})
const emit = defineEmits(['rollCompleted'])
const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const currentNumber = ref(0)
const previousNumber = ref(null)
let addTime = ref(0)
let animationFrameId
let currentDuration = ref(props.duration) //
let lastUpdateTime = 0
const changeNumber = () => {
previousNumber.value = currentNumber.value
currentNumber.value = (currentNumber.value + 1) % numbers.length
}
const startScrolling = () => {
addTime.value = 0
currentDuration.value = props.duration
const scroll = (timestamp) => {
if (timestamp - lastUpdateTime >= currentDuration.value) {
changeNumber() //
lastUpdateTime = timestamp
currentDuration.value += addTime.value // 使
}
animationFrameId = requestAnimationFrame(scroll)
}
animationFrameId = requestAnimationFrame(scroll)
}
const stopScrolling = () => {
cancelAnimationFrame(animationFrameId)
emit('rollCompleted') // rollCompleted
}
watch(
() => props.isScrolling,
(newVal) => {
if (newVal) {
startScrolling()
} else {
addTime.value = 160
const waitForOne = () => {
if (currentNumber.value === props.stopNumber) {
stopScrolling()
} else {
requestAnimationFrame(waitForOne)
}
}
waitForOne()
}
}
)
onMounted(() => {
if (props.isScrolling) {
startScrolling()
}
})
onBeforeUnmount(() => {
stopScrolling()
})
</script>
<style scoped>
.scrolling-number {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100px;
height: 120px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
background-color: #f8f8f8;
border: 3px solid #bcbaba;
}
.number {
font-size: 3rem;
position: absolute;
opacity: 0;
transform: translateY(-100%);
transition:
opacity 0.3s ease,
transform 0.3s ease;
color: #333;
}
.number.active {
opacity: 1;
transform: translateY(0);
}
.number.exiting {
opacity: 0;
transform: translateY(100%);
}
</style>

@ -0,0 +1,13 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
import '@/mock/upload.js'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia().use(persist))
app.use(router)
app.mount('#app')

@ -0,0 +1,6 @@
import Mock from 'mockjs'
Mock.mock('http://localhost:8080/upload', 'post', {
status: 200,
message: '文件上传成功!'
})

@ -0,0 +1,21 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/views/upload/UploadFilePage.vue')
},
{
path: '/roll-call',
component: () => import('@/views/roll/RollCallPage.vue')
},
{
path: '/rating',
component: () => import('@/views/rating/RatingPage.vue')
}
]
})
export default router

@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
// 用户模块
export const useUserStore = defineStore(
'big-user',
() => {
const token = ref('') // 定义 token
const setToken = (t) => (token.value = t) // 设置 token
return { token, setToken }
},
{
persist: true // 持久化
}
)

@ -0,0 +1,46 @@
import { useUserStore } from '@/stores/user'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'
const baseURL = 'http://big-event-vue-api-t.itheima.net'
const instance = axios.create({
baseURL,
timeout: 100000
})
instance.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = userStore.token
}
return config
},
(err) => Promise.reject(err)
)
instance.interceptors.response.use(
(res) => {
if (res.data.code === 0) {
return res
}
ElMessage({ message: res.data.message || '服务异常', type: 'error' })
return Promise.reject(res.data)
},
(err) => {
ElMessage({
message: err.response.data.message || '服务异常',
type: 'error'
})
console.log(err)
if (err.response?.status === 401) {
router.push('/login')
}
return Promise.reject(err)
}
)
export default instance
export { baseURL }

@ -0,0 +1,67 @@
<template>
<div class="container">
<h1 class="heading">
就决定是你了<br />
田所浩二
</h1>
<h2 class="sub-heading">你成功的复述了所提的问题吗</h2>
<div class="buttons">
<el-button class="button"></el-button>
<el-button class="button"></el-button>
</div>
</div>
</template>
<script></script>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 40vh;
text-align: center;
}
.heading {
font-size: 2em;
margin-bottom: 20px;
}
.sub-heading {
font-size: 1.5em;
margin-bottom: 30px;
}
.buttons {
display: flex;
gap: 20px;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 150px;
height: 60px;
border-radius: 22px;
border: none;
color: white;
font-size: 18px;
transition:
background-color 0.3s,
box-shadow 0.3s,
transform 0.1s;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
</style>

@ -0,0 +1,111 @@
<template>
<div class="center-container">
<div class="rolling-numbers">
<RollingNumber
v-for="(stopNumber, index) in stopNumbers"
:key="index"
:isScrolling="isScrolling"
:stopNumber="stopNumber"
:duration="durations[index]"
@rollCompleted="handleRollCompleted(index)"
/>
</div>
<button class="button" @click="toggleScrolling">
{{ isScrolling ? '停止点名' : '开始点名' }}
</button>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import RollingNumber from '@/components/scrollingNumber.vue'
const router = useRouter()
const isScrolling = ref(true)
const stopNumbers = ref([1, 0, 1, 1, 4, 5, 1, 4, 1]) // stopNumber
// duration 80-120
const durations = ref(
stopNumbers.value.map(() => Math.floor(Math.random() * (120 - 80 + 1)) + 80)
)
const completedRolls = ref(new Array(stopNumbers.value.length).fill(false))
const confirmed = ref(false)
const toggleScrolling = () => {
isScrolling.value = !isScrolling.value // isScrolling
if (isScrolling.value) {
completedRolls.value = new Array(stopNumbers.value.length).fill(false) // completedRolls
confirmed.value = false //
}
}
const handleRollCompleted = (index) => {
completedRolls.value[index] = true
if (
completedRolls.value.every((completed) => completed) &&
!confirmed.value
) {
setTimeout(() => {
if (
!confirmed.value &&
confirm(`抽到了: ${stopNumbers.value.join('')},确定后跳转`)
) {
confirmed.value = true
router.push('/rating')
}
}, 800)
}
}
onUnmounted(() => {
completedRolls.value = new Array(stopNumbers.value.length).fill(false) //
confirmed.value = false //
})
</script>
<style scoped>
.center-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 20px; /* 设置按钮与RollingNumber部分之间的间距 */
}
.rolling-numbers {
display: flex;
gap: 10px;
justify-content: center;
align-items: center;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 184px;
height: 85px;
border-radius: 22px;
border: none;
color: white;
font-size: 18px;
transition:
background-color 0.3s,
box-shadow 0.3s,
transform 0.1s;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
</style>

@ -0,0 +1,107 @@
<template>
<div id="base" class="center-container">
<!-- 标题部分 -->
<h1 id="u0" class="title-text">点击上传文件开始点名</h1>
<!-- 上传文件按钮 -->
<input
type="file"
id="fileInput"
accept=".xlsx, .xls"
style="display: none"
@change="handleFileUpload"
/>
<el-button id="u1" class="ax_default button" @click="selectFile"
><span class="button-text">上传文件</span>
</el-button>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import axios from 'axios'
const router = useRouter()
//
const selectFile = () => {
document.getElementById('fileInput').click()
}
//
const handleFileUpload = async (event) => {
const file = event.target.files[0]
if (!file) return
const formData = new FormData()
formData.append('file', file)
try {
const response = await axios.post(
'http://localhost:8080/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
console.log('上传成功:', response.data)
router.push('/roll-call')
} catch (error) {
console.error('上传失败:', error)
}
}
</script>
<style scoped>
.center-container {
height: 75vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button {
cursor: pointer;
background-color: #007bff;
width: 184px;
height: 85px;
border-radius: 22px;
}
.button:hover {
background-color: #1e90ff;
box-shadow: inset -4px -4px 10px rgba(0, 0, 0, 0.2);
}
.button:active {
background-color: #0056b3;
box-shadow: inset 2px 2px 8px rgba(0, 0, 0, 0.3);
transform: translateY(2px);
}
.title-text {
font-size: 48px;
color: #4a4a4a;
font-weight: bold;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.button-text {
font-size: 18px;
color: #ffffff;
}
</style>

@ -0,0 +1,25 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save