Files
Chengdong Zhang 21e99bb52b feat(mall): classic B2B2C PC storefront with fixed mock API layer
- Mock adapter implementing @vmall/shared ApiClient (localStorage-persisted
  cart/orders/invoices), runtime switch via mockApi flag (default on)
- Fixed bilingual mock catalog: 3-level categories, 24 products w/ SKUs,
  stores, brands, banners, floors, seckill/collective/integral, comments,
  coupons, addresses, seeded orders/shipments/invoices
- B2B2C mall shell: top bar, logo/search/cart header, dark nav + category
  mega-menu, value-prop footer, back-to-top; 1200px grid, #ca151e theme
- UI primitives replacing element-plus: carousel, pagination, breadcrumb,
  qty stepper, rating stars, modal, tabs, step bar, product card
- Pages: home floors, search (filters/sort/paging), goods detail (SKU
  picker, store rail, review tabs), cart -> checkout -> pay -> success,
  auth pages, user center (dashboard/orders/addresses/favorites/coupons/
  invoices), stores, seckill, collective, integral
- i18n split into per-domain locale modules (en+zh)
- OpenSpec change mall-pc-storefront-replica archived; all specs green
2026-09-17 19:13:29 +08:00

24 KiB
Raw Permalink Blame History

PC buyer mall reference digest

1. Home route table

resources/js/plugins/routes/index.js defines one parent path: '/' component Home/index (the global buyer shell). Its children are:

Path Page file Purpose / mock data boundary
/ Home/default.vue Home landing page: hero/banner, quick links and category product floors. /home response supplies goods groups; each group has name, goods[], and each product has id, goods_master_image, goods_name, goods_subname, goods_price, goods_market_price.
/login Home/auth/login.vue Username/password login plus WeChat login.
/register Home/auth/register.vue Phone registration, password confirmation and SMS code.
/forget_password Home/auth/forget_password.vue Phone, new password/confirmation and SMS code reset.
/stores Home/stores/list.vue Store directory, default/distance sort, pagination.
/store/:id Home/stores/info.vue Store profile/rating, store sales rail, product listing/sort.
/store/join Home/stores/index.vue Merchant onboarding landing; links into join steps.
/store/step_1, /store/step_2, /store/step_3 corresponding Home/stores/step_*.vue Join agreement/data/review state steps.
/s/:params? Home/goods/index.vue Product search/list; optional base64 JSON query (keywords, pid/sid/tid, class_id, brand_id, sort_type, sort_order).
/goods/:id Home/goods/info.vue Product detail, gallery/zoom, SKU, buy/cart, store, detail/comments/service.
/carts Home/carts/index.vue Cart grouped by store, selection/quantity/removal and checkout.
/integral Home/integral/index.vue Points-mall home (banner, account panel, recommendations/category rails).
/integral/search/:params? Home/integral/goods.vue Points product search/category/sort list.
/integral/goods/:id Home/integral/info.vue Points product detail and quantity selection.
/integral/order/:id/:buy_num Home/integral/orders/index.vue Address + points redemption order.
/seckills Home/seckills/index.vue Timed seckill sessions/products.
/collectives/:params? Home/collectives/index.vue Group-buy product list, using ordinary goods filters with is_collective: 1.
/user Home/users/index.vue with child Home/users/default.vue Buyer center shell and dashboard.
/user/address (+ /form/:id?) Home/users/address/index.vue (+ form.vue) Address list and add/edit.
/user/comments, /user/comment/add/:id comments pages Buyer comments and add-comment flow.
/user/info, /user/safe/**, /user/oauth, /user/cashes user subpages Profile, safety/password/phone, OAuth binding and withdrawal.
/user/favorites Home/users/favorites/index.vue Toggle product favorites/store follows.
/user/money, /user/integral, /user/frozen_money money-log pages Balance, points and frozen-funds records.
/user/orders, /user/order/:id, /user/order/comment/:id, /user/order/refund/** order subpages Order list/detail/comment/refund flows.
/user/integral_order integral-order page Users points orders.
/user/coupons Home/users/coupons/index.vue Coupon table.
/user/distribution/** distribution pages Distribution profile/members/commissions.
/user/article/:name article page Named announcement/help/about content.
/order/before/:params Home/orders/before.vue Address/coupon/remark and order preview.
/order/pay/:params Home/orders/pay.vue Payment selection and generated order summary.
/order/success Home/orders/success.vue Completed payment result.

views/App.vue is only <router-view/>; /Admin/login and /Seller/login are separate non-buyer routes (Admin/Seller areas exist but are out of this survey). router.js creates a history router, installs baseRoute, and has global progress/auth navigation logic.

2. Global layout anatomy

The naming is slightly split: views/Home/index.vue is the shell, views/Home/default.vue is the actual home landing page, and components/home/base.vue is the shared outer layout.

  1. components/home/top.vue fixed top bar (.head, 30px): 1200px .head_in; left links in order: 平台官网, separator, 商城官网, separator, 联系方式, separator, 关于我们, separator, city/location (default 北京市, optionally IP/AMap-resolved). Right side conditionally shows 登录/注册 or 欢迎您,nickname, 个人中心, 注销账号; then 商家入驻, 手机端, APP下载. Uses #ca151e for login/location/merchant links.
  2. components/home/head.vue fixed header (top:30px, white): .center_top center1200 logo (.shop_logo, 242x74), then .shop_top_seach with 434px text input (data.keywords), search icon button and .index_my_car “我的商城/我的购物车” with cart count badge .shop_car_dot. Below is 40px dark .shop_top_nav: 240px red .shop_top_nav_left “全部商品” (hover mounts leftbar), then links 首页, 店铺街, 秒杀, 拼团, 积分商城, 帮助中心. Header collapses logo/search area after scroll >200; home category menu can collapse after scroll. Search pushes /s/ + base64 JSON {keywords}.
  3. components/home/leftbar.vue category mega-menu: 240px x 450px .leftbar (white) or .leftbar2 (black translucent when over hero). Each top-level goodsClass renders .left_bar_block: .class_1 top category name, .class_2 up to three child names, and hidden hover .subbar 960px wide. Mega menu contains optional top tags, right brand tiles from goodsBrand[] (thumb), optional advert, and .subbar_subnav rows: child heading name, grandchildren {id,pid,name}. Navigation encodes pid/sid/tid/class_id into /s/:params.
  4. components/home/base.vue: <div class="home_default"><top/><div class="padding_content"/><slot name="main"/><foot/></div>. .padding_content is 228px to offset fixed top + header. On mount dispatches init/loadCommon (common config, classes, brands, cart, etc.). In Home/index.vue, the main slot is <head-view/><router-view/>; therefore every buyer page receives the top/header/footer shell. Home default.vue itself is just the route content under that shell.
  5. components/home/foot.vue: centered 1200px .shop_foot; four value propositions in order (“500强企业 品质保证”, “7天退货 15天换货”, “99元起免运费”, “448家维修网点 全国联保”), then copyright/ICP/security text interpolating common.web_name and common.icp, plus Element el-backtop. Replace el-backtop with a small fixed scroll-to-top control in Nuxt.

Element Plus pieces to replace in this shell: el-icon/icons (search, cart, etc.), el-image (native img with lazy loading), el-carousel/transition (CSS/Headless carousel), el-backtop, and any el-breadcrumb used by child pages.

3. Homepage sections and mock data

views/Home/default.vue is intentionally simpler than the nav labels suggest—there are no dedicated seckill/collective/brand sections in this file. Those are quick links in banner_adv; product floors are generic category groups.

  1. Hero carousel (components/home/banner.vue, first child): <el-carousel height=450> → each <el-carousel-item>.shop_banner background image (background-position:center; background-size:cover) → full 1200x450 router link. banner shape: [{image:string, url?:string, name?:string}]; default landing mock is one default_banner.jpg item. Nuxt replacement: regular CSS/JS carousel.
  2. Below-hero promo/quick-link strip (components/home/banner_adv.vue): .banner_bottom_adv center1200; left 240px six 78x80 cells in order: 实名认证 (/user/safe), 积分商城, 优惠团购, 秒杀活动, 公告 (/user), 入驻商家. Each icon is an image; right side has up to three 312x160 linked promo images. index_adv shape: [{image:string, url?:string, name?:string}]. Uses el-image (replace with img).
  3. Floor background: .index_bg gray #f5f5f5, top margin 40px and 20px/60px padding. For every data.goods category group, render .goods_class_list w1200; if v.goods.length > 0, first adv-view full-width placeholder (adv:{image,url?}), then .title group name + “查看更多”.
  4. Floor body: left .index_adv_goods_left 234x614 image from data.classLeftAdv[k].image_url or fallback pc_class_btadv.jpg; right .index_class_goods_right 966px with product cards in <ul>. Product cards are 220x300 white .product_act_in with 140x140 image, title, subtitle, red sale price and struck market price. Floor product shape: {id, goods_master_image, goods_name, goods_subname, goods_price, goods_market_price}; floor group shape: {name, goods:[product...]}. Note classLeftAdv is initialized but not assigned from /home in the shown code, so a mock can supply it separately.
  5. components/home/adv.vue: a linked lazy image, default width/height:'100%', margin top/bottom 40px; adv:{image,url?}.

Visual card behavior: hover lifts cards by 3px and adds 0 2px 12px rgba(0,0,0,.1). A Nuxt mock module should include home {goods: Floor[], classLeftAdv: Adv[]}, banner, and index_adv even though the current banner/floor images are hardcoded assets.

4. Goods list (views/Home/goods/index.vue)

  • Breadcrumb .w1200 breadcrumb: 首页, then either 搜索 "..." 结果列表 (decoded keywords) or 商品列表.
  • Filter block .goods_where w1200: three .item rows: 商品分类, 品牌筛选, 筛选排序. Category has “全部”, top-level common.classes, selected second-level and third-level children; each class is {id,name,children:[...]}. Brand options are {id,name}. The route state/query shape is {keywords?,pid?,sid?,tid?,class_id?:number[],brand_id?,sort_type?,sort_order?} encoded in base64 path.
  • Important reference limitation: despite a request to survey “attrs/brands/price”, this template has no attribute facet row and no price-range controls. It exposes category + brands + sort only. Do not infer an attrs/price UI from this file; if the new app adds them, it is an intentional enhancement rather than parity.
  • Sort choices are default, 价格 (goods_price), 销量 (goods_sale), 评论 (order_comment_count), each toggling sort_order asc/desc. In the shown template the sort controls are also in .goods_where, not a separate bar.
  • Results: .s_goods_content; .s_goods_list five cards/row (.item 224x364). Each card <dl> has 176x176 image, title, red price, footer “立即购买” and {order_comment_count} 人评论; links /goods/{id}. Product list shape: {id,goods_master_image,goods_name,goods_price,order_comment_count} (market/subname are not rendered here).
  • Pagination: .fy Element el-pagination total/prev/pager/next; response fields are data, total, per_page, last_page, current_page; empty state is el-empty. Replace with plain button/page-number pagination, empty state, and native img.

5. Goods detail (views/Home/goods/info.vue)

  • Breadcrumb: .mbx w1200: 首页 → every goods_info.classList[{name}] → product name.
  • Gallery: .goods_info_top_left has .goods_image_item containing custom components/home/vue-piczoom.vue, then thumbnail rail .pic_zoom_list with previous/next controls and goods_images_thumb_150[]; selected index chose_img_pos, display image goods_images_thumb_400[], zoom source goods_images[]. Mock fields: goods_images, goods_images_thumb_400, goods_images_thumb_150 arrays of URLs.
  • Product summary: .goods_info_top_right title/subtitle, favorite toggle, sale/market prices, sale count, QR hint, coupon cards, optional seckill and group-buy activity, full-reduction messages. Core goods_info: id,goods_name,goods_subname,goods_price,goods_market_price,goods_sale,goods_stock,goods_content,classList,attrList,skuList,sale_list; coupons[] entries {id,money,use_money}, full_reductions[] {money,use_money}, seckills {discount,end_time,format_time}, collectives {discount,need}, collective_list[] {id,avatar,nickname,orders_count,need}.
  • SKU/quantity: attrList[] each {name,specs:[{id,name,is_chose?}]}; skuList[] each {id,spec_id:number[],goods_price,goods_market_price,goods_stock}. Chosen sku_id, buy_num, current stock; “立即购买”, “加入购物车” (regular goods) or “立即抢购” (seckill). Group buy adds collective_id.
  • Store card / left rail: .goods_info_content left .left_item store card with store_info.store_name, store_company_name, area_info, store_address, id, user_id; four scores in rate_info (scoreAll, agreeAll, serviceAll, speedAll); buttons enter store/customer service. Below, 销售排行 from goods_info.sale_list[] product {id,goods_master_image,goods_name,goods_price}.
  • Tabs: right .right_item Element tabs: 商品详情 renders sanitized goods_content; 用户评价 renders statistics {all,good,commonly,bad,rate}, comment filters, paginated comments[] where {avatar,nickname,score,content,image:string[],reply}; 售后服务 renders store_info.after_sale_service. Replace el-tabs, el-rate, el-pagination, el-image with native tabs, stars and controls. Chat is a separate common component and may be replaced with a simple contact CTA if chat is not in scope.

6. Cart and order flow

Cart (views/Home/carts/index.vue): .create_order_1 w1200 begins a four-step .step_bar (cart active, address, payment, success). If nonempty, .cart_th header columns are 商品, 规格, 单价(元), 数量, 小计, 操作. .cart_table groups data.list by store: store group {store_id,store_name,checked,css,cart_list[]}, item {cart_id,goods_id,goods_image,goods_name,sku_name,goods_price,buy_num,checked}. Each item has checkbox, 40px thumb/name link, SKU, +/- quantity controls, subtotal and 移除. Bottom select-all summarizes allCount, allPrice and 结算; empty el-empty. Pagination response state exists (data.params={page,per_page,last_page,total}) but no pager is rendered. Replace Element checkboxes/empty/icons.

Order before (orders/before.vue): same step bar with cart/address active. Address selection list entries {id,receive_name,receive_tel,area_info,address,is_default}; then order preview grouped by store. Order group includes store_info:{id,store_logo,store_name}, coupon_id, coupons[]:{id,money}, is_coupon, goods_list[]:{goods_master_image,goods_name,sku_name,goods_price,buy_num,total}. Shows remark textarea (data.remark), total, create-order button. Submit payload uses encoded route params, address_id, comma-joined coupon_id, remark.

Order pay (orders/pay.vue): step bar through payment; balance panel userInfo.money; payment options are balance/password, WeChat and Alipay. Displays generated order[] groups {order_no,coupon_money,order_goods[]:{goods_image,goods_name,sku_name,goods_price,buy_num,total_price}}, total, freight_money; balance password and QR modal state {pay_password,qr_code}. Replace el-dialog, QR Vue and Element grid with Tailwind modal/grid and a QR library.

Order success (orders/success.vue): all four steps active, success result (“订单支付成功!”), link to /user; replace Element el-result with a simple success panel.

7. User center

Home/users/index.vue is a two-column .user_default shell: 234px .user_left + 20px gap, 946px .user_right with nested <router-view>. Profile card has avatar, nickname, edit link, account-profile progress (check) and safety indicators (phone, check). Nav menu groups/items, in order:

  • 订单中心: 我的订单 /user/orders, 收货地址 /user/address, 评论列表 /user/comments.
  • 会员中心: 个人中心 /user, 用户资料 /user/info, 账户安全 /user/safe, 账号绑定 /user/oauth, 资金提现 /user/cashes, 收藏/关注 /user/favorites, 我的优惠券 /user/coupons.
  • 积分商城: 积分订单 /user/integral_order.
  • 资产记录: 平台余额 /user/money, 冻结资金 /user/frozen_money, 平台积分 /user/integral.
  • 分销分佣: 分销信息 /user/distribution, 分销会员 /user/distribution_users, 分销佣金 /user/distribution_logs.
  • 帮助中心: 网站公告、其他合作、帮助中心、关于我们 at /user/article/:name.

users/default.vue dashboard data shape: user_info:{money,integral,frozen_money}, count[5] for pending payment/shipping/receipt/comment/after-sale, order[] with {id,created_at,order_no,order_status,order_status_cn,order_goods[]:{goods_image,goods_name,sku_name,buy_num,total_price}}, fav[] with {out_id,goods_master_image,goods_name,goods_price}, and local history[] with {id,image,goods_name,goods_price}. It renders balance cards, five status links/badges, recent orders, recent favorites and browsing history, all in .user_main cards.

Focused page shapes:

  • Address: addresses[]:{id,receive_name,receive_tel,area_info,address,is_default}; add/edit fields are receive_name, receive_tel, area (cascader), address, is_default; paginated response. Uses el-dialog, el-form, el-pagination.
  • Orders: filter {order_status,order_no,created_at:[start,end],is_type}; statuses -1/0/1/2/3/4/5/6 = all/cancelled/wait-pay/wait-ship/wait-receive/wait-comment/after-sale/completed. Cards use the same order/item shape above and action buttons; logistics modal expects order_info.delivery_list[]:{context,time}.
  • Favorites: toggle isGoods; product table fields goods_master_image,goods_name,goods_price,created_at, store table fields store_logo,store_name,created_at, records carry is_type,out_id.
  • Coupons: generic table columns name,money,use_money,end_time,created_at; no custom card UI. table-view is a large Element-based shared component and should be replaced with a plain responsive table in Nuxt.

8. Stores, seckill, collective and integral pages

  • Stores list (stores/list.vue): breadcrumb + one sort row (default/distance), then store rows with 80px logo and {id,store_logo,store_name,store_company_name,area_info,store_address,distance}; center hardcodes 好评率 100%, right shows distance and 进入店铺; 30/page pagination. Location comes from local lonlat. Replace el-breadcrumb, icons, image and pagination.
  • Store info (stores/info.vue): 350px banner from store_info.store_slide[], left store profile/rates/company/address/favorite/contact and sales ranking; right store-scoped sort (default/price/sales/comments) plus 220x300 product cards with {id,goods_master_image,goods_name,goods_subname,goods_price,goods_market_price} and pagination. Store payload adds id,user_id,store_verify,store_status,store_logo,sale_list[], rate:{scoreAll,agreeAll,serviceAll,speedAll}, and after_sale_service where relevant.
  • Seckills (seckills/index.vue): breadcrumb, four equal timed session selectors (timeList hours, timeIndex, timeFormat countdown), then a 4-column product grid using {id,goods_master_image,goods_name,goods_subname,goods_price,goods_market_price}, paginated by {page,per_page,last_page,total,start_time}. Countdown refreshes the page when the hour expires.
  • Collectives (collectives/index.vue): 350px collective banner, then the same 3-row category/brand/sort filter and 5-column standard goods cards as /s; API query adds is_collective:1. Mock the same class/brand/filter/product/page shapes as goods list.
  • Integral home (integral/index.vue): 350px banner, four info blocks (login/register or avatar + userInfo.integral, plus private customization/24H/priority cards), then “热门推荐” and category rails. Data is {store_slide?,userInfo:{avatar,integral},recommend:[{id,goods_master_image,goods_name,goods_price,goods_market_price}],list:[{name,integral_goods:[same product shape]}]}.
  • Integral goods/detail/order: integral/goods.vue is a category/sort-only list (class[]:{id,name}, product card uses “立即兑换”); integral/info.vue is zoom gallery + points price/market price, sale/stock and quantity, “立即兑换”, with goods_content; integral/orders/index.vue reuses address/remark four-step checkout but has one points product {goods_master_image,goods_name,sku_name,goods_price,buy_num}, and submits {id,buy_num,address_id,remark} to points pay.

9. Auth pages

All three use .home_login with user_login__bgs.png, a 1200px .login_block, and right-floating white .login_item 400x450, 40px padding, 50px top margin. Inputs are 320px wide, 35px high; submit is full-width red #ca151e; no shared top/header is rendered inside the auth template (they still are routed beneath the Home shell in the current route tree).

  • Login: title tabs “帐号登录 | 扫码登录”; username, password; 登录 button; links 注册 and 忘记密码; Element divider and WeChat icon/click. Mock {username,password,provider:'users'}.
  • Register: title “注册账号 | 账号登录”; username (phone), password, re_password, SMS code, send-code countdown (code_text, math); 注册 + forgot-password link. Optional inviter id from query/session. Mock {username,password,re_password,code,code_text,math}.
  • Forgot password: title “忘记密码 | 账号登录”; username, new password, re_password, SMS code, send-code state; 确定 + 立即登录. Same {username,password,re_password,code,code_text,math}.

Element replacements here: el-divider → border/label, el-message → toast, and ordinary inputs/buttons can be native/Tailwind.

10. Theme and visual language

  • plugins/css/style.css resets body/common block margins and list styles, sets html,body,#app height 100%, base font-size:14px, color #333, no explicit font-family (browser/system font applies). Links default #333 with no decoration; hover #000. body margin is zero.
  • Fixed desktop container is .center1200 and .w1200, both width:1200px;margin:0 auto; floats are .float_left/.float_right; .clear clears floats. Other recurring utility classes: .breadcrumb (30px vertical margin), .shadow (0 2px 12px rgba(0,0,0,.1)), .x20 1px gray divider with 20px margin, .user_main white padded card, .table_lists min-height 600px, .block_title 16px with 3px red left border.
  • base.scss has $admin_background:#f4f4f4; .home Element primary CSS vars are --el-color-primary:#e50e19 and light variants, but storefront source consistently uses #ca151e for active links, red prices, buttons, selected filters, nav, and borders. Preserve both: #ca151e is the visual brand red; #e50e19 is Element theme fallback. Status colors are green #67c23a, gray #909399, yellow #e6a23c, red #f56c6c (round utility classes).
  • Button language: white bordered .default_btn, red .error_btn, yellow .warn_btn, green .success_btn, gray .gray_btn; all compact 12px, 3px radius, 6px/15px padding. Cards/rows are mostly white with #efefef 1px rules, subtle hover lift/shadow, restrained 1216px text, and red price emphasis. Home/floor backgrounds use #f5f5f5; user center uses #f1f1f1; header nav is #333 with a red category block.
  • Replication recommendation: keep 1200px desktop grid, 2030px section rhythm, 20px card gaps, 40px header/nav spacing, 220224px product cards, gray separators, low-radius 3px controls, red active states and strikethrough market prices. Since the reference relies heavily on Element Plus (el-carousel, el-image, el-breadcrumb, el-pagination, el-empty, el-checkbox, el-select, el-dialog, el-form, el-tabs, el-rate, el-result, el-badge, el-progress, el-timeline, el-icon), a fresh Nuxt 3/Tailwind build should provide plain semantic replacements rather than porting Elements runtime.