├── LICENSE ├── .yarnrc.yml ├── env.d.ts ├── public └── favicon.ico ├── .yarn └── install-state.gz ├── postcss.config.js ├── src ├── assets │ ├── TinyGallery_Logo.png │ ├── style.css │ └── main.css ├── views │ ├── HomeView.vue │ ├── RemarkView.vue │ ├── AdminView.vue │ ├── ProfileView.vue │ ├── EditProfileView.vue │ ├── RegisterView.vue │ ├── AboutView.vue │ └── AuthView.vue ├── components │ ├── Admin │ │ ├── Admin.vue │ │ ├── AdminPanel │ │ │ ├── AdminPanel.vue │ │ │ ├── DataManagement │ │ │ │ ├── PostsEditor.vue │ │ │ │ ├── UserEditor.vue │ │ │ │ ├── CommentsEditor.vue │ │ │ │ ├── PostsManagement.vue │ │ │ │ ├── CommentsAndRepliesManagement.vue │ │ │ │ └── UserManagement.vue │ │ │ ├── SideMenu │ │ │ │ └── SideMenu.vue │ │ │ └── Overview │ │ │ │ └── Overview.vue │ │ └── NavBarOfAdmin │ │ │ └── NavBarOfAdmin.vue │ ├── Profile │ │ ├── EditPost.vue │ │ ├── SettingsPanel.vue │ │ ├── Profile.vue │ │ ├── PostsList.vue │ │ └── EditProfile.vue │ ├── Common │ │ ├── AnimatedNumber.vue │ │ └── StarPopAnimation.vue │ ├── Remark │ │ ├── RemarkPanel.vue │ │ ├── ReplyPanel.vue │ │ └── Remark.vue │ ├── Notification │ │ └── NotificationBar.vue │ ├── Auth │ │ ├── LoginPanel.vue │ │ └── RegisterPanel.vue │ ├── NavBar.vue │ ├── Upload │ │ └── Uploader.vue │ ├── About │ │ └── About.vue │ └── Home │ │ └── Home.vue ├── style.css ├── main.ts ├── stores │ ├── NotificationStore.ts │ ├── UpdateRemarks.ts │ ├── UpdateReplies.ts │ ├── UpdateImages.ts │ ├── Authentication.ts │ └── TimeZone.ts ├── utilities │ └── axios-instance.ts ├── App.vue └── router │ └── index.ts ├── tsconfig.config.json ├── tsconfig.json ├── .gitignore ├── tailwind.config.js ├── index.html ├── .eslintrc.cjs ├── Dockerfile ├── nginx.conf ├── package.json ├── vite.config.ts └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeepingDogel/tinygallery-vue/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeepingDogel/tinygallery-vue/HEAD/.yarn/install-state.gz -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/TinyGallery_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeepingDogel/tinygallery-vue/HEAD/src/assets/TinyGallery_Logo.png -------------------------------------------------------------------------------- /src/assets/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base;@tailwind components;@tailwind utilities;@layer components { .btn-primary { @apply btn btn-primary; } .card-normal { @apply card bg-base-100 shadow-xl; } .input-normal { @apply input input-bordered w-full; }} -------------------------------------------------------------------------------- /src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/views/RemarkView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/views/AdminView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/ProfileView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/views/EditProfileView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "verbatimModuleSyntax": true, 7 | "types": ["node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Admin/Admin.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/RegisterView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/views/AboutView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /src/components/Profile/EditPost.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Profile/SettingsPanel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/AdminPanel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/PostsEditor.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/UserEditor.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/CommentsEditor.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/PostsManagement.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/CommentsAndRepliesManagement.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "verbatimModuleSyntax": true, 7 | "paths": { 8 | "@/*": ["./src/*"] 9 | }, 10 | "ignoreDeprecations": "5.0" 11 | }, 12 | "references": [ 13 | { 14 | "path": "./tsconfig.config.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{vue,js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [require("daisyui")], 11 | daisyui: { 12 | themes: ["light", "dark", "cupcake"], 13 | darkTheme: "dark", 14 | base: true, 15 | styled: true, 16 | utils: true, 17 | logs: true, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TinyGallery 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/SideMenu/SideMenu.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* 添加一些基础样式 */ 6 | :root { 7 | background-color: hsl(var(--b1)); 8 | color: hsl(var(--bc)); 9 | } 10 | 11 | @layer components { 12 | .custom-button { 13 | @apply bg-primary text-primary-content px-4 py-2 rounded; 14 | } 15 | .custom-card { 16 | @apply bg-base-100 shadow-xl rounded-lg p-4; 17 | } 18 | .custom-input { 19 | @apply border rounded px-4 py-2 w-full focus:outline-none focus:ring-2 focus:ring-primary; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createPinia } from "pinia"; 3 | import App from "./App.vue"; 4 | import router from "./router"; 5 | import "./assets/main.css"; 6 | import "./style.css"; 7 | import "daisyui/dist/full.css"; 8 | import { useAuthenticationStore } from "@/stores/Authentication"; 9 | 10 | const app = createApp(App); 11 | 12 | const pinia = createPinia(); 13 | app.use(pinia); 14 | app.use(router); 15 | 16 | const authStore = useAuthenticationStore(); 17 | authStore.checkAuth(); 18 | 19 | app.mount("#app"); 20 | -------------------------------------------------------------------------------- /src/views/AuthView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 19 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | These CSS styles are used to normalize default browser behaviors. 3 | 4 | The first style sets the margin and padding of all HTML elements to zero to eliminate any potential inconsistencies that 5 | may be caused by different browser defaults. 6 | 7 | The second style sets the scrolling behavior of the page to "smooth," 8 | which creates a smooth animation when navigating to different sections of the page using anchor tags. 9 | */ 10 | 11 | /* Set margin and padding to 0 for all elements */ 12 | * { 13 | padding: 0; 14 | margin: 0; 15 | } 16 | 17 | /* Set scrolling behavior of body to smooth */ 18 | body { 19 | scroll-behavior: smooth; 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | env: { 7 | node: true, 8 | }, 9 | extends: [ 10 | 'plugin:vue/vue3-essential', 11 | 'eslint:recommended', 12 | '@vue/typescript/recommended', 13 | '@vue/prettier', 14 | '@vue/prettier/@typescript-eslint', 15 | ], 16 | parserOptions: { 17 | ecmaVersion: 2020, 18 | }, 19 | rules: { 20 | 'no-unused-vars': 'warn', 21 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 22 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 23 | '@typescript-eslint/no-unused-vars': 'warn', 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official nginx runtime as a parent image 2 | FROM nginx:alpine 3 | 4 | # Set the working directory 5 | WORKDIR /usr/share/nginx/html 6 | 7 | # Remove default nginx static assets 8 | RUN rm -rf ./* 9 | 10 | # Copy the build output from the dist folder to nginx's html directory 11 | COPY dist/ . 12 | 13 | # Copy custom nginx configuration file 14 | COPY nginx.conf /etc/nginx/conf.d/default.conf 15 | 16 | # Set environment variables 17 | ENV API_BASE_URL=http://your-domain.com/api/v1 18 | 19 | # Add metadata labels 20 | LABEL maintainer="WeepingDogel " 21 | LABEL version="1.0" 22 | LABEL description="Docker image for TinyGallery-Frontend application" 23 | 24 | # Expose port 80 25 | EXPOSE 80 26 | 27 | # Start nginx server 28 | CMD ["nginx", "-g", "daemon off;"] 29 | -------------------------------------------------------------------------------- /src/components/Admin/NavBarOfAdmin/NavBarOfAdmin.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /src/components/Common/AnimatedNumber.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 38 | -------------------------------------------------------------------------------- /src/stores/NotificationStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | interface Notification { 4 | id: number 5 | message: string 6 | type: 'success' | 'error' 7 | } 8 | 9 | export const useNotificationStore = defineStore('notification', { 10 | state: () => ({ 11 | notifications: [] as Notification[], 12 | nextId: 0 13 | }), 14 | 15 | actions: { 16 | addNotification(message: string, type: 'success' | 'error' = 'success') { 17 | const notification = { 18 | id: this.nextId++, 19 | message, 20 | type 21 | } 22 | this.notifications.push(notification) 23 | 24 | // 3秒后自动移除 25 | setTimeout(() => { 26 | this.removeNotification(notification.id) 27 | }, 3000) 28 | }, 29 | 30 | removeNotification(id: number) { 31 | const index = this.notifications.findIndex(n => n.id === id) 32 | if (index > -1) { 33 | this.notifications.splice(index, 1) 34 | } 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | server_name localhost; 5 | 6 | client_max_body_size 20M; 7 | 8 | location / { 9 | root /usr/share/nginx/html; 10 | index index.html; 11 | try_files $uri $uri/ /index.html; 12 | } 13 | 14 | location /api/ { 15 | proxy_pass http://backend:8000; 16 | proxy_set_header Host $host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header X-Forwarded-Proto $scheme; 20 | } 21 | 22 | location /api/v1/ { 23 | proxy_pass http://backend:8000; 24 | proxy_set_header Host $host; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | proxy_set_header X-Forwarded-Proto $scheme; 28 | } 29 | 30 | error_page 500 502 503 504 /50x.html; 31 | location = /50x.html { 32 | root /usr/share/nginx/html; 33 | } 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinygallery-vue", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "autoprefixer": "^10.4.21", 15 | "axios": "^1.3.2", 16 | "daisyui": "^3.9.2", 17 | "pinia": "^2.0.28", 18 | "postcss": "^8.4.29", 19 | "tailwindcss": "^3.3.3", 20 | "vue": "^3.2.45", 21 | "vue-router": "^4.1.6" 22 | }, 23 | "devDependencies": { 24 | "@rushstack/eslint-patch": "^1.1.4", 25 | "@types/node": "^18.11.12", 26 | "@vitejs/plugin-vue": "^4.0.0", 27 | "@vue/eslint-config-typescript": "^11.0.0", 28 | "@vue/tsconfig": "^0.1.3", 29 | "eslint": "^8.22.0", 30 | "eslint-plugin-vue": "^9.3.0", 31 | "npm-run-all": "^4.1.5", 32 | "typescript": "~4.7.4", 33 | "vite": "^4.4.9", 34 | "vue-tsc": "^1.0.12" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/stores/UpdateRemarks.ts: -------------------------------------------------------------------------------- 1 | // store/UpdateRemarks 2 | // The store has a single property called update which represents the status of the remarks update. 3 | // It has an initial value of 1. 4 | // The store also has a single action called Update which takes a number as its argument and updates the update property by 5 | // adding that number to it. 6 | // This store can be accessed and modified in any component in the Vue application 7 | // using the useStore function provided by Pinia. 8 | 9 | import { defineStore } from "pinia"; // Import the "defineStore" function from Pinia 10 | 11 | export const UpdateRemarks = defineStore("UpdateRemarks", { 12 | // Define the "UpdateRemarks" store module 13 | state: () => ({ 14 | // Define the initial state of the store 15 | update: 1, // Set the update status to 1 by default 16 | }), 17 | actions: { 18 | // Define the actions that can be performed on the store 19 | Update(status: number) { 20 | // Define an action to update the status 21 | this.update = this.update + status; // Update the status by adding the specified value to it 22 | }, 23 | ResetUpdate() { 24 | this.update = 1; 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /src/components/Remark/RemarkPanel.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /src/components/Notification/NotificationBar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 41 | -------------------------------------------------------------------------------- /src/utilities/axios-instance.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { useAuthenticationStore } from '@/stores/Authentication' 3 | import { useNotificationStore } from '@/stores/NotificationStore' 4 | 5 | const axiosInstance = axios.create() 6 | 7 | axiosInstance.interceptors.request.use(config => { 8 | const authStore = useAuthenticationStore() 9 | if (authStore.token) { 10 | config.headers.Authorization = `Bearer ${authStore.token}` 11 | } 12 | return config 13 | }) 14 | 15 | axiosInstance.interceptors.response.use( 16 | response => { 17 | const notificationStore = useNotificationStore() 18 | if (response.data?.message) { 19 | notificationStore.addNotification(response.data.message, 'success') 20 | } 21 | return response 22 | }, 23 | error => { 24 | const notificationStore = useNotificationStore() 25 | 26 | // 根据状态码处理不同的错误消息 27 | if (error.response?.status === 401) { 28 | const message = error.response?.data?.detail || '未授权,请重新登录' 29 | notificationStore.addNotification(message, 'error') 30 | } 31 | // 验证错误(通常是 400) 32 | else if (error.response?.status === 400) { 33 | const message = error.response?.data?.detail || '请求参数错误' 34 | notificationStore.addNotification(message, 'error') 35 | } 36 | // 其他错误 37 | else { 38 | const message = error.response?.data?.detail || error.message || '操作失败' 39 | notificationStore.addNotification(message, 'error') 40 | } 41 | 42 | return Promise.reject(error) 43 | } 44 | ) 45 | 46 | export default axiosInstance 47 | -------------------------------------------------------------------------------- /src/components/Common/StarPopAnimation.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 30 | 31 | 62 | -------------------------------------------------------------------------------- /src/components/Remark/ReplyPanel.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/stores/UpdateReplies.ts: -------------------------------------------------------------------------------- 1 | // This code is using the Pinia library to define a reactive store named UpdateReplies. 2 | // A store is a simple object that contains reactive state and methods to modify that state. 3 | // Stores are commonly used in Vue.js applications to manage global state. 4 | 5 | // The defineStore function from the Pinia library is used to create the UpdateReplies store. 6 | // It takes two arguments: the name of the store (a string), and an object containing the store's state and actions. 7 | 8 | // In this case, the state object contains a single property named update, which is initialized to 1. 9 | // The actions object contains a single method named Update. 10 | // This method takes a number parameter named status, and it increments the update property of the store's state by status. 11 | 12 | // Importing the defineStore function from the Pinia library 13 | import { defineStore } from "pinia"; 14 | 15 | // Defining a store named UpdateReplies using the defineStore function 16 | export const UpdateReplies = defineStore( 17 | // The name of the store is 'UpdateReplies' 18 | "UpdateReplies", 19 | { 20 | // The state object contains reactive data for the store 21 | state: () => ({ 22 | update: 1, 23 | }), 24 | // The actions object contains methods to modify the state 25 | actions: { 26 | // The Update action takes a number parameter named status 27 | Update(status: number) { 28 | // The update property of the state is incremented by status 29 | this.update = this.update + status; 30 | }, 31 | // The ResetUpdate action resets the update property to 1 32 | ResetUpdate() { 33 | this.update = 1; 34 | }, 35 | }, 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/Overview/Overview.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /src/stores/UpdateImages.ts: -------------------------------------------------------------------------------- 1 | // stores/UpdateImages 2 | // This code defines a Pinia store named "UpdateImages" 3 | // which is used for managing image update status in a Vue.js application. 4 | // The defineStore function is imported from the Pinia library at the top of the file. 5 | // This function creates a new store instance based on a given name and configuration object. 6 | // The UpdateImages store is created using the defineStore function, with a unique name of "UpdateImage". 7 | // The configuration object passed to defineStore contains two properties: state and actions. 8 | // The state property is a function that returns an object containing the initial state of the store. 9 | // In this case, the only piece of state defined is update, which is initialized to 1. 10 | // This variable is used to keep track of the current update status of the images. 11 | // The actions property is an object that contains methods which can be called to modify the state of the store. 12 | // In this case, there is only one action defined, named Update. 13 | // This method takes a number argument named status and updates the update state variable by adding the status value to it. 14 | // This action can be used to update the image status within the store. 15 | // Overall, this code provides a simple implementation of a store for image update management in a Vue.js app. 16 | 17 | import { defineStore } from "pinia"; // Import the `defineStore` function from Pinia 18 | 19 | // Define a Pinia store named "UpdateImages" 20 | export const UpdateImages = defineStore("UpdateImage", { 21 | // Define the initial state of the store 22 | state: () => ({ 23 | update: 1, // A number representing the current update status of the images 24 | }), 25 | 26 | // Define actions that can be used to modify the state of the store 27 | actions: { 28 | // This action takes a number value as its argument and updates the `update` state variable accordingly 29 | Update(status: number) { 30 | this.update = this.update + status; 31 | }, 32 | ResetUpdate() { 33 | this.update = 1; 34 | }, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/Auth/LoginPanel.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 61 | 62 | 65 | -------------------------------------------------------------------------------- /src/components/Admin/AdminPanel/DataManagement/UserManagement.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 77 | 78 | 81 | -------------------------------------------------------------------------------- /src/components/Auth/RegisterPanel.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | // This code defines a configuration object for the Vite build tool. 2 | // The resolve option specifies an alias for the src directory. 3 | // This allows you to import files from the src directory using @ in the path instead of specifying the full path. 4 | // The server option configures a proxy server to forward requests made to certain paths 5 | // to another server running locally on http://127.0.0.1:8000. 6 | // The plugins option specifies an array of Vite plugins to use, 7 | // with the vue plugin being included in this example. 8 | 9 | // Import necessary modules 10 | import { fileURLToPath, URL } from 'node:url' 11 | import { defineConfig } from 'vite' 12 | import vue from '@vitejs/plugin-vue' 13 | 14 | // Define the configuration for Vite 15 | export default defineConfig({ 16 | plugins: [vue()], // Use the Vue plugin 17 | resolve: { 18 | alias: { 19 | '@': fileURLToPath(new URL('./src', import.meta.url)), // Create an alias for the src directory 20 | 'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' 21 | } 22 | }, 23 | server: { 24 | proxy: { 25 | // Proxy requests to these URLs to the specified target server 26 | '/user/register': { 27 | target: 'http://127.0.0.1:8001', 28 | }, 29 | '/user/token': { 30 | target: 'http://127.0.0.1:8001', 31 | }, 32 | '/posts/create': { 33 | target: 'http://127.0.0.1:8001', 34 | }, 35 | '/posts/remove':{ 36 | target: 'http://127.0.0.1:8001', 37 | }, 38 | '/posts/update': { 39 | target: 'http://127.0.0.1:8001', 40 | }, 41 | '/resources': { 42 | target: 'http://127.0.0.1:8001', 43 | }, 44 | '/userdata': { 45 | target: 'http://127.0.0.1:8001', 46 | }, 47 | '/remark/get': { 48 | target: 'http://127.0.0.1:8001' 49 | }, 50 | '/remark/create': { 51 | target: 'http://127.0.0.1:8001' 52 | }, 53 | '/likes': { 54 | target: 'http://127.0.0.1:8001' 55 | }, 56 | // Updated admin API routes 57 | '/admin/users': { 58 | target: 'http://127.0.0.1:8001', 59 | }, 60 | '/admin/posts': { 61 | target: 'http://127.0.0.1:8001', 62 | }, 63 | '/admin/comments': { 64 | target: 'http://127.0.0.1:8001', 65 | }, 66 | '/admin/user_tendency_addition': { 67 | target: 'http://127.0.0.1:8001', 68 | }, 69 | '/admin/posts_toplist': { 70 | target: 'http://127.0.0.1:8001', 71 | }, 72 | '/admin/admin_authentication': { 73 | target: 'http://127.0.0.1:8001' 74 | }, 75 | '/static': { 76 | target: 'http://127.0.0.1:8001' 77 | } 78 | }, 79 | }, 80 | }) 81 | -------------------------------------------------------------------------------- /src/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 76 | 77 | 82 | -------------------------------------------------------------------------------- /src/components/Upload/Uploader.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 93 | 94 | 97 | -------------------------------------------------------------------------------- /src/stores/Authentication.ts: -------------------------------------------------------------------------------- 1 | // stores/Authentication.ts 2 | // This code defines a Pinia store named "Authentication" 3 | // which is used for managing authentication state in a Vue.js application. 4 | // At the top of the file, the defineStore function is imported from the Pinia library. 5 | // This function is used to create a new store instance based on a given name and configuration object. 6 | // The Authentication store is created using the defineStore function, with a unique name of "Authentication". 7 | // The configuration object passed to defineStore contains two properties: state and actions. 8 | // The state property is a function that returns an object containing the initial state of the store. 9 | // In this case, the only piece of state defined is isLogged, which is initialized to false. 10 | // This variable is used to keep track of whether the user is logged in or not. 11 | // The actions property is an object that contains methods which can be called to modify the state of the store. 12 | // In this case, there is only one action defined, named setLogStatus. 13 | // This method takes a boolean argument named status and sets the isLogged state variable to the value of status. 14 | // This action can be used to update the authentication status of the user within the store. 15 | // Overall, this code provides a simple implementation of a store for authentication management in a Vue.js app. 16 | 17 | import { defineStore } from "pinia"; // Import the `defineStore` function from Pinia library 18 | import axios from "axios"; // Import the `axios` library for making HTTP requests 19 | 20 | // Define a store named "Authentication" using the `defineStore` function 21 | export const useAuthenticationStore = defineStore("authentication", { 22 | // Define the initial state of the store 23 | state: () => ({ 24 | isLogged: !!localStorage.getItem("auth_token"), // 根据localStorage中的token初始化登录状态 25 | token: localStorage.getItem("auth_token") || "", // A string to store the authentication token 26 | }), 27 | 28 | // Define actions that can be used to modify the state of the store 29 | actions: { 30 | // This action takes a boolean value as its argument and sets the `isLogged` state variable accordingly 31 | setLogStatus(status: boolean) { 32 | this.isLogged = status; 33 | }, 34 | // This action takes a username and password, sends a login request, and updates the state accordingly 35 | async login(username: string, password: string) { 36 | try { 37 | const response = await axios.post('/user/token', 38 | new URLSearchParams({ 39 | username, 40 | password 41 | }), 42 | { 43 | headers: { 44 | 'Content-Type': 'application/x-www-form-urlencoded' 45 | } 46 | } 47 | ) 48 | this.token = response.data.access_token 49 | localStorage.setItem("auth_token", response.data.access_token) 50 | this.isLogged = true 51 | } catch (error: any) { 52 | this.token = "" 53 | localStorage.removeItem("auth_token") 54 | this.isLogged = false 55 | throw error // 确保错误被传递给调用者 56 | } 57 | }, 58 | // This action logs the user out by clearing the token and updating the state 59 | logout() { 60 | this.token = ""; 61 | localStorage.removeItem("auth_token"); 62 | this.setLogStatus(false); 63 | }, 64 | // This action checks the authentication status based on the token 65 | checkAuth() { 66 | if (this.token) { 67 | this.setLogStatus(true); 68 | } else { 69 | this.setLogStatus(false); 70 | } 71 | }, 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyGallery Vue Edition FrontEnd 2 | 3 | ![](/src/assets/TinyGallery_Logo.png) 4 | 5 | Welcome to TinyGallery Vue Edition FrontEnd - an open-source project that provides a simple and free gallery service for drawing lovers. The project is divided into two parts: frontend and backend. 6 | 7 | The frontend is built using Vue.js, Vite, TypeScript, CSS3, and Yarn. It features a simple and clean user interface, fast and responsive design, and is easy to use and navigate. 8 | 9 | The backend is built using FastAPI, a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. The database used is SQLite, a lightweight disk-based database engine that doesn't require a separate server process. You can find the source code for the backend at [TinyGallery BackEnd](https://github.com/WeepingDogel/tinygallery-backend). 10 | 11 | ~~If you're interested in contributing to this project, please read our Contributing Guide for more information.~~ 12 | 13 | # Features 14 | 15 | * Simple and clean user interface 16 | * Fast and responsive design 17 | * Easy to use and navigate 18 | * Free and open-source 19 | 20 | 21 | # Technology Stack 22 | 23 | 24 | ## Frontend 25 | 26 | The technology stack used in the frontend of this project includes: 27 | 28 | ![Vue.js](https://img.shields.io/badge/vuejs-%2335495e.svg?style=for-the-badge&logo=vuedotjs&logoColor=%234FC08D)![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white)![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=for-the-badge&logo=css3&logoColor=white)![Yarn](https://img.shields.io/badge/yarn-%232C8EBB.svg?style=for-the-badge&logo=yarn&logoColor=white) 29 | 30 | 31 | * Vue.js: A popular JavaScript framework for building user interfaces. 32 | * Vite: An opinionated build tool that focuses on fast development and optimized builds. 33 | * TypeScript: A superset of JavaScript that adds optional static typing and other features to the language. 34 | * CSS3: The latest version of Cascading Style Sheets, which is used to style web pages. 35 | * Yarn: A package manager that allows for easy installation and management of dependencies. 36 | 37 | 38 | ## Backend 39 | 40 | The technology stack used in the backend of this project includes: 41 | 42 | ![FastAPI](https://img.shields.io/badge/FastAPI-005571?style=for-the-badge&logo=fastapi)![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=for-the-badge&logo=sqlite&logoColor=white)![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) 43 | 44 | * FastAPI: A modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. 45 | * SQLite: A lightweight disk-based database engine that doesn't require a separate server process. 46 | 47 | # Getting Started 48 | 49 | To get started with the TinyGallery Vue Edition FrontEnd, follow these steps: 50 | 51 | * Clone the repository: 52 | ``` 53 | git clone https://github.com/WeepingDogel/tinygallery-vue.git 54 | ``` 55 | * Enter the directory: 56 | ``` 57 | cd tinygallery-vue 58 | ``` 59 | * Install dependencies: 60 | ``` 61 | yarn install 62 | ``` 63 | * Start the development server: 64 | ``` 65 | yarn dev 66 | ``` 67 | * Open http://localhost:5173 in your browser 68 | 69 | # Contributing 70 | 71 | This project is open-source and contributions are welcome. If you want to contribute to the project, please read our Contributing Guide for more information. 72 | 73 | # License 74 | 75 | This project is licensed under the MIT License. -------------------------------------------------------------------------------- /src/stores/TimeZone.ts: -------------------------------------------------------------------------------- 1 | // 1. The code imports the necessary dependencies: 2 | // defineStore from the Pinia library, axios for making HTTP requests, 3 | // and moment-timezone for working with timezones. 4 | 5 | // 2. The Timezone store is defined using defineStore. 6 | // It has two properties in its state: 7 | // TimeZoneServer and TimeZoneBrowser, 8 | // which will store the timezone of the server and the timezone of the browser, respectively. 9 | 10 | // 3. The GetTheTimeZoneOfServer action is responsible for retrieving the timezone of the server. 11 | // It makes an HTTP GET request to the /userdata/get/timezone endpoint using axios. 12 | // Upon a successful response, 13 | // the server's timezone is extracted from the response data and stored in the TimeZoneServer property. 14 | // The timezone is also logged to the console. If there's an error, the error details are returned. 15 | 16 | // 4. The GetTheLocalTimeZone action is responsible for retrieving the timezone of the browser. 17 | // It uses the Intl.DateTimeFormat().resolvedOptions().timeZone method to obtain the browser's timezone 18 | // and stores it in the TimeZoneBrowser property. 19 | // The timezone is also logged to the console. 20 | 21 | // 5. The CaculateTheCorrectDate action takes an original date as input 22 | // and calculates the correct date based on the original date, 23 | // server's timezone, and browser's timezone. 24 | // It first creates a moment object using the original date and the server's timezone (this.TimeZoneServer). 25 | // Then, it clones the moment object and converts it to the browser's timezone (this.TimeZoneBrowser). 26 | // Finally, it formats the date as "YYYY-MM-DD HH:mm" and returns the formatted date. 27 | 28 | // Overall, this code allows you to retrieve and manage the timezones of both the server and the browser, 29 | // and perform calculations to ensure that dates are displayed correctly based on the respective timezones. 30 | 31 | import { defineStore } from "pinia"; 32 | import axios from "axios"; 33 | import moment from "moment-timezone"; 34 | 35 | export const Timezone = defineStore("TimeZone", { 36 | state: () => ({ 37 | TimeZoneServer: "", // Stores the timezone of the server 38 | TimeZoneBrowser: "", // Stores the timezone of the browser 39 | }), 40 | actions: { 41 | // Retrieves the timezone of the server 42 | GetTheTimeZoneOfServer() { 43 | axios 44 | .get("/userdata/get/timezone") 45 | .then((response) => { 46 | // console.log(response); 47 | this.TimeZoneServer = response.data; // Sets the server's timezone based on the response data 48 | console.log(this.TimeZoneServer); 49 | }) 50 | .catch((error) => { 51 | // console.log(error); 52 | return error.detail; // Returns the error details if there's an error 53 | }); 54 | }, 55 | // Retrieves the timezone of the browser 56 | GetTheLocalTimeZone() { 57 | const TimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Gets the timezone of the browser 58 | this.TimeZoneBrowser = TimeZone; // Sets the browser's timezone 59 | console.log(this.TimeZoneBrowser); 60 | }, 61 | // Manually sets the timezone of the server 62 | SetTimeZoneServer(timeZone: string) { 63 | this.TimeZoneServer = timeZone; 64 | }, 65 | // Calculates the correct date based on the original date, server's timezone, and browser's timezone 66 | CaculateTheCorrectDate(OriginalDate: String) { 67 | const dateString = OriginalDate; 68 | return moment 69 | .tz(dateString, this.TimeZoneServer) // Converts the original date to the server's timezone 70 | .clone() 71 | .tz(this.TimeZoneBrowser) // Converts the date to the browser's timezone 72 | .format("YYYY-MM-DD HH:mm"); // Formats the date as "YYYY-MM-DD HH:mm" 73 | }, 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /src/components/Profile/Profile.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 98 | 99 | 113 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | // import { createRouter, createWebHistory } from 'vue-router': 2 | // This imports the createRouter() and createWebHistory() functions from the Vue Router library. 3 | 4 | // import HomePage from '../views/HomeView.vue': 5 | // This imports the HomeView.vue file from the views folder. 6 | 7 | // const router = createRouter({ ... }): 8 | // This creates the router object with the specified configuration. 9 | // It takes an object with two properties: history and routes. 10 | 11 | // history: createWebHistory(import.meta.env.BASE_URL),: 12 | // This sets the history mode to "web history" and uses the BASE_URL environment variable to set the base URL of the app. 13 | 14 | // routes: [ ... ]: 15 | // This defines an array of route objects which map a URL path to a specific component. 16 | 17 | // { path: '/', name: 'home', component: HomePage }: 18 | // This sets the root path to the HomePage component and names it 'home'. 19 | 20 | // { path: '/login', name: 'login', component: () => import('../views/AuthView.vue') }: 21 | // This sets the /login path to the AuthView component and names it 'login'. 22 | // The component is loaded lazily using the import() function. 23 | 24 | // { path: '/about', name: 'about', component: () => import('../views/AboutView.vue') }: 25 | // This sets the /about path to the AboutView component and names it 'about'. 26 | // The component is loaded lazily using the import() function. 27 | 28 | // { path: '/profile', name: 'Profile', component: () => import('../views/ProfileView.vue') }: 29 | // This sets the /profile path to the ProfileView component and names it 'Profile'. 30 | // The component is loaded lazily using the import() function. 31 | 32 | // { path: '/remark/:post_uuid', name: 'Remark', component: () => import('../views/RemarkView.vue') }: 33 | // This sets the /remark path with a dynamic parameter named post_uuid to the RemarkView component and names it 'Remark'. 34 | // The component is loaded lazily using the import() function. 35 | 36 | // export default router: This exports the router object as the default export. 37 | 38 | // Import the necessary functions from vue-router 39 | import { createRouter, createWebHistory } from "vue-router"; 40 | // Import the different views for each page 41 | import HomePage from "../views/HomeView.vue"; 42 | 43 | // Define the router object 44 | const router = createRouter({ 45 | // Use web history API to maintain browser history 46 | history: createWebHistory(import.meta.env.BASE_URL), 47 | routes: [ 48 | { 49 | // Define the home page route 50 | path: "/", 51 | name: "home", 52 | component: HomePage, 53 | }, 54 | { 55 | // Define the login page route 56 | path: "/login", 57 | name: "login", 58 | component: () => import("../views/AuthView.vue"), 59 | }, 60 | { 61 | // Define the about page route 62 | path: "/about", 63 | name: "about", 64 | component: () => import("../views/AboutView.vue"), 65 | }, 66 | { 67 | // Define the profile page route 68 | path: "/profile", 69 | name: "Profile", 70 | component: () => import("../views/ProfileView.vue"), 71 | }, 72 | { 73 | // Define the edit profile page route 74 | path: "/profile/edit", 75 | name: "EditProfile", 76 | component: () => import("../views/EditProfileView.vue"), 77 | }, 78 | { 79 | // Define the remark page route with a dynamic parameter 80 | path: "/remark/:uuid", 81 | name: "Remark", 82 | component: () => import("../views/RemarkView.vue"), 83 | }, 84 | { 85 | path: "/admin", 86 | name: "Administration", 87 | component: () => import("../views/AdminView.vue"), 88 | }, 89 | { 90 | path: "/register", 91 | name: "register", 92 | component: () => import("../views/RegisterView.vue"), 93 | } 94 | ], 95 | }); 96 | 97 | export default router; 98 | -------------------------------------------------------------------------------- /src/components/Profile/PostsList.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 138 | 139 | 149 | -------------------------------------------------------------------------------- /src/components/Profile/EditProfile.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 126 | 127 | 130 | -------------------------------------------------------------------------------- /src/components/About/About.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 113 | 114 | 145 | -------------------------------------------------------------------------------- /src/components/Home/Home.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 264 | 265 | 337 | -------------------------------------------------------------------------------- /src/components/Remark/Remark.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 334 | 335 | 369 | --------------------------------------------------------------------------------