WuXinyu 7 سال پیش
والد
کامیت
f76c01b150

+ 2 - 1
package.json

@@ -13,7 +13,8 @@
     "register-service-worker": "^1.5.2",
     "vue": "^2.5.17",
     "vue-router": "^3.0.1",
-    "vuex": "^3.0.1"
+    "vuex": "^3.0.1",
+    "whatwg-fetch": "^3.0.0"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.2.0",

+ 6 - 0
src/api/_prefix.js

@@ -0,0 +1,6 @@
+export const API_VERSION = "/api/v2";
+
+export const AUTH_MODULE = `${API_VERSION}/authorization`;
+export const USER_MODULE = `${API_VERSION}/user`;
+export const PHOTO_MODULE = `${API_VERSION}/photo`;
+export const TAG_MODULE = `${API_VERSION}/tag`;

+ 31 - 0
src/api/authorization.js

@@ -0,0 +1,31 @@
+import request from "@/util/request";
+import { AUTH_MODULE } from "./_prefix";
+
+/**
+ * 登陆 login
+ * @param {{username,password}} 登陆信息
+ * @returns {{username,password}}
+ */
+export const login = ({ username, password }) => {
+  return request(`${AUTH_MODULE}/login`, {
+    method: "POST",
+    body: {
+      username: username || null,
+      password: password || null
+    }
+  });
+};
+
+/**
+ * 注册 register
+ * @param {{username,password}}  注册信息
+ */
+export const register = ({ username, password }) => {
+  return request(`${AUTH_MODULE}/register`, {
+    method: "POST",
+    body: {
+      username: username || null,
+      password: password || null
+    }
+  });
+};

+ 50 - 0
src/api/photo.js

@@ -0,0 +1,50 @@
+import { PHOTO_MODULE } from "@/api/_prefix";
+import request from "@/util/request";
+
+/**
+ * 获取全部图片
+ * @returns {{_headers}}
+ */
+export const fetchUserPhotos = ({ tag = -1, sort = -1 }) => {
+  return request(`${PHOTO_MODULE}?tag=${tag}&sort=${sort}`);
+};
+
+/**
+ * 新增pageview
+ * @param userId
+ * @param photoId+
+ * @returns {Object}
+ */
+export const addPageview = (userId, photoId) => {
+  return request(`${PHOTO_MODULE}/${photoId}/pageview`, {
+    method: "POST"
+  });
+};
+
+/**
+ * 获得pageview
+ * @param userId
+ * @param photoId+
+ * @returns {Object}
+ */
+export const getPageview = (userId, photoId) => {
+  return request(`${PHOTO_MODULE}/${photoId}/pageview`);
+};
+
+/**
+ *
+ * @param userId
+ * @param files
+ * @param onload
+ */
+export const postUserPhotos = (userId, files = [], onload = () => {}) => {
+  const xhr = new XMLHttpRequest();
+  const formData = new FormData();
+  xhr.onload = onload;
+  xhr.open("POST", `${PHOTO_MODULE}?userId=${userId}`, true);
+  xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+  for (let i = 0; i < files.length; i++) {
+    formData.append("photos", files[i]);
+  }
+  xhr.send(formData);
+};

+ 28 - 0
src/api/tag.js

@@ -0,0 +1,28 @@
+import request from "@/util/request";
+import { TAG_MODULE } from "@/api/_prefix";
+
+/**
+ * 新增tag
+ * @param photoId
+ * @param name
+ * @returns {Object}
+ */
+export const addTag = (photoId, name) => {
+  return request(`${TAG_MODULE}`, {
+    method: "POST",
+    body: {
+      name,
+      photoId
+    }
+  });
+};
+
+/**
+ * 获得tag
+ * @param photoId+
+ * @param name
+ * @returns {Object}
+ */
+export const getTags = ({ photoId = -1, name = -1 }) => {
+  return request(`${TAG_MODULE}?photoId=${photoId}&name=${name}`);
+};

+ 10 - 0
src/api/user.js

@@ -0,0 +1,10 @@
+import request from "@/util/request";
+import { USER_MODULE } from "./_prefix";
+
+/**
+ * 获取用户数据
+ * @returns {{avatar,username,nickname,bios}}
+ */
+export const fetchUserAuthProfile = userId => {
+  return request(`${USER_MODULE}/auth/${userId}`);
+};

+ 46 - 0
src/router/index.js

@@ -0,0 +1,46 @@
+import Vue from "vue";
+import Router from "vue-router";
+import {
+  LOGIN_ROUTER,
+  REGISTER_ROUTER,
+  HOME_ROUTER,
+  MIXER_ROUTER
+} from "@/router/name";
+
+Vue.use(Router);
+
+export default new Router({
+  routes: [
+    {
+      path: "/login",
+      component: () => import("@/layout/LoginLayout"),
+      meta: { requiresAuth: false },
+      children: [
+        {
+          name: REGISTER_ROUTER,
+          path: "/register",
+          component: () => import("@/views/Register"),
+          meta: { requiresAuth: false }
+        },
+        {
+          name: LOGIN_ROUTER,
+          path: "",
+          component: () => import("@/views/Login"),
+          meta: { requiresAuth: false }
+        }
+      ]
+    },
+    {
+      name: HOME_ROUTER,
+      path: "/",
+      component: () => import("@/views/Home"),
+      meta: { requiresAuth: true }
+    },
+    {
+      name: MIXER_ROUTER,
+      path: "/mixer",
+      component: () => import("@/views/Mixer"),
+      meta: { requiresAuth: true }
+    }
+  ]
+});

+ 4 - 0
src/router/name.js

@@ -0,0 +1,4 @@
+export const LOGIN_ROUTER = "login";
+export const REGISTER_ROUTER = "register";
+export const HOME_ROUTER = "home";
+export const MIXER_ROUTER = "mixer";

+ 62 - 0
src/store/auth.module.js

@@ -0,0 +1,62 @@
+import { login, register } from "@/api/authorization";
+import { LOGIN, LOGOUT, REGISTER } from "@/store/type/actions.type";
+import {
+  SET_AUTH,
+  SET_LOGIN_ERROR,
+  REMOVE_AUTH,
+  SET_REGISTER_ERROR,
+  SET_PROFILE
+} from "@/store/type/mutations.type";
+import { getToken, destroyToken, saveToken } from "@/util/token";
+
+const state = {
+  isAuthenticated: !!getToken(),
+  isLoginError: false,
+  isRegisterError: false
+};
+
+const actions = {
+  async [LOGIN](context, credentials) {
+    try {
+      const { token, user } = await login(credentials);
+      context.commit(SET_PROFILE, user);
+      context.commit(SET_AUTH, { token, profile: user });
+      context.commit(SET_LOGIN_ERROR, false);
+    } catch (e) {
+      context.commit(SET_LOGIN_ERROR, true);
+    }
+  },
+  [LOGOUT](context) {
+    context.commit(REMOVE_AUTH);
+  },
+  async [REGISTER](context, credentials) {
+    try {
+      await register(credentials);
+    } catch (e) {
+      context.commit(SET_REGISTER_ERROR, true);
+    }
+  }
+};
+
+const mutations = {
+  [SET_LOGIN_ERROR](state, error) {
+    state.isLoginError = error;
+  },
+  [SET_REGISTER_ERROR](state, error) {
+    state.isRegisterError = error;
+  },
+  [SET_AUTH](state, { token, profile }) {
+    state.isAuthenticated = true;
+    saveToken(token, profile);
+  },
+  [REMOVE_AUTH](state) {
+    state.isAuthenticated = false;
+    destroyToken();
+  }
+};
+
+export default {
+  state,
+  actions,
+  mutations
+};

+ 14 - 0
src/store/index.js

@@ -0,0 +1,14 @@
+import Vue from "vue";
+import Vuex from "vuex";
+
+import auth from "./auth.module";
+import user from "./user.module";
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  modules: {
+    user,
+    auth
+  }
+});

+ 7 - 0
src/store/type/actions.type.js

@@ -0,0 +1,7 @@
+export const LOGIN = "login";
+export const REGISTER = "register";
+export const FETCH_PROFILE = "fetchProfile";
+export const FETCH_PHOTOS = "fetchPhotos";
+export const POST_PHOTOS = "postPhotos";
+export const LOGOUT = "logout";
+export const ORDER_BY = "orderBy";

+ 8 - 0
src/store/type/mutations.type.js

@@ -0,0 +1,8 @@
+export const SET_LOGIN_ERROR = "setLoginError";
+export const SET_REGISTER_ERROR = "setRegisterError";
+export const SET_AUTH = "setAuthorization";
+export const SET_PROFILE = "setProfile";
+export const REMOVE_AUTH = "removeAuthorization";
+export const SET_PHOTOS = "setPhotos";
+export const SET_ORDER = "setOrder";
+export const SET_LAST_SEARCH = "setLastSearch";

+ 83 - 0
src/store/user.module.js

@@ -0,0 +1,83 @@
+import { fetchUserAuthProfile } from "@/api/user";
+import {
+  FETCH_PROFILE,
+  FETCH_PHOTOS,
+  POST_PHOTOS,
+  ORDER_BY
+} from "@/store/type/actions.type";
+import {
+  SET_PROFILE,
+  SET_PHOTOS,
+  SET_ORDER,
+  SET_LAST_SEARCH
+} from "@/store/type/mutations.type";
+import { fetchUserPhotos, postUserPhotos } from "@/api/photo";
+
+const LATEST = "LATEST";
+
+const state = {
+  profile: {
+    id: undefined,
+    username: undefined,
+    nickname: undefined
+  },
+  photos: [],
+  orderType: LATEST,
+  searchBy: -1,
+  lastSearch: -1
+};
+
+const actions = {
+  async [FETCH_PROFILE](context) {
+    if (context.state.profile) {
+      const newProfile = await fetchUserAuthProfile(context.state.profile.id);
+      context.commit(SET_PROFILE, newProfile);
+    }
+  },
+  async [FETCH_PHOTOS](context, tag = -1) {
+    let searchTag = tag;
+    if (tag === "") {
+      searchTag = -1;
+    }
+    const photos = await fetchUserPhotos({
+      tag: searchTag,
+      sort: context.state.orderType
+    });
+    context.commit(SET_PHOTOS, photos);
+    context.commit(SET_LAST_SEARCH, tag);
+  },
+  async [POST_PHOTOS](context, { files, onload }) {
+    if (context.state.profile) {
+      postUserPhotos(context.state.profile.id, files, onload);
+    }
+  },
+  async [ORDER_BY](context, type) {
+    context.commit(SET_ORDER, type);
+    const photos = await fetchUserPhotos({
+      tag: context.state.lastSearch,
+      sort: type
+    });
+    context.commit(SET_PHOTOS, photos);
+  }
+};
+
+const mutations = {
+  [SET_PROFILE](state, profile) {
+    state.profile = profile;
+  },
+  [SET_PHOTOS](state, photos = []) {
+    state.photos = photos;
+  },
+  [SET_ORDER](state, type) {
+    state.orderType = type;
+  },
+  [SET_LAST_SEARCH](state, search) {
+    state.lastSearch = search;
+  }
+};
+
+export default {
+  state,
+  actions,
+  mutations
+};

+ 84 - 0
src/util/request.js

@@ -0,0 +1,84 @@
+// ant-design-pro request.js file with MIT license
+import "whatwg-fetch";
+import router from "@/router";
+import { LOGIN_ROUTER } from "@/router/name";
+
+const codeMessage = {
+  200: "服务器成功返回请求的数据。",
+  201: "新建或修改数据成功。",
+  202: "一个请求已经进入后台排队(异步任务)。",
+  204: "删除数据成功。",
+  400: "发出的请求有错误,服务器没有进行新建或修改数据的操作。",
+  401: "用户没有权限(令牌、用户名、密码错误)。",
+  403: "用户得到授权,但是访问是被禁止的。",
+  404: "发出的请求针对的是不存在的记录,服务器没有进行操作。",
+  406: "请求的格式不可得。",
+  410: "请求的资源被永久删除,且不会再得到的。",
+  422: "当创建一个对象时,发生一个验证错误。",
+  500: "服务器发生错误,请检查服务器。",
+  502: "网关错误。",
+  503: "服务不可用,服务器暂时过载或维护。",
+  504: "网关超时。"
+};
+
+function checkStatus(response) {
+  if (response.status >= 200 && response.status < 300) {
+    return response;
+  }
+  const errortext = codeMessage[response.status] || response.message;
+  const error = new Error(errortext);
+  error.name = response.status;
+  error.response = response;
+  throw error;
+}
+
+/**
+ * Requests a URL, returning a promise.
+ *
+ * @param  {string} url       The URL we want to request
+ * @param  {object} [options] The options we want to pass to "fetch"
+ * @return {object}           An object containing either "data" or "err"
+ */
+export default async function request(url, options) {
+  const defaultOptions = {
+    credentials: "include"
+  };
+  const newOptions = { ...defaultOptions, ...options };
+  if (
+    newOptions.method === "POST" ||
+    newOptions.method === "PUT" ||
+    newOptions.method === "DELETE"
+  ) {
+    if (!(newOptions.body instanceof FormData)) {
+      newOptions.headers = {
+        Accept: "application/json",
+        "Content-Type": "application/json; charset=utf-8",
+        ...newOptions.headers
+      };
+      newOptions.body = JSON.stringify(newOptions.body);
+    } else {
+      // newOptions.body is FormData
+      newOptions.headers = {
+        Accept: "application/json",
+        ...newOptions.headers
+      };
+    }
+  }
+
+  const response = await fetch(url, newOptions);
+  try {
+    checkStatus(response);
+  } catch (e) {
+    if (e.name !== 418) {
+      router.push({ name: LOGIN_ROUTER });
+    } else {
+      throw new Error(e);
+    }
+  }
+
+  if (newOptions.method === "DELETE" || response.status === 204) {
+    return response.text();
+  }
+
+  return response.json();
+}

+ 31 - 0
src/util/token.js

@@ -0,0 +1,31 @@
+const ID_TOKEN_KEY = "id_token";
+const USER_PROFILE = "user_profile";
+
+export const getToken = () => {
+  const token = window.localStorage.getItem(ID_TOKEN_KEY);
+  if (token === "undefined") {
+    return false;
+  } else {
+    return window.localStorage.getItem(ID_TOKEN_KEY);
+  }
+};
+
+export const saveToken = (token, profile) => {
+  token && window.localStorage.setItem(ID_TOKEN_KEY, token);
+  profile && window.localStorage.setItem(USER_PROFILE, JSON.stringify(profile));
+};
+
+export const destroyToken = () => {
+  window.localStorage.removeItem(ID_TOKEN_KEY);
+};
+
+export const getUserProfile = () => {
+  const profileText = window.localStorage.getItem(USER_PROFILE);
+  let profile = {};
+  try {
+    profile = JSON.parse(profileText);
+  } catch (e) {
+    profile = {};
+  }
+  return profile;
+};

+ 8 - 0
webpack.config.js

@@ -0,0 +1,8 @@
+// only to make webstorm know alias
+module.exports = {
+  resolve: {
+    alias: {
+      "@": require("path").resolve(__dirname, "src")
+    }
+  }
+};

+ 5 - 0
yarn.lock

@@ -10452,6 +10452,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
   dependencies:
     iconv-lite "0.4.24"
 
+whatwg-fetch@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
+  integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
+
 whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0:
   version "2.3.0"
   resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"