# vue2-elm Admin & Architecture Guide

This guide is the deep reference for developers, architects, and anyone maintaining,
extending, or deploying **vue2-elm**. Where the User Guide covers *using* the app, this guide
covers *how it is built*: its architecture, its configuration and build pipeline, its data and
state layers, its security posture, and the technical debt a maintainer should be aware of. It
is written to be complete enough that an engineer — or an AI — could reconstruct the project
from this document alone.

vue2-elm is a **client-side single-page application**. It has no server of its own; it is a
Vue 2 front end that consumes a separate REST backend ([`node-elm`](https://github.com/bailicangdu/node-elm)).
Keep that boundary in mind throughout: nearly every "operational" concern here is about
building, hosting, and configuring a static bundle, not about running a service.

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture](#2-architecture)
3. [Requirements & Toolchain](#3-requirements--toolchain)
4. [Configuration Reference](#4-configuration-reference)
5. [The Data Layer (Service & Fetch)](#5-the-data-layer-service--fetch)
6. [State Management (Vuex Store)](#6-state-management-vuex-store)
7. [Routing](#7-routing)
8. [Shared Components & Utilities](#8-shared-components--utilities)
9. [Build & Deployment](#9-build--deployment)
10. [Security Analysis](#10-security-analysis)
11. [Code Quality & Patterns](#11-code-quality--patterns)
12. [Operations & Troubleshooting](#12-operations--troubleshooting)
13. [Potential Improvements](#13-potential-improvements)
14. [Quick Reference](#14-quick-reference)

---

## 1. Executive Summary

| Attribute | Value |
|---|---|
| **Type** | Mobile-first SPA (front end only) |
| **Domain** | Food-delivery storefront (an Ele.me / 饿了么 clone) |
| **Framework** | Vue 2 + Vuex 2 + Vue Router 2 |
| **Build** | webpack 1, Babel (ES6/7 → ES5), Sass |
| **Routing** | Hash mode (`#/path`) |
| **Backend** | External: [`node-elm`](https://github.com/bailicangdu/node-elm) (not in this repo) |
| **Output** | Static assets in `elm/` |
| **Scope** | ~45 screens, deeply nested routes (up to 4 levels) |
| **License** | GPL |
| **Purpose** | A real-world learning project demonstrating a complex, money-handling SPA |

**What makes it architecturally interesting:** unlike most Vue learning demos (single-page
players, to-do lists), vue2-elm implements a *complete* commerce flow with the hard parts —
a persistent multi-restaurant cart, a multi-step checkout, address management, and an
authentication flow — and it leans on Vuex precisely where local component state would break
down. It is a textbook example of "when do you actually need a store."

**What it intentionally omits:** real payment (the flow stops at the pay step by design) and
any of its own backend. Treat both as deliberate scope boundaries, not gaps.

---

## 2. Architecture

### 2.1 High-level topology

```text
                        ┌──────────────────────────────────────────┐
   Browser (mobile)     │              vue2-elm SPA                 │
   ┌───────────────┐    │                                          │
   │  index.html   │───▶│  main.js  →  new Vue({ router, store })   │
   └───────────────┘    │       │                                  │
                        │       ├─ Vue Router (hash)  ── pages/*    │
                        │       ├─ Vuex Store ──────── cart, user   │
                        │       └─ App.vue (router-view + keepAlive)│
                        │                                          │
                        │  service/getData.js                      │
                        │       │  (one function per endpoint)     │
                        │       ▼                                  │
                        │  config/fetch.js  (fetch + XHR fallback) │
                        └───────────────┼──────────────────────────┘
                                        │  HTTP (cookies: credentials:include)
                                        ▼
                        ┌──────────────────────────────────────────┐
                        │   node-elm backend  (separate project)   │
                        │   cangdu.org (prod)  |  localhost (local) │
                        └──────────────────────────────────────────┘
```

### 2.2 Application bootstrap

The entry sequence is small and explicit:

- **`index.html`** mounts a single `#app` element.
- **`src/main.js`** is the program entry. It:
  - Registers Vue Router and constructs the router from `src/router/router.js`.
  - Reads `routerMode` from `src/config/env.js` (`hash`).
  - Imports `src/config/rem.js` for the mobile rem layout (side-effect import).
  - Defines a `scrollBehavior` that preserves scroll position for `keep-alive` routes.
  - Instantiates the root `new Vue({ router, store }).$mount('#app')`.
- **`src/App.vue`** is the shell. It renders **two** `<router-view>`s wrapped in transitions —
  one inside `<keep-alive>` (for routes whose `meta.keepAlive` is true, e.g. Msite) and one
  outside it — plus the global `<svg-icon>` sprite component.

> [!NOTE]
> The dual `router-view` pattern is how this app gets selective view caching: only routes that
> opt in via `meta: { keepAlive: true }` are kept alive (preserving scroll and state when you
> navigate back), while everything else re-renders fresh.

### 2.3 Layered design

The `src/` tree separates concerns cleanly into layers:

| Layer | Directory | Responsibility |
|---|---|---|
| **Entry** | `src/` (`main.js`, `App.vue`) | Bootstrap, root shell |
| **Config** | `src/config/` | Environment, fetch wrapper, utilities, rem layout |
| **Routing** | `src/router/` | Route table (lazy-loaded pages) |
| **Pages** | `src/page/` | One folder per screen; nested children for sub-screens |
| **Components** | `src/components/` | Reusable UI: `common/`, `header/`, `footer/` |
| **Service** | `src/service/` | API surface — one exported function per endpoint |
| **Store** | `src/store/` | Vuex state, mutations, actions, getters, mutation-types |
| **Styles** | `src/style/` | Shared SCSS (`common.scss`, `mixin.scss`) |

This is a conventional and well-factored Vue 2 layout: **pages compose components, call the
service layer for data, and read/write shared state through the store.**

### 2.4 Data flow for a typical screen

Using **Msite** (`src/page/msite/msite.vue`) as the canonical example:

1. `beforeMount` resolves a `geohash` — from the route query if present, otherwise via
   `cityGuess()` (IP-based) — and commits it to the store (`SAVE_GEOHASH`).
2. It calls `msiteAddress(geohash)` to resolve the address, sets the header title, and commits
   `RECORD_ADDRESS` (latitude/longitude) to the store.
3. `mounted` calls `msiteFoodTypes(geohash)`, chunks the categories into pages of 8, and
   initializes a Swiper carousel.
4. The embedded `<shop-list>` component reads `latitude`/`longitude` from the store
   (`mapState`) and calls `shopList(...)` itself, paginating via infinite scroll.

This illustrates the app's consistent pattern: **lifecycle hooks fetch via the service layer,
shared coordinates flow through Vuex, and child components subscribe to that state.**

---

## 3. Requirements & Toolchain

### 3.1 Runtime/build requirements

| Tool | Declared / recommended | Source |
|---|---|---|
| Node.js | `engines.node >= 4.0.0`; README recommends `>= 6.0.0` | `package.json`, README |
| npm | `engines.npm >= 3.0.0` | `package.json` |
| Yarn | Optional alternative (lockfile committed) | README, `yarn.lock` |

The author's documented development environment was macOS 10.12.3, Chrome 56, Node 6.10.0.

### 3.2 Runtime dependencies

| Package | Version | Role |
|---|---|---|
| `vue` | `^2.1.0` | Core framework |
| `vue-router` | `^2.1.1` | SPA routing |
| `vuex` | `^2.0.0` | State management |
| `better-scroll` | `1.15.2` | Smooth scrolling regions |
| `fastclick` | `^1.0.6` | Removes mobile tap delay |
| `iscroll` | `^5.2.0` | Scrolling fallback |
| `showdown` | `^1.6.4` | Markdown → HTML (used for static text content) |

### 3.3 Notable build dependencies

The `devDependencies` define a classic webpack-1 + Babel-6 pipeline: `webpack@^1.13.2`,
`vue-loader@^10`, `vue-template-compiler`, `babel-preset-es2015` + `babel-preset-stage-2`,
`node-sass` + `sass-loader`, `less` + `less-loader`, `autoprefixer`, `extract-text-webpack-plugin`,
`html-webpack-plugin`, `http-proxy-middleware` (dev API proxy), `webpack-dev-middleware` /
`webpack-hot-middleware` (HMR), and `cross-env` (cross-platform `NODE_ENV`).

> [!WARNING]
> This is an aged toolchain. `node-sass` builds native bindings against the active Node version
> and is the most common install failure on modern Node. webpack 1 and Babel 6 are both several
> major versions behind current. See [Potential Improvements](#13-potential-improvements).

---

## 4. Configuration Reference

vue2-elm's configuration is concentrated in two small files plus the npm scripts. There are no
environment files (`.env`) and no runtime configuration service — everything is decided at
build time.

### 4.1 `src/config/env.js` — environment switching

This file exports the host configuration, selected by `process.env.NODE_ENV`:

| Export | Development | Production | Notes |
|---|---|---|---|
| `baseUrl` | `''` (empty — requests are dev-proxied) | `//elm.cangdu.org` | Prefixed onto every API path in `fetch.js`. |
| `imgBaseUrl` | `/img/` | `//elm.cangdu.org/img/` | Base for image URLs. |
| `routerMode` | `hash` | `hash` | Constant; consumed by `main.js`. |

```javascript
let baseUrl = '';
let routerMode = 'hash';
let imgBaseUrl = '';

if (process.env.NODE_ENV == 'development') {
    imgBaseUrl = '/img/';
} else if (process.env.NODE_ENV == 'production') {
    baseUrl = '//elm.cangdu.org';
    imgBaseUrl = '//elm.cangdu.org/img/';
}

export { baseUrl, routerMode, imgBaseUrl }
```

> [!CAUTION]
> **Negative knowledge — `localapi` and `proapi` do not exist.** Several modules
> (`src/store/mutations.js`, `src/components/common/mixin.js`, `src/page/login/login.vue`) write
> `import { localapi, proapi } from 'src/config/env'`. `env.js` exports **only** `baseUrl`,
> `routerMode`, and `imgBaseUrl`, so `localapi` and `proapi` resolve to `undefined`. They are
> dead leftovers from an earlier configuration scheme and are never used meaningfully. Do not
> document them as configuration knobs; if cleaning up, remove the imports.

### 4.2 `config/index.js` — dev server & build paths

This is the webpack-template config, split into `build` and `dev` sections.

**`build` block (production):**

| Key | Value | Meaning |
|---|---|---|
| `env.NODE_ENV` | `"production"` | Build-time environment. |
| `index` | `../elm/index.html` | Output HTML path. |
| `assetsRoot` | `../elm` | Output root. |
| `assetsSubDirectory` | `static` | Asset subfolder. |
| `assetsPublicPath` | `/elm/` | Public base path for assets. |
| `productionSourceMap` | `true` | Emit source maps. |
| `productionGzip` | `false` | Gzip is off (host handles it). |

**`dev` block (development):**

| Key | Value | Meaning |
|---|---|---|
| `env.NODE_ENV` | `"development"` | Dev environment. |
| `port` | `8000` | Dev server port. |
| `assetsPublicPath` | `/` | Public base in dev. |
| `proxypath` | `http://cangdu.org:8001` | Upstream API target for the dev proxy. |
| `context` | see below | Path prefixes proxied to `proxypath`. |
| `cssSourceMap` | `false` | CSS source maps off. |

The proxied `context` prefixes are exactly the API namespaces used by the service layer:

```text
/shopping  /ugc  /v1  /v2  /v3  /v4  /bos  /member  /promotion  /eus  /payapi  /img
```

> [!NOTE]
> This proxy is why `baseUrl` is empty in development: the dev server forwards those prefixed
> requests to `proxypath`, avoiding cross-origin issues. In a production build, `baseUrl`
> (`//elm.cangdu.org`) is prepended instead and the proxy is not involved.

### 4.3 npm scripts and `NODE_ENV`

```json
"scripts": {
  "dev":   "cross-env NODE_ENV=online node build/dev-server.js",
  "local": "cross-env NODE_ENV=local node build/dev-server.js",
  "build": "node build/build.js"
}
```

`dev` and `local` both launch the same dev server; the `NODE_ENV` value (`online` vs `local`)
is what selects which backend the dev tooling targets. `build` runs the production build into
`elm/`.

> [!NOTE]
> The `build/` directory (`dev-server.js`, `build.js`, and webpack configs) is the standard
> `vuejs-templates/webpack` scaffolding referenced by these scripts and by `config/index.js`.
> It orchestrates the dev server, the API proxy, and the production build.

---

## 5. The Data Layer (Service & Fetch)

### 5.1 `src/config/fetch.js` — the HTTP client

All network traffic funnels through a single default-exported async function. Its contract:

```javascript
fetch(url = '', data = {}, type = 'GET', method = 'fetch')
```

Behavior:

- Prepends `baseUrl` (from `env.js`) to `url`.
- For `GET`, serializes `data` into a `key=value&…` query string.
- If `window.fetch` is available and `method == 'fetch'`, it issues a `fetch()` with:
  - `credentials: 'include'` (sends cookies — the session mechanism),
  - `headers: Accept/Content-Type: application/json`,
  - `mode: "cors"`, `cache: "force-cache"`,
  - JSON-stringified body for `POST`.
- Otherwise it falls back to a manual `XMLHttpRequest` (with `ActiveXObject` for very old IE),
  using `application/x-www-form-urlencoded`.
- Returns the parsed JSON; throws `new Error(error)` on failure.

> [!IMPORTANT]
> `credentials: 'include'` means the app relies on **cookies** for authentication state on the
> backend side, while also persisting `user_id` in `localStorage` on the client. The two work
> together; see [Security Analysis](#10-security-analysis).

> [!WARNING]
> `cache: "force-cache"` tells the browser to prefer a cached response when one exists. For a
> data API this can serve stale results; it is a questionable default worth revisiting.

### 5.2 `src/service/getData.js` — the API surface

This file is the **single, centralized catalog of every backend call** — one exported function
per endpoint, grouped by domain. This is an exemplary pattern: pages never construct URLs
themselves; they import named functions. The complete inventory:

| Function | Method | Endpoint |
|---|---|---|
| `cityGuess` | GET | `/v1/cities?type=guess` |
| `hotcity` | GET | `/v1/cities?type=hot` |
| `groupcity` | GET | `/v1/cities?type=group` |
| `currentcity` | GET | `/v1/cities/:id` |
| `searchplace` | GET | `/v1/pois?type=search` |
| `msiteAddress` | GET | `/v2/pois/:geohash` |
| `msiteFoodTypes` | GET | `/v2/index_entry` |
| `shopList` | GET | `/shopping/restaurants` |
| `searchRestaurant` | GET | `/v4/restaurants` |
| `foodCategory` | GET | `/shopping/v2/restaurant/category` |
| `foodDelivery` | GET | `/shopping/v1/restaurants/delivery_modes` |
| `foodActivity` | GET | `/shopping/v1/restaurants/activity_attributes` |
| `shopDetails` | GET | `/shopping/restaurant/:id` |
| `foodMenu` | GET | `/shopping/v2/menu` |
| `getRatingList` | GET | `/ugc/v2/restaurants/:id/ratings` |
| `ratingScores` | GET | `/ugc/v2/restaurants/:id/ratings/scores` |
| `ratingTags` | GET | `/ugc/v2/restaurants/:id/ratings/tags` |
| `mobileCode` | POST | `/v4/mobile/verify_code/send` |
| `getcaptchas` | POST | `/v1/captchas` |
| `checkExsis` | GET | `/v1/users/exists` |
| `sendMobile` | POST | `/v1/mobile/verify_code/send` |
| `checkout` | POST | `/v1/carts/checkout` |
| `getRemark` | GET | `/v1/carts/:id/remarks` |
| `getAddress` | GET | `/v1/carts/:id/addresses` |
| `searchNearby` | GET | `/v1/pois?type=nearby` |
| `postAddAddress` | POST | `/v1/users/:id/addresses` |
| `placeOrders` | POST | `/v1/users/:id/carts/:cartId/orders` |
| `rePostVerify` | POST | `/v1/carts/:id/verify_code` |
| `validateOrders` | POST | `/v1/users/:id/carts/:cartId/orders` |
| `payRequest` | GET | `/payapi/payment/queryOrder` |
| `getService` | GET | `/v3/profile/explain` |
| `vipCart` | POST | `/member/v1/users/:id/delivery_card/physical_card/bind` |
| `getHongbaoNum` | GET | `/promotion/v2/users/:id/hongbaos` |
| `getExpired` | GET | `/promotion/v2/users/:id/expired_hongbaos` |
| `exChangeHongbao` | POST | `/v1/users/:id/hongbao/exchange` |
| `getUser` | GET | `/v1/user` |
| `sendLogin` | POST | `/v1/login/app_mobile` |
| `getOrderList` | GET | `/bos/v2/users/:id/orders` |
| `getOrderDetail` | GET | `/bos/v1/users/:id/orders/:orderId/snapshot` |
| `getAddressList` | GET | `/v1/users/:id/addresses` |
| `getSearchAddress` | GET | `v1/pois?type=nearby` |
| `deleteAddress` | DELETE | `/v1/users/:id/addresses/:addressId` |
| `accountLogin` | POST | `/v2/login` |
| `signout` | GET | `/v2/signout` |
| `changePassword` | POST | `/v2/changepassword` |

> [!NOTE]
> The API namespaces (`/v1`–`/v4`, `/shopping`, `/ugc`, `/bos`, `/member`, `/promotion`,
> `/payapi`) mirror Ele.me's real micro-service decomposition and are exactly the `context`
> prefixes proxied in `config/index.js`. Adding a new endpoint means adding a function here and
> (if it introduces a new prefix) registering that prefix in the dev proxy `context` list.

---

## 6. State Management (Vuex Store)

The store (`src/store/`) is split into the canonical Vuex files: `index.js` (state + assembly),
`mutations.js`, `action.js`, `getters.js`, and `mutation-types.js` (string constants).

### 6.1 State shape (`src/store/index.js`)

| State key | Initial | Purpose |
|---|---|---|
| `latitude`, `longitude` | `''` | Current location coordinates. |
| `cartList` | `{}` | The cart, keyed `shopid → category_id → item_id → food_id`. |
| `shopDetail` | `null` | Current restaurant detail. |
| `userInfo` | `null` | Logged-in user object. |
| `shopid` | `null` | Active restaurant id. |
| `remarkText`, `inputText` | `null` / `''` | Order remark (selected + free text). |
| `invoice` | `false` | Whether an invoice was requested. |
| `newAddress`, `searchAddress`, `choosedAddress`, `addressIndex` | `[]` / `null` | Address selection in checkout. |
| `geohash` | `'31.22299,121.36025'` | Location geohash (defaults to Shanghai coords). |
| `needValidation` | `null` | Whether the order needs extra verification. |
| `cartId`, `sig` | `null` | Cart identifier + signature returned at checkout. |
| `orderParam`, `orderMessage`, `orderDetail` | `null` | Order placement parameters and results. |
| `login` | `true` | Login flag. |
| `imgPath` | `null` | Avatar image path. |
| `removeAddress`, `addAddress` | `[]` / `''` | Profile address-book editing. |
| `question` | `null` | Selected service-center question. |
| `cartPrice` | `null` | VIP card price. |

### 6.2 Mutations (`src/store/mutations.js`)

Mutations are declared with computed-property names bound to the constants in
`mutation-types.js`. The cart mutations are the most substantial:

- **`ADD_CART`** builds a nested object path (`cartList[shopid][category_id][item_id][food_id]`),
  incrementing `num` if the dish exists or creating it (with `name`, `price`, `specs`,
  `packing_fee`, `sku_id`, `stock`). It reassigns `cartList` with a spread to trigger reactivity
  and persists to `localStorage` via `setStore('buyCart', …)`.
- **`REDUCE_CART`** decrements, nulling the entry at zero, and re-persists.
- **`INIT_BUYCART`** rehydrates `cartList` from `localStorage` on load.
- **`CLEAR_CART`** empties one restaurant's cart.

Other mutation groups handle user info (`RECORD_USERINFO` persists `user_id`; `GET_USERINFO`,
`RETSET_NAME`, `OUT_LOGIN`, `SAVE_AVANDER`), checkout (`SAVE_GEOHASH`, `CONFIRM_REMARK`,
`CONFIRM_INVOICE`, `CHOOSE_ADDRESS`, `CONFIRM_ADDRESS`, `NEED_VALIDATION`, `SAVE_CART_ID_SIG`,
`SAVE_ORDER_PARAM`, `CHANGE_ORDER_PARAM`, `ORDER_SUCCESS`, `SAVE_ORDER`, `SAVE_SHOPID`),
addresses (`SAVE_ADDRESS`, `SAVE_ADDDETAIL`, `ADD_ADDRESS`), and misc (`SAVE_QUESTION`,
`BUY_CART`).

> [!TIP]
> The cart's reliance on `{...spread}` reassignment is a deliberate Vue 2 reactivity workaround:
> Vue 2 cannot detect new properties added to an object, so the code replaces the object
> reference to force re-render. In Vue 3 (Proxy-based reactivity) this would be unnecessary.

### 6.3 Actions (`src/store/action.js`)

Only two async actions exist — both bridge the service layer into the store:

- **`getUserInfo`** calls `getUser()` and commits `GET_USERINFO`.
- **`saveAddress`** loads the user's address list (`getAddressList`) and commits `SAVE_ADDRESS`,
  short-circuiting if `removeAddress` is already populated.

Most data fetching happens **directly in components**, not in actions — a pragmatic choice for a
project of this size.

### 6.4 Getters (`src/store/getters.js`)

The getters file is **empty** (`export default {}`). All derived values are computed locally in
components (e.g. `buyCart.vue`'s `foodNum`). This is worth noting for anyone expecting a getters
layer — there isn't one.

---

## 7. Routing

`src/router/router.js` defines the entire route tree. Two conventions dominate:

### 7.1 Lazy loading via `require.ensure`

Every page is wrapped in a webpack-1 code-split chunk:

```javascript
const msite = r => require.ensure([], () => r(require('../page/msite/msite')), 'msite')
```

Each page becomes a named chunk (`'msite'`, `'shop'`, …), so the browser only downloads a
screen's code when it is first visited. This keeps the initial bundle small despite ~45 screens.

### 7.2 Deeply nested routes

Routes mirror the UI's drill-down structure, nesting up to four levels. For example, the
checkout address flow:

```text
/confirmOrder
  └─ chooseAddress
       └─ addAddress
            └─ searchAddress
```

and the profile address editor:

```text
/profile → info → address → add → addDetail
```

All pages are children of a single top-level route whose component is `App`. The Msite route
sets `meta: { keepAlive: true }` to participate in the `<keep-alive>` view caching described in
[§2.2](#22-application-bootstrap).

> [!NOTE]
> Because routing is **hash mode**, no server-side rewrite configuration is needed for deep
> links — the path after `#` is handled entirely client-side. This simplifies hosting (any
> static server works) at the cost of `#` in URLs.

---

## 8. Shared Components & Utilities

### 8.1 Common components (`src/components/common/`)

| Component | Responsibility |
|---|---|
| `shoplist.vue` | The restaurant list used by Msite and Shop. Owns infinite scroll (the `v-load-more` directive), back-to-top, loading skeletons, and per-item rendering. Reads `latitude`/`longitude` from the store. |
| `buyCart.vue` | The add/remove cart control on each dish. Computes per-dish quantity from `cartList`, dispatches `ADD_CART`/`REDUCE_CART`, and emits the "drop into cart" animation event. Handles multi-spec dishes. |
| `ratingStar.vue` | Renders a 5-star rating using overlaid SVG stars and a width clip. |
| `loading.vue` | The sprite-sheet loading animation. |
| `alertTip.vue` | A modal alert with a single confirm button; emits `closeTip`. |
| `computeTime.vue` | A payment countdown timer. Its "go to pay" handler shows `暂不开放支付接口` — the explicit hook where payment is stubbed out. |
| `svg.vue` | A hidden inline SVG sprite (`<symbol>` defs) referenced everywhere via `<use xlink:href="#id">`. Defines icons: `eleme`, `user`, `shop`, `location`, `star`, `backtop`, cart icons, payment-brand icons (`weixin`, `zhifubao`), and more. |
| `mixin.js` | Two reusable mixins (below). |

### 8.2 `mixin.js` — shared behavior

- **`loadMore`** — registers a `load-more` directive implementing touch-driven infinite scroll
  (listens to `touchstart/touchmove/touchend`, computes scroll position with `requestAnimationFrame`,
  and invokes the bound callback at the bottom).
- **`getImgPath`** — converts a backend image hash into a CDN URL on
  `https://fuss10.elemecdn.com` (splitting the hash into path segments and appending `.jpeg`
  or `.png`); falls back to `//elm.cangdu.org/img/default.jpg`.

### 8.3 `src/config/mUtils.js` — utilities

| Export | Purpose |
|---|---|
| `setStore` / `getStore` / `removeStore` | `localStorage` helpers (JSON-encode objects). |
| `getStyle` | Cross-browser computed-style reader. |
| `loadMore` | Standalone infinite-scroll helper (parallel to the directive). |
| `showBack` | Scroll listener that toggles the back-to-top button past 500 px. |
| `animate` | A hand-rolled animation engine supporting `ease-out`, `linear`, and `ease-in` modes, rem-aware unit handling, opacity/`scrollTop` special-casing, and chained callbacks. |

### 8.4 `src/config/rem.js` — the mobile layout primitive

A self-invoking function that sets the document's root font size proportionally to viewport
width, recalculating on `resize`/`orientationchange`:

```javascript
docEl.style.fontSize = 20 * (clientWidth / 320) + 'px';
```

This makes `1rem = 20px` at a 320 px-wide screen and scales linearly with device width — the
mechanism behind every `rem` dimension in the SCSS. **It is also why a desktop viewport renders
the UI oversized.**

### 8.5 Header & footer

- `src/components/header/head.vue` — the shared top bar (title, back button, search slot,
  context-specific slots used by Home/Msite/Login).
- `src/components/footer/footGuide.vue` — the bottom tab navigation.

---

## 9. Build & Deployment

### 9.1 Building

```bash
npm run build
```

Per `config/index.js`, this writes:

- `elm/index.html` (HTML entry),
- `elm/static/…` (JS/CSS/img assets, with source maps),
- using a public asset path of `/elm/`.

The result is a self-contained static site.

### 9.2 Deploying

Because vue2-elm is a static SPA with hash routing, deployment is simply **serving the `elm/`
directory** from any static host (nginx, Apache, a CDN, Netlify, Surge, GitHub Pages, etc.).
No server-side routing rules are required.

> [!IMPORTANT]
> The production build hard-codes `baseUrl = //elm.cangdu.org` (via `env.js`). If you host the
> front end yourself **and** run your own backend, you must edit `env.js` so the production
> `baseUrl`/`imgBaseUrl` point at *your* API host before building. Otherwise the deployed app
> will call `elm.cangdu.org`.

### 9.3 Deployment checklist

- [ ] Backend (`node-elm` or equivalent) deployed and reachable over HTTPS.
- [ ] `src/config/env.js` production `baseUrl`/`imgBaseUrl` point to your API + image hosts.
- [ ] `npm run build` completed without errors.
- [ ] `elm/` served by a static host; `assetsPublicPath` matches the serving path.
- [ ] App verified in a mobile viewport over HTTPS.

> [!NOTE]
> The project's own demo notes emphasize that the preview and its interfaces must be accessed
> over **HTTPS**. Mixed-content (HTTPS page calling HTTP API) will be blocked by browsers.

---

## 10. Security Analysis

vue2-elm is a learning project, and its security posture reflects that. A maintainer adapting it
toward anything real should weigh the following.

### 10.1 Authentication & session

- Auth state is carried by **cookies** (`fetch` uses `credentials: 'include'`) plus a client-side
  `user_id` stored in `localStorage`. The real session authority is the backend cookie; the
  `localStorage` value is convenience state.
- The login flow (`src/page/login/login.vue`) supports SMS and account+captcha modes. Against
  the demo backend, **unknown accounts auto-register on login** — acceptable for a sandbox,
  unacceptable for production.

### 10.2 Transport

- HTTPS is required (the demo explicitly calls this out). The hard-coded `//`-relative
  `baseUrl` inherits the page scheme, which is the correct behavior on an HTTPS page.
- `cache: "force-cache"` on every request risks serving stale authenticated data from cache.

### 10.3 Client-side validation only

- Input validation (phone format via `/^1\d{10}$/`, SMS code via `/^\d{6}$/`, required fields)
  is purely client-side and user-experience oriented. **All real authorization must be enforced
  by the backend** — the front end cannot be trusted.

### 10.4 Sensitive data exposure

- `productionSourceMap: true` ships source maps to production, exposing readable source. Fine for
  an open-source learning project; disable for anything proprietary.
- No secrets live in the front end (correctly) — there are no API keys in the client.

> [!CAUTION]
> Do not treat vue2-elm as a security template. Its value is in UI/state architecture, not in
> auth hardening. Any production fork needs server-enforced authz, CSRF protection appropriate to
> its cookie auth, a sane cache policy, and removal of auto-registration.

---

## 11. Code Quality & Patterns

### 11.1 Strengths

- **Clean layering.** Config / service / store / pages / components are well separated.
- **Centralized API surface.** `getData.js` as the single endpoint catalog is exemplary and makes
  the backend contract auditable in one place.
- **Named-chunk lazy loading** keeps the initial payload small across ~45 screens.
- **Pragmatic Vuex usage.** State is reserved for genuinely shared concerns (cart, user,
  checkout hand-off between deeply nested routes) — exactly where prop-drilling would hurt.
- **localStorage persistence** of cart and session gives a resilient UX.

### 11.2 Rough edges

- **Dead imports** of `localapi`/`proapi` (see [§4.1](#41-srcconfigenvjs--environment-switching)).
- **Empty getters** layer; derived state is scattered in components.
- **Two parallel infinite-scroll implementations** (the `mixin.js` directive and `mUtils.loadMore`).
- **Bilingual inline comments** (Chinese-first) — informative but a barrier for non-Chinese
  maintainers (mitigated by the bundled `README-en.md`).
- **`cache: "force-cache"`** as a global default is risky for a data API.
- **Hand-rolled `animate`** duplicates what CSS transitions / a small lib would handle more
  robustly.

### 11.3 Conventions to follow when extending

- Add backend calls only in `getData.js`; never build URLs in a page.
- Register any new API prefix in the `config/index.js` dev-proxy `context`.
- Use `mutation-types.js` constants for new mutations.
- Keep dimensions in `rem` so the layout scales with `rem.js`.
- Opt a route into caching with `meta: { keepAlive: true }` only when scroll/state preservation
  is genuinely wanted.

---

## 12. Operations & Troubleshooting

Because there is no server to operate, "operations" here means build/host health and the
external-backend dependency.

| Symptom | Likely cause | Resolution |
|---|---|---|
| All data calls return `ERROR_DATA` / `404` | Backend down or wrong host | Run local `node-elm`; use `npm run local`; verify `baseUrl` |
| `npm install` fails on `node-sass` | Node version mismatch | Use Node 6–8 via `nvm`; reinstall |
| `browserslist@1.7.7` warning | Stale caniuse data | Harmless; ignore |
| UI oversized on desktop | rem layout for 320 px | View in mobile emulation |
| Mixed-content blocked | HTTPS page → HTTP API | Serve API over HTTPS |
| Login captcha image broken | Backend unreachable | Fix backend; tap "换一张" to refetch |
| Deployed app calls `cangdu.org` unexpectedly | Production `baseUrl` not changed | Edit `env.js`, rebuild |

> [!TIP]
> When diagnosing "nothing loads," open DevTools → **Network** and inspect a failing request.
> A blocked/`ERR_CONNECTION` or non-200 to a `/v1`/`/shopping`/… path confirms a backend
> connectivity problem rather than a front-end bug.

---

## 13. Potential Improvements

A prioritized list for anyone modernizing the project:

1. **Modernize the toolchain.** webpack 1 → 5 and Babel 6 → current remove the `node-sass`
   install pain and unlock current tooling. This is the highest-leverage change.
2. **Migrate Vue 2 → Vue 3.** Composition API and Proxy reactivity would eliminate the
   `{...spread}` cart workaround and the empty-getters awkwardness.
3. **Remove dead code.** Delete the `localapi`/`proapi` imports and one of the two duplicate
   infinite-scroll implementations.
4. **Externalize configuration.** Replace the hard-coded hosts in `env.js` with build-time
   environment variables so self-hosting doesn't require editing source.
5. **Fix the cache policy.** Replace the global `cache: "force-cache"` with per-request caching
   (or none) to avoid stale data.
6. **Consolidate data fetching.** Move component-level fetches into Vuex actions for testability,
   or adopt a data-fetching library.
7. **Replace `node-sass`** with `dart-sass` (`sass`) to remove native-build fragility.
8. **Add tests.** There is no test suite; component and store unit tests would make refactors
   (especially the cart) safe.
9. **Disable production source maps** for any non-open deployment.

---

## 14. Quick Reference

### 14.1 Project map

```text
vue2-elm/
├── build/                  webpack config, dev-server, build scripts
├── config/index.js         build paths + dev proxy (port 8000, proxypath cangdu.org:8001)
├── elm/                    PRE-BUILT output (not source)
├── src/
│   ├── main.js             entry: router + store + rem, mounts #app
│   ├── App.vue             shell: dual router-view + keep-alive + svg sprite
│   ├── config/
│   │   ├── env.js          baseUrl / imgBaseUrl / routerMode (hash)
│   │   ├── fetch.js         HTTP client (fetch + XHR fallback)
│   │   ├── mUtils.js        localStorage, getStyle, loadMore, showBack, animate
│   │   └── rem.js           root font-size = 20 * (width/320) px
│   ├── service/getData.js   ALL API endpoints (one fn each)
│   ├── store/               Vuex: state, mutations, action, getters(empty), mutation-types
│   ├── router/router.js     route tree (lazy require.ensure chunks)
│   ├── components/
│   │   ├── common/          shoplist, buyCart, loading, alertTip, ratingStar, computeTime, svg, mixin
│   │   ├── header/head.vue
│   │   └── footer/footGuide.vue
│   ├── page/                ~45 screens, nested children
│   └── style/               common.scss, mixin.scss
├── package.json            scripts: dev / local / build
└── README.md / README-en.md
```

### 14.2 Commands

| Command | Effect |
|---|---|
| `npm install` / `yarn` | Install dependencies |
| `npm run dev` | Dev server → public backend (`NODE_ENV=online`) |
| `npm run local` | Dev server → local `node-elm` (`NODE_ENV=local`) |
| `npm run build` | Production build → `elm/` |

### 14.3 Key facts

| Fact | Value |
|---|---|
| Dev server port | `8000` |
| Dev proxy target | `http://cangdu.org:8001` |
| Production API host | `//elm.cangdu.org` |
| Image CDN | `https://fuss10.elemecdn.com` |
| Router mode | `hash` |
| Default geohash | `31.22299,121.36025` (Shanghai) |
| localStorage keys | `buyCart`, `user_id` |
| Payment | Intentionally not implemented |
| Backend repo | [`node-elm`](https://github.com/bailicangdu/node-elm) |
| Admin console | [`vue2-manage`](https://github.com/bailicangdu/vue2-manage) |
| License | GPL |

---

_Generated June 2026 from bailicangdu/vue2-elm at b85ff02. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
